diff options
570 files changed, 12845 insertions, 5008 deletions
diff --git a/.github/workflows/android_builds.yml b/.github/workflows/android_builds.yml index ee75d53282..69ab830829 100644 --- a/.github/workflows/android_builds.yml +++ b/.github/workflows/android_builds.yml @@ -85,6 +85,7 @@ jobs: run: | cd platform/android/java ./gradlew generateGodotEditor + ./gradlew generateGodotMetaEditor cd ../../.. ls -l bin/android_editor_builds/ diff --git a/.github/workflows/godot_cpp_test.yml b/.github/workflows/godot_cpp_test.yml index e1c37bbc1e..e3223c799b 100644 --- a/.github/workflows/godot_cpp_test.yml +++ b/.github/workflows/godot_cpp_test.yml @@ -56,3 +56,13 @@ jobs: cd godot-cpp/test scons target=template_debug dev_build=yes cd ../.. + + gdextension-c-compile: + runs-on: "ubuntu-20.04" + name: "Check GDExtension header with a C compiler" + steps: + - uses: actions/checkout@v4 + + - name: "Run C compiler on gdextension_interface.h" + run: | + gcc -c core/extension/gdextension_interface.h diff --git a/SConstruct b/SConstruct index 0297cd6e61..5c1ccd2449 100644 --- a/SConstruct +++ b/SConstruct @@ -791,6 +791,8 @@ else: # Note that this is still not complete conformance, as certain Windows-related headers # don't compile under complete conformance. env.Prepend(CCFLAGS=["/permissive-"]) + # Allow use of `__cplusplus` macro to determine C++ standard universally. + env.Prepend(CXXFLAGS=["/Zc:__cplusplus"]) # Disable exception handling. Godot doesn't use exceptions anywhere, and this # saves around 20% of binary size and very significant build time (GH-80513). 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 e764b9c112..d4b50facb2 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -675,7 +675,7 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const String } Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoader> &p_loader) { - ERR_FAIL_NULL_V_MSG(p_loader, FAILED, "Can't open GDExtension without a loader."); + ERR_FAIL_COND_V_MSG(p_loader.is_null(), FAILED, "Can't open GDExtension without a loader."); loader = p_loader; Error err = loader->open_library(p_path); 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 cac76d39bd..d3132baf1b 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -268,7 +268,7 @@ typedef void (*GDExtensionClassReference)(GDExtensionClassInstancePtr p_instance typedef void (*GDExtensionClassUnreference)(GDExtensionClassInstancePtr p_instance); typedef void (*GDExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance)(void *p_class_userdata); -typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, bool p_notify_postinitialize); +typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance2)(void *p_class_userdata, GDExtensionBool p_notify_postinitialize); typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance); typedef GDExtensionClassInstancePtr (*GDExtensionClassRecreateInstance)(void *p_class_userdata, GDExtensionObjectPtr p_object); typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name); @@ -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/file_access.cpp b/core/io/file_access.cpp index c857d54925..d919243e6b 100644 --- a/core/io/file_access.cpp +++ b/core/io/file_access.cpp @@ -223,59 +223,44 @@ String FileAccess::fix_path(const String &p_path) const { } /* these are all implemented for ease of porting, then can later be optimized */ +uint8_t FileAccess::get_8() const { + uint8_t data = 0; + get_buffer(&data, sizeof(uint8_t)); -uint16_t FileAccess::get_16() const { - uint16_t res; - uint8_t a, b; + return data; +} - a = get_8(); - b = get_8(); +uint16_t FileAccess::get_16() const { + uint16_t data = 0; + get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint16_t)); if (big_endian) { - SWAP(a, b); + data = BSWAP16(data); } - res = b; - res <<= 8; - res |= a; - - return res; + return data; } uint32_t FileAccess::get_32() const { - uint32_t res; - uint16_t a, b; - - a = get_16(); - b = get_16(); + uint32_t data = 0; + get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint32_t)); if (big_endian) { - SWAP(a, b); + data = BSWAP32(data); } - res = b; - res <<= 16; - res |= a; - - return res; + return data; } uint64_t FileAccess::get_64() const { - uint64_t res; - uint32_t a, b; - - a = get_32(); - b = get_32(); + uint64_t data = 0; + get_buffer(reinterpret_cast<uint8_t *>(&data), sizeof(uint64_t)); if (big_endian) { - SWAP(a, b); + data = BSWAP64(data); } - res = b; - res <<= 32; - res |= a; - - return res; + return data; } float FileAccess::get_float() const { @@ -465,17 +450,6 @@ String FileAccess::get_as_text(bool p_skip_cr) const { return text; } -uint64_t FileAccess::get_buffer(uint8_t *p_dst, uint64_t p_length) const { - ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); - - uint64_t i = 0; - for (i = 0; i < p_length && !eof_reached(); i++) { - p_dst[i] = get_8(); - } - - return i; -} - Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const { Vector<uint8_t> data; @@ -488,7 +462,7 @@ Vector<uint8_t> FileAccess::get_buffer(int64_t p_length) const { ERR_FAIL_COND_V_MSG(err != OK, data, "Can't resize data to " + itos(p_length) + " elements."); uint8_t *w = data.ptrw(); - int64_t len = get_buffer(&w[0], p_length); + int64_t len = get_buffer(w, p_length); if (len < p_length) { data.resize(len); @@ -512,46 +486,32 @@ String FileAccess::get_as_utf8_string(bool p_skip_cr) const { return s; } -void FileAccess::store_16(uint16_t p_dest) { - uint8_t a, b; - - a = p_dest & 0xFF; - b = p_dest >> 8; +void FileAccess::store_8(uint8_t p_dest) { + store_buffer(&p_dest, sizeof(uint8_t)); +} +void FileAccess::store_16(uint16_t p_dest) { if (big_endian) { - SWAP(a, b); + p_dest = BSWAP16(p_dest); } - store_8(a); - store_8(b); + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint16_t)); } void FileAccess::store_32(uint32_t p_dest) { - uint16_t a, b; - - a = p_dest & 0xFFFF; - b = p_dest >> 16; - if (big_endian) { - SWAP(a, b); + p_dest = BSWAP32(p_dest); } - store_16(a); - store_16(b); + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint32_t)); } void FileAccess::store_64(uint64_t p_dest) { - uint32_t a, b; - - a = p_dest & 0xFFFFFFFF; - b = p_dest >> 32; - if (big_endian) { - SWAP(a, b); + p_dest = BSWAP64(p_dest); } - store_32(a); - store_32(b); + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), sizeof(uint64_t)); } void FileAccess::store_real(real_t p_real) { @@ -708,22 +668,11 @@ void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_ store_line(line); } -void FileAccess::store_buffer(const uint8_t *p_src, uint64_t p_length) { - ERR_FAIL_COND(!p_src && p_length > 0); - for (uint64_t i = 0; i < p_length; i++) { - store_8(p_src[i]); - } -} - void FileAccess::store_buffer(const Vector<uint8_t> &p_buffer) { uint64_t len = p_buffer.size(); - if (len == 0) { - return; - } - const uint8_t *r = p_buffer.ptr(); - store_buffer(&r[0], len); + store_buffer(r, len); } void FileAccess::store_var(const Variant &p_var, bool p_full_objects) { diff --git a/core/io/file_access.h b/core/io/file_access.h index 2ab84db4b6..2f4d1a8604 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -137,7 +137,7 @@ public: virtual bool eof_reached() const = 0; ///< reading passed EOF - virtual uint8_t get_8() const = 0; ///< get a byte + virtual uint8_t get_8() const; ///< get a byte virtual uint16_t get_16() const; ///< get 16 bits uint virtual uint32_t get_32() const; ///< get 32 bits uint virtual uint64_t get_64() const; ///< get 64 bits uint @@ -148,7 +148,7 @@ public: Variant get_var(bool p_allow_objects = false) const; - virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const; ///< get an array of bytes + virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const = 0; ///< get an array of bytes, needs to be overwritten by children. Vector<uint8_t> get_buffer(int64_t p_length) const; virtual String get_line() const; virtual String get_token() const; @@ -168,7 +168,7 @@ public: virtual Error resize(int64_t p_length) = 0; virtual void flush() = 0; - virtual void store_8(uint8_t p_dest) = 0; ///< store a byte + virtual void store_8(uint8_t p_dest); ///< store a byte virtual void store_16(uint16_t p_dest); ///< store 16 bits uint virtual void store_32(uint32_t p_dest); ///< store 32 bits uint virtual void store_64(uint64_t p_dest); ///< store 64 bits uint @@ -184,7 +184,7 @@ public: virtual void store_pascal_string(const String &p_string); virtual String get_pascal_string(); - virtual void store_buffer(const uint8_t *p_src, uint64_t p_length); ///< store an array of bytes + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) = 0; ///< store an array of bytes, needs to be overwritten by children. void store_buffer(const Vector<uint8_t> &p_buffer); void store_var(const Variant &p_var, bool p_full_objects = false); diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp index 0f00bd292c..3602baf8c5 100644 --- a/core/io/file_access_compressed.cpp +++ b/core/io/file_access_compressed.cpp @@ -260,38 +260,6 @@ bool FileAccessCompressed::eof_reached() const { } } -uint8_t FileAccessCompressed::get_8() const { - ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use."); - ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode."); - - if (at_end) { - read_eof = true; - return 0; - } - - uint8_t ret = read_ptr[read_pos]; - - read_pos++; - if (read_pos >= read_block_size) { - read_block++; - - if (read_block < read_block_count) { - //read another block of compressed data - f->get_buffer(comp_buffer.ptrw(), read_blocks[read_block].csize); - int total = Compression::decompress(buffer.ptrw(), read_blocks.size() == 1 ? read_total : block_size, comp_buffer.ptr(), read_blocks[read_block].csize, cmode); - ERR_FAIL_COND_V_MSG(total == -1, 0, "Compressed file is corrupt."); - read_block_size = read_block == read_block_count - 1 ? read_total % block_size : block_size; - read_pos = 0; - - } else { - read_block--; - at_end = true; - } - } - - return ret; -} - uint64_t FileAccessCompressed::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use."); @@ -341,12 +309,13 @@ void FileAccessCompressed::flush() { // compressed files keep data in memory till close() } -void FileAccessCompressed::store_8(uint8_t p_dest) { +void FileAccessCompressed::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_COND_MSG(f.is_null(), "File must be opened before use."); ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); - WRITE_FIT(1); - write_ptr[write_pos++] = p_dest; + WRITE_FIT(p_length); + memcpy(write_ptr + write_pos, p_src, p_length); + write_pos += p_length; } bool FileAccessCompressed::file_exists(const String &p_name) { diff --git a/core/io/file_access_compressed.h b/core/io/file_access_compressed.h index f706c82f8e..ea9837dd03 100644 --- a/core/io/file_access_compressed.h +++ b/core/io/file_access_compressed.h @@ -83,14 +83,13 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/core/io/file_access_encrypted.cpp b/core/io/file_access_encrypted.cpp index b689f5b628..13d1e0c8fc 100644 --- a/core/io/file_access_encrypted.cpp +++ b/core/io/file_access_encrypted.cpp @@ -37,7 +37,7 @@ #include <stdio.h> Error FileAccessEncrypted::open_and_parse(Ref<FileAccess> p_base, const Vector<uint8_t> &p_key, Mode p_mode, bool p_with_magic) { - ERR_FAIL_COND_V_MSG(file != nullptr, ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open."); + ERR_FAIL_COND_V_MSG(file.is_valid(), ERR_ALREADY_IN_USE, "Can't open file while another file from path '" + file->get_path_absolute() + "' is open."); ERR_FAIL_COND_V(p_key.size() != 32, ERR_INVALID_PARAMETER); pos = 0; @@ -162,7 +162,7 @@ void FileAccessEncrypted::_close() { } bool FileAccessEncrypted::is_open() const { - return file != nullptr; + return file.is_valid(); } String FileAccessEncrypted::get_path() const { @@ -206,26 +206,13 @@ bool FileAccessEncrypted::eof_reached() const { return eofed; } -uint8_t FileAccessEncrypted::get_8() const { - ERR_FAIL_COND_V_MSG(writing, 0, "File has not been opened in read mode."); - if (pos >= get_length()) { - eofed = true; - return 0; - } - - uint8_t b = data[pos]; - pos++; - return b; -} - uint64_t FileAccessEncrypted::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V_MSG(writing, -1, "File has not been opened in read mode."); uint64_t to_copy = MIN(p_length, get_length() - pos); - for (uint64_t i = 0; i < to_copy; i++) { - p_dst[i] = data[pos++]; - } + memcpy(p_dst, data.ptr() + pos, to_copy); + pos += to_copy; if (to_copy < p_length) { eofed = true; @@ -242,17 +229,12 @@ void FileAccessEncrypted::store_buffer(const uint8_t *p_src, uint64_t p_length) ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); ERR_FAIL_COND(!p_src && p_length > 0); - if (pos < get_length()) { - for (uint64_t i = 0; i < p_length; i++) { - store_8(p_src[i]); - } - } else if (pos == get_length()) { + if (pos + p_length >= get_length()) { data.resize(pos + p_length); - for (uint64_t i = 0; i < p_length; i++) { - data.write[pos + i] = p_src[i]; - } - pos += p_length; } + + memcpy(data.ptrw() + pos, p_src, p_length); + pos += p_length; } void FileAccessEncrypted::flush() { @@ -261,18 +243,6 @@ void FileAccessEncrypted::flush() { // encrypted files keep data in memory till close() } -void FileAccessEncrypted::store_8(uint8_t p_dest) { - ERR_FAIL_COND_MSG(!writing, "File has not been opened in write mode."); - - if (pos < get_length()) { - data.write[pos] = p_dest; - pos++; - } else if (pos == get_length()) { - data.push_back(p_dest); - pos++; - } -} - bool FileAccessEncrypted::file_exists(const String &p_name) { Ref<FileAccess> fa = FileAccess::open(p_name, FileAccess::READ); if (fa.is_null()) { diff --git a/core/io/file_access_encrypted.h b/core/io/file_access_encrypted.h index 42afe49a5e..5f8c803d60 100644 --- a/core/io/file_access_encrypted.h +++ b/core/io/file_access_encrypted.h @@ -73,14 +73,12 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp index 9521a4f666..1541a5ed4a 100644 --- a/core/io/file_access_memory.cpp +++ b/core/io/file_access_memory.cpp @@ -122,16 +122,6 @@ bool FileAccessMemory::eof_reached() const { return pos >= length; } -uint8_t FileAccessMemory::get_8() const { - uint8_t ret = 0; - if (pos < length) { - ret = data[pos]; - } - ++pos; - - return ret; -} - uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_NULL_V(data, -1); @@ -157,16 +147,12 @@ void FileAccessMemory::flush() { ERR_FAIL_NULL(data); } -void FileAccessMemory::store_8(uint8_t p_byte) { - ERR_FAIL_NULL(data); - ERR_FAIL_COND(pos >= length); - data[pos++] = p_byte; -} - void FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_COND(!p_src && p_length > 0); + uint64_t left = length - pos; uint64_t write = MIN(p_length, left); + if (write < p_length) { WARN_PRINT("Writing less data than requested"); } diff --git a/core/io/file_access_memory.h b/core/io/file_access_memory.h index e9fbc26d75..39e1528d97 100644 --- a/core/io/file_access_memory.h +++ b/core/io/file_access_memory.h @@ -55,15 +55,12 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte - virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; ///< get an array of bytes virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_byte) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 02bf0a6039..eec27ce0aa 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -313,17 +313,6 @@ bool FileAccessPack::eof_reached() const { return eof; } -uint8_t FileAccessPack::get_8() const { - ERR_FAIL_COND_V_MSG(f.is_null(), 0, "File must be opened before use."); - if (pos >= pf.size) { - eof = true; - return 0; - } - - pos++; - return f->get_8(); -} - uint64_t FileAccessPack::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V_MSG(f.is_null(), -1, "File must be opened before use."); ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); @@ -366,10 +355,6 @@ void FileAccessPack::flush() { ERR_FAIL(); } -void FileAccessPack::store_8(uint8_t p_dest) { - ERR_FAIL(); -} - void FileAccessPack::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL(); } diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h index 594ac8f089..595a36bca4 100644 --- a/core/io/file_access_pack.h +++ b/core/io/file_access_pack.h @@ -169,8 +169,6 @@ public: virtual bool eof_reached() const override; - virtual uint8_t get_8() const override; - virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual void set_big_endian(bool p_big_endian) override; @@ -179,8 +177,6 @@ public: virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; - virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_name) override; diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp index c0d1afc8e1..b33b7b35c3 100644 --- a/core/io/file_access_zip.cpp +++ b/core/io/file_access_zip.cpp @@ -291,12 +291,6 @@ bool FileAccessZip::eof_reached() const { return at_eof; } -uint8_t FileAccessZip::get_8() const { - uint8_t ret = 0; - get_buffer(&ret, 1); - return ret; -} - uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_NULL_V(zfile, -1); @@ -328,7 +322,7 @@ void FileAccessZip::flush() { ERR_FAIL(); } -void FileAccessZip::store_8(uint8_t p_dest) { +void FileAccessZip::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL(); } diff --git a/core/io/file_access_zip.h b/core/io/file_access_zip.h index 88b63e93e2..1e11e050df 100644 --- a/core/io/file_access_zip.h +++ b/core/io/file_access_zip.h @@ -95,14 +95,13 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/core/io/image.cpp b/core/io/image.cpp index f6065d984b..fcbe483e38 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -4225,7 +4225,7 @@ Dictionary Image::compute_image_metrics(const Ref<Image> p_compared_image, bool result["root_mean_squared"] = INFINITY; result["peak_snr"] = 0.0f; - ERR_FAIL_NULL_V(p_compared_image, result); + ERR_FAIL_COND_V(p_compared_image.is_null(), result); Error err = OK; Ref<Image> compared_image = duplicate(true); if (compared_image->is_compressed()) { diff --git a/core/io/plist.cpp b/core/io/plist.cpp index 86737609bf..8d91e6dec2 100644 --- a/core/io/plist.cpp +++ b/core/io/plist.cpp @@ -814,7 +814,7 @@ bool PList::load_string(const String &p_string, String &r_err_out) { } PackedByteArray PList::save_asn1() const { - if (root == nullptr) { + if (root.is_null()) { ERR_FAIL_V_MSG(PackedByteArray(), "PList: Invalid PList, no root node."); } size_t size = root->get_asn1_size(1); @@ -848,7 +848,7 @@ PackedByteArray PList::save_asn1() const { } String PList::save_text() const { - if (root == nullptr) { + if (root.is_null()) { ERR_FAIL_V_MSG(String(), "PList: Invalid PList, no root node."); } diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 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..41a8a569d0 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -857,6 +857,19 @@ Error ResourceLoaderBinary::load() { } } + 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()); + } + } + } + if (set_valid) { res->set(name, value); } @@ -2064,6 +2077,8 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant case Variant::DICTIONARY: { Dictionary d = p_variant; + _find_resources(d.get_typed_key_script()); + _find_resources(d.get_typed_value_script()); List<Variant> keys; d.get_key_list(&keys); for (const Variant &E : keys) { diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index b4c43abe00..a572dd562e 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -35,6 +35,8 @@ #include "core/os/os.h" #include "core/variant/variant_parser.h" +ResourceFormatImporterLoadOnStartup ResourceImporter::load_on_startup = nullptr; + bool ResourceFormatImporter::SortImporterByName::operator()(const Ref<ResourceImporter> &p_a, const Ref<ResourceImporter> &p_b) const { return p_a->get_importer_name() < p_b->get_importer_name(); } @@ -137,6 +139,20 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy } Ref<Resource> ResourceFormatImporter::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { +#ifdef TOOLS_ENABLED + // When loading a resource on startup, we use the load_on_startup callback, + // which executes the loading in the EditorFileSystem. It can reimport + // the resource and retry the load, allowing the resource to be loaded + // even if it is not yet imported. + if (ResourceImporter::load_on_startup != nullptr) { + return ResourceImporter::load_on_startup(this, p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode); + } +#endif + + return load_internal(p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode, false); +} + +Ref<Resource> ResourceFormatImporter::load_internal(const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode, bool p_silence_errors) { PathAndType pat; Error err = _get_path_and_type(p_path, pat); @@ -148,6 +164,13 @@ Ref<Resource> ResourceFormatImporter::load(const String &p_path, const String &p return Ref<Resource>(); } + if (p_silence_errors) { + // Note: Some importers do not create files in the .godot folder, so we need to check if the path is empty. + if (!pat.path.is_empty() && !FileAccess::exists(pat.path)) { + return Ref<Resource>(); + } + } + Ref<Resource> res = ResourceLoader::_load(pat.path, p_path, pat.type, p_cache_mode, r_error, p_use_sub_threads, r_progress); #ifdef TOOLS_ENABLED diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index 7b1806c3d2..6ea5d0972a 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -35,6 +35,9 @@ #include "core/io/resource_saver.h" class ResourceImporter; +class ResourceFormatImporter; + +typedef Ref<Resource> (*ResourceFormatImporterLoadOnStartup)(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode); class ResourceFormatImporter : public ResourceFormatLoader { struct PathAndType { @@ -60,6 +63,7 @@ class ResourceFormatImporter : public ResourceFormatLoader { public: static ResourceFormatImporter *get_singleton() { return singleton; } virtual Ref<Resource> load(const String &p_path, const String &p_original_path = "", Error *r_error = nullptr, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override; + Ref<Resource> load_internal(const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode, bool p_silence_errors); virtual void get_recognized_extensions(List<String> *p_extensions) const override; virtual void get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const override; virtual bool recognize_path(const String &p_path, const String &p_for_type = String()) const override; @@ -94,6 +98,7 @@ public: String get_import_base_path(const String &p_for_file) const; Error get_resource_import_info(const String &p_path, StringName &r_type, ResourceUID::ID &r_uid, String &r_import_group_file) const; + ResourceFormatImporter(); }; @@ -104,6 +109,8 @@ protected: static void _bind_methods(); public: + static ResourceFormatImporterLoadOnStartup load_on_startup; + virtual String get_importer_name() const = 0; virtual String get_visible_name() const = 0; virtual void get_recognized_extensions(List<String> *p_extensions) const = 0; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 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/object/class_db.cpp b/core/object/class_db.cpp index b81130f43e..a65411629f 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -181,7 +181,7 @@ public: return 0; } - static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, bool p_notify_postinitialize) { + static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, GDExtensionBool p_notify_postinitialize) { ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata; // Find the closest native parent, that isn't a runtime class. @@ -192,7 +192,7 @@ public: ERR_FAIL_NULL_V(native_parent->creation_func, nullptr); // Construct a placeholder. - Object *obj = native_parent->creation_func(p_notify_postinitialize); + Object *obj = native_parent->creation_func(static_cast<bool>(p_notify_postinitialize)); // ClassDB::set_object_extension_instance() won't be called for placeholders. // We need need to make sure that all the things it would have done (even if diff --git a/core/object/object.cpp b/core/object/object.cpp index 8b3ab40271..d6b7d7a7fe 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -605,7 +605,7 @@ Variant Object::_call_bind(const Variant **p_args, int p_argcount, Callable::Cal return Variant(); } - if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -624,7 +624,7 @@ Variant Object::_call_deferred_bind(const Variant **p_args, int p_argcount, Call return Variant(); } - if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -720,7 +720,7 @@ Variant Object::getvar(const Variant &p_key, bool *r_valid) const { *r_valid = false; } - if (p_key.get_type() == Variant::STRING_NAME || p_key.get_type() == Variant::STRING) { + if (p_key.is_string()) { return get(p_key, r_valid); } return Variant(); @@ -730,7 +730,7 @@ void Object::setvar(const Variant &p_key, const Variant &p_value, bool *r_valid) if (r_valid) { *r_valid = false; } - if (p_key.get_type() == Variant::STRING_NAME || p_key.get_type() == Variant::STRING) { + if (p_key.is_string()) { return set(p_key, p_value, r_valid); } } @@ -1104,7 +1104,7 @@ Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::Cal ERR_FAIL_V(Error::ERR_INVALID_PARAMETER); } - if (unlikely(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING)) { + if (unlikely(!p_args[0]->is_string())) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -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 be8820eba9..30d2a4266f 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -75,6 +75,7 @@ class OS { int _display_driver_id = -1; String _current_rendering_driver_name; String _current_rendering_method; + bool _is_gles_over_gl = false; RemoteFilesystemClient default_rfs; @@ -111,9 +112,6 @@ protected: virtual void initialize() = 0; virtual void initialize_joypads() = 0; - void set_current_rendering_driver_name(const String &p_driver_name) { _current_rendering_driver_name = p_driver_name; } - void set_current_rendering_method(const String &p_name) { _current_rendering_method = p_name; } - void set_display_driver_id(int p_display_driver_id) { _display_driver_id = p_display_driver_id; } virtual void set_main_loop(MainLoop *p_main_loop) = 0; @@ -131,8 +129,13 @@ public: static OS *get_singleton(); + void set_current_rendering_driver_name(const String &p_driver_name) { _current_rendering_driver_name = p_driver_name; } + void set_current_rendering_method(const String &p_name) { _current_rendering_method = p_name; } + void set_gles_over_gl(bool p_enabled) { _is_gles_over_gl = p_enabled; } + String get_current_rendering_driver_name() const { return _current_rendering_driver_name; } String get_current_rendering_method() const { return _current_rendering_method; } + bool get_gles_over_gl() const { return _is_gles_over_gl; } int get_display_driver_id() const { return _display_driver_id; } @@ -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/string/translation_server.cpp b/core/string/translation_server.cpp index 6e784881d0..4ac79ad10a 100644 --- a/core/string/translation_server.cpp +++ b/core/string/translation_server.cpp @@ -284,6 +284,11 @@ String TranslationServer::_standardize_locale(const String &p_locale, bool p_add } int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const { + if (p_locale_a == p_locale_b) { + // Exact match. + return 10; + } + String locale_a = _standardize_locale(p_locale_a, true); String locale_b = _standardize_locale(p_locale_b, true); diff --git a/core/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 7416101d51..f2522a4545 100644 --- a/core/variant/dictionary.cpp +++ b/core/variant/dictionary.cpp @@ -32,6 +32,7 @@ #include "core/templates/hash_map.h" #include "core/templates/safe_refcount.h" +#include "core/variant/container_type_validate.h" #include "core/variant/variant.h" // required in this order by VariantInternal, do not remove this comment. #include "core/object/class_db.h" @@ -43,6 +44,9 @@ struct DictionaryPrivate { SafeRefCount refcount; Variant *read_only = nullptr; // If enabled, a pointer is used to a temporary value that is used to return read-only values. HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map; + ContainerTypeValidate typed_key; + ContainerTypeValidate typed_value; + Variant *typed_fallback = nullptr; // Allows a typed dictionary to return dummy values when attempting an invalid access. }; void Dictionary::get_key_list(List<Variant> *p_keys) const { @@ -81,15 +85,7 @@ Variant Dictionary::get_value_at_index(int p_index) const { Variant &Dictionary::operator[](const Variant &p_key) { if (unlikely(_p->read_only)) { - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - const String &key = sn->operator String(); - if (likely(_p->variant_map.has(key))) { - *_p->read_only = _p->variant_map[key]; - } else { - *_p->read_only = Variant(); - } - } else if (likely(_p->variant_map.has(p_key))) { + if (likely(_p->variant_map.has(p_key))) { *_p->read_only = _p->variant_map[p_key]; } else { *_p->read_only = Variant(); @@ -97,12 +93,7 @@ Variant &Dictionary::operator[](const Variant &p_key) { return *_p->read_only; } else { - if (p_key.get_type() == Variant::STRING_NAME) { - const StringName *sn = VariantInternal::get_string_name(&p_key); - return _p->variant_map[sn->operator String()]; - } else { - return _p->variant_map[p_key]; - } + return _p->variant_map[p_key]; } } @@ -133,7 +124,9 @@ Variant *Dictionary::getptr(const Variant &p_key) { } Variant Dictionary::get_valid(const Variant &p_key) const { - HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(p_key)); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get_valid"), Variant()); + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(key)); if (!E) { return Variant(); @@ -142,7 +135,9 @@ Variant Dictionary::get_valid(const Variant &p_key) const { } Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const { - const Variant *result = getptr(p_key); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default); + const Variant *result = getptr(key); if (!result) { return p_default; } @@ -151,10 +146,14 @@ Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const { } Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) { - const Variant *result = getptr(p_key); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "get"), p_default); + const Variant *result = getptr(key); if (!result) { - operator[](p_key) = p_default; - return p_default; + Variant value = p_default; + ERR_FAIL_COND_V(!_p->typed_value.validate(value, "add"), value); + operator[](key) = value; + return value; } return *result; } @@ -168,12 +167,16 @@ bool Dictionary::is_empty() const { } bool Dictionary::has(const Variant &p_key) const { + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has'"), false); return _p->variant_map.has(p_key); } bool Dictionary::has_all(const Array &p_keys) const { for (int i = 0; i < p_keys.size(); i++) { - if (!has(p_keys[i])) { + Variant key = p_keys[i]; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "use 'has_all'"), false); + if (!has(key)) { return false; } } @@ -181,8 +184,10 @@ bool Dictionary::has_all(const Array &p_keys) const { } Variant Dictionary::find_key(const Variant &p_value) const { + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed_value.validate(value, "find_key"), Variant()); for (const KeyValue<Variant, Variant> &E : _p->variant_map) { - if (E.value == p_value) { + if (E.value == value) { return E.key; } } @@ -190,8 +195,10 @@ Variant Dictionary::find_key(const Variant &p_value) const { } bool Dictionary::erase(const Variant &p_key) { + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "erase"), false); ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state."); - return _p->variant_map.erase(p_key); + return _p->variant_map.erase(key); } bool Dictionary::operator==(const Dictionary &p_dictionary) const { @@ -251,8 +258,12 @@ void Dictionary::clear() { void Dictionary::merge(const Dictionary &p_dictionary, bool p_overwrite) { ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state."); for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { - if (p_overwrite || !has(E.key)) { - operator[](E.key) = E.value; + Variant key = E.key; + Variant value = E.value; + ERR_FAIL_COND(!_p->typed_key.validate(key, "merge")); + ERR_FAIL_COND(!_p->typed_key.validate(value, "merge")); + if (p_overwrite || !has(key)) { + operator[](key) = value; } } } @@ -269,6 +280,9 @@ void Dictionary::_unref() const { if (_p->read_only) { memdelete(_p->read_only); } + if (_p->typed_fallback) { + memdelete(_p->typed_fallback); + } memdelete(_p); } _p = nullptr; @@ -297,6 +311,9 @@ uint32_t Dictionary::recursive_hash(int recursion_count) const { Array Dictionary::keys() const { Array varr; + if (is_typed_key()) { + varr.set_typed(get_typed_key_builtin(), get_typed_key_class_name(), get_typed_key_script()); + } if (_p->variant_map.is_empty()) { return varr; } @@ -314,6 +331,9 @@ Array Dictionary::keys() const { Array Dictionary::values() const { Array varr; + if (is_typed_value()) { + varr.set_typed(get_typed_value_builtin(), get_typed_value_class_name(), get_typed_value_script()); + } if (_p->variant_map.is_empty()) { return varr; } @@ -329,6 +349,146 @@ Array Dictionary::values() const { return varr; } +void Dictionary::assign(const Dictionary &p_dictionary) { + const ContainerTypeValidate &typed_key = _p->typed_key; + const ContainerTypeValidate &typed_key_source = p_dictionary._p->typed_key; + + const ContainerTypeValidate &typed_value = _p->typed_value; + const ContainerTypeValidate &typed_value_source = p_dictionary._p->typed_value; + + if ((typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) && + (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source)))) { + // From same to same or, + // from anything to variants or, + // from subclasses to base classes. + _p->variant_map = p_dictionary._p->variant_map; + return; + } + + int size = p_dictionary._p->variant_map.size(); + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator> variant_map = HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>(size); + + Vector<Variant> key_array; + key_array.resize(size); + Variant *key_data = key_array.ptrw(); + + Vector<Variant> value_array; + value_array.resize(size); + Variant *value_data = value_array.ptrw(); + + if (typed_key == typed_key_source || typed_key.type == Variant::NIL || (typed_key_source.type == Variant::OBJECT && typed_key.can_reference(typed_key_source))) { + // From same to same or, + // from anything to variants or, + // from subclasses to base classes. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + key_data[i++] = *key; + } + } else if ((typed_key_source.type == Variant::NIL && typed_key.type == Variant::OBJECT) || (typed_key_source.type == Variant::OBJECT && typed_key_source.can_reference(typed_key))) { + // From variants to objects or, + // from base classes to subclasses. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + if (key->get_type() != Variant::NIL && (key->get_type() != Variant::OBJECT || !typed_key.validate_object(*key, "assign"))) { + ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + key_data[i++] = *key; + } + } else if (typed_key.type == Variant::OBJECT || typed_key_source.type == Variant::OBJECT) { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } else if (typed_key_source.type == Variant::NIL && typed_key.type != Variant::OBJECT) { + // From variants to primitives. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + if (key->get_type() == typed_key.type) { + key_data[i++] = *key; + continue; + } + if (!Variant::can_convert_strict(key->get_type(), typed_key.type)) { + ERR_FAIL_MSG(vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + Callable::CallError ce; + Variant::construct(typed_key.type, key_data[i++], &key, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + } else if (Variant::can_convert_strict(typed_key_source.type, typed_key.type)) { + // From primitives to different convertible primitives. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *key = &E.key; + Callable::CallError ce; + Variant::construct(typed_key.type, key_data[i++], &key, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert key from "%s" to "%s".)", Variant::get_type_name(key->get_type()), Variant::get_type_name(typed_key.type))); + } + } else { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } + + if (typed_value == typed_value_source || typed_value.type == Variant::NIL || (typed_value_source.type == Variant::OBJECT && typed_value.can_reference(typed_value_source))) { + // From same to same or, + // from anything to variants or, + // from subclasses to base classes. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + value_data[i++] = *value; + } + } else if (((typed_value_source.type == Variant::NIL && typed_value.type == Variant::OBJECT) || (typed_value_source.type == Variant::OBJECT && typed_value_source.can_reference(typed_value)))) { + // From variants to objects or, + // from base classes to subclasses. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + if (value->get_type() != Variant::NIL && (value->get_type() != Variant::OBJECT || !typed_value.validate_object(*value, "assign"))) { + ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + value_data[i++] = *value; + } + } else if (typed_value.type == Variant::OBJECT || typed_value_source.type == Variant::OBJECT) { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s]".)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } else if (typed_value_source.type == Variant::NIL && typed_value.type != Variant::OBJECT) { + // From variants to primitives. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + if (value->get_type() == typed_value.type) { + value_data[i++] = *value; + continue; + } + if (!Variant::can_convert_strict(value->get_type(), typed_value.type)) { + ERR_FAIL_MSG(vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + Callable::CallError ce; + Variant::construct(typed_value.type, value_data[i++], &value, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + } else if (Variant::can_convert_strict(typed_value_source.type, typed_value.type)) { + // From primitives to different convertible primitives. + int i = 0; + for (const KeyValue<Variant, Variant> &E : p_dictionary._p->variant_map) { + const Variant *value = &E.value; + Callable::CallError ce; + Variant::construct(typed_value.type, value_data[i++], &value, 1, ce); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert value at key "%s" from "%s" to "%s".)", key_data[i - 1], Variant::get_type_name(value->get_type()), Variant::get_type_name(typed_value.type))); + } + } else { + ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Dictionary[%s, %s]" to "Dictionary[%s, %s].)", Variant::get_type_name(typed_key_source.type), Variant::get_type_name(typed_value_source.type), + Variant::get_type_name(typed_key.type), Variant::get_type_name(typed_value.type))); + } + + for (int i = 0; i < size; i++) { + variant_map.insert(key_data[i], value_data[i]); + } + + _p->variant_map = variant_map; +} + const Variant *Dictionary::next(const Variant *p_key) const { if (p_key == nullptr) { // caller wants to get the first element @@ -337,7 +497,9 @@ const Variant *Dictionary::next(const Variant *p_key) const { } return nullptr; } - HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(*p_key); + Variant key = *p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "next"), nullptr); + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E = _p->variant_map.find(key); if (!E) { return nullptr; @@ -367,6 +529,8 @@ bool Dictionary::is_read_only() const { Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) const { Dictionary n; + n._p->typed_key = _p->typed_key; + n._p->typed_value = _p->typed_value; if (recursion_count > MAX_RECURSION) { ERR_PRINT("Max recursion reached"); @@ -387,6 +551,76 @@ Dictionary Dictionary::recursive_duplicate(bool p_deep, int recursion_count) con return n; } +void Dictionary::set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) { + ERR_FAIL_COND_MSG(_p->read_only, "Dictionary is in read-only state."); + ERR_FAIL_COND_MSG(_p->variant_map.size() > 0, "Type can only be set when dictionary is empty."); + ERR_FAIL_COND_MSG(_p->refcount.get() > 1, "Type can only be set when dictionary has no more than one user."); + ERR_FAIL_COND_MSG(_p->typed_key.type != Variant::NIL || _p->typed_value.type != Variant::NIL, "Type can only be set once."); + ERR_FAIL_COND_MSG((p_key_class_name != StringName() && p_key_type != Variant::OBJECT) || (p_value_class_name != StringName() && p_value_type != Variant::OBJECT), "Class names can only be set for type OBJECT."); + Ref<Script> key_script = p_key_script; + ERR_FAIL_COND_MSG(key_script.is_valid() && p_key_class_name == StringName(), "Script class can only be set together with base class name."); + Ref<Script> value_script = p_value_script; + ERR_FAIL_COND_MSG(value_script.is_valid() && p_value_class_name == StringName(), "Script class can only be set together with base class name."); + + _p->typed_key.type = Variant::Type(p_key_type); + _p->typed_key.class_name = p_key_class_name; + _p->typed_key.script = key_script; + _p->typed_key.where = "TypedDictionary.Key"; + + _p->typed_value.type = Variant::Type(p_value_type); + _p->typed_value.class_name = p_value_class_name; + _p->typed_value.script = value_script; + _p->typed_value.where = "TypedDictionary.Value"; +} + +bool Dictionary::is_typed() const { + return is_typed_key() || is_typed_value(); +} + +bool Dictionary::is_typed_key() const { + return _p->typed_key.type != Variant::NIL; +} + +bool Dictionary::is_typed_value() const { + return _p->typed_value.type != Variant::NIL; +} + +bool Dictionary::is_same_typed(const Dictionary &p_other) const { + return is_same_typed_key(p_other) && is_same_typed_value(p_other); +} + +bool Dictionary::is_same_typed_key(const Dictionary &p_other) const { + return _p->typed_key == p_other._p->typed_key; +} + +bool Dictionary::is_same_typed_value(const Dictionary &p_other) const { + return _p->typed_value == p_other._p->typed_value; +} + +uint32_t Dictionary::get_typed_key_builtin() const { + return _p->typed_key.type; +} + +uint32_t Dictionary::get_typed_value_builtin() const { + return _p->typed_value.type; +} + +StringName Dictionary::get_typed_key_class_name() const { + return _p->typed_key.class_name; +} + +StringName Dictionary::get_typed_value_class_name() const { + return _p->typed_value.class_name; +} + +Variant Dictionary::get_typed_key_script() const { + return _p->typed_key.script; +} + +Variant Dictionary::get_typed_value_script() const { + return _p->typed_value.script; +} + void Dictionary::operator=(const Dictionary &p_dictionary) { if (this == &p_dictionary) { return; @@ -398,6 +632,13 @@ const void *Dictionary::id() const { return _p; } +Dictionary::Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script) { + _p = memnew(DictionaryPrivate); + _p->refcount.init(); + set_typed(p_key_type, p_key_class_name, p_key_script, p_value_type, p_value_class_name, p_value_script); + assign(p_base); +} + Dictionary::Dictionary(const Dictionary &p_from) { _p = nullptr; _ref(p_from); diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h index 67178ee7b7..57fbefc8f2 100644 --- a/core/variant/dictionary.h +++ b/core/variant/dictionary.h @@ -80,6 +80,7 @@ public: uint32_t recursive_hash(int recursion_count) const; void operator=(const Dictionary &p_dictionary); + void assign(const Dictionary &p_dictionary); const Variant *next(const Variant *p_key = nullptr) const; Array keys() const; @@ -88,11 +89,26 @@ public: Dictionary duplicate(bool p_deep = false) const; Dictionary recursive_duplicate(bool p_deep, int recursion_count) const; + void set_typed(uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script); + bool is_typed() const; + bool is_typed_key() const; + bool is_typed_value() const; + bool is_same_typed(const Dictionary &p_other) const; + bool is_same_typed_key(const Dictionary &p_other) const; + bool is_same_typed_value(const Dictionary &p_other) const; + uint32_t get_typed_key_builtin() const; + uint32_t get_typed_value_builtin() const; + StringName get_typed_key_class_name() const; + StringName get_typed_value_class_name() const; + Variant get_typed_key_script() const; + Variant get_typed_value_script() const; + void make_read_only(); bool is_read_only() const; const void *id() const; + Dictionary(const Dictionary &p_base, uint32_t p_key_type, const StringName &p_key_class_name, const Variant &p_key_script, uint32_t p_value_type, const StringName &p_value_class_name, const Variant &p_value_script); Dictionary(const Dictionary &p_from); Dictionary(); ~Dictionary(); diff --git a/core/variant/typed_dictionary.h b/core/variant/typed_dictionary.h new file mode 100644 index 0000000000..67fc33b4fc --- /dev/null +++ b/core/variant/typed_dictionary.h @@ -0,0 +1,342 @@ +/**************************************************************************/ +/* typed_dictionary.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TYPED_DICTIONARY_H +#define TYPED_DICTIONARY_H + +#include "core/object/object.h" +#include "core/variant/binder_common.h" +#include "core/variant/dictionary.h" +#include "core/variant/method_ptrcall.h" +#include "core/variant/type_info.h" +#include "core/variant/variant.h" + +template <typename K, typename V> +class TypedDictionary : public Dictionary { +public: + _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { + ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign a dictionary with a different element type."); + Dictionary::operator=(p_dictionary); + } + _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : + TypedDictionary(Dictionary(p_variant)) { + } + _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { + set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant()); + if (is_same_typed(p_dictionary)) { + Dictionary::operator=(p_dictionary); + } else { + assign(p_dictionary); + } + } + _FORCE_INLINE_ TypedDictionary() { + set_typed(Variant::OBJECT, K::get_class_static(), Variant(), Variant::OBJECT, V::get_class_static(), Variant()); + } +}; + +template <typename K, typename V> +struct VariantInternalAccessor<TypedDictionary<K, V>> { + static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); } + static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; } +}; + +template <typename K, typename V> +struct VariantInternalAccessor<const TypedDictionary<K, V> &> { + static _FORCE_INLINE_ TypedDictionary<K, V> get(const Variant *v) { return *VariantInternal::get_dictionary(v); } + static _FORCE_INLINE_ void set(Variant *v, const TypedDictionary<K, V> &p_dictionary) { *VariantInternal::get_dictionary(v) = p_dictionary; } +}; + +template <typename K, typename V> +struct PtrToArg<TypedDictionary<K, V>> { + _FORCE_INLINE_ static TypedDictionary<K, V> convert(const void *p_ptr) { + return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr)); + } + typedef Dictionary EncodeT; + _FORCE_INLINE_ static void encode(TypedDictionary<K, V> p_val, void *p_ptr) { + *(Dictionary *)p_ptr = p_val; + } +}; + +template <typename K, typename V> +struct PtrToArg<const TypedDictionary<K, V> &> { + typedef Dictionary EncodeT; + _FORCE_INLINE_ static TypedDictionary<K, V> + convert(const void *p_ptr) { + return TypedDictionary<K, V>(*reinterpret_cast<const Dictionary *>(p_ptr)); + } +}; + +template <typename K, typename V> +struct GetTypeInfo<TypedDictionary<K, V>> { + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; + static inline PropertyInfo get_class_info() { + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static())); + } +}; + +template <typename K, typename V> +struct GetTypeInfo<const TypedDictionary<K, V> &> { + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; + static inline PropertyInfo get_class_info() { + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, vformat("%s;%s", K::get_class_static(), V::get_class_static())); + } +}; + +// Specialization for the rest of the Variant types. + +#define MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \ + template <typename T> \ + class TypedDictionary<T, m_type> : public Dictionary { \ + public: \ + _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \ + ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \ + Dictionary::operator=(p_dictionary); \ + } \ + _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \ + TypedDictionary(Dictionary(p_variant)) { \ + } \ + _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \ + set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \ + if (is_same_typed(p_dictionary)) { \ + Dictionary::operator=(p_dictionary); \ + } else { \ + assign(p_dictionary); \ + } \ + } \ + _FORCE_INLINE_ TypedDictionary() { \ + set_typed(Variant::OBJECT, T::get_class_static(), Variant(), m_variant_type, StringName(), Variant()); \ + } \ + }; \ + template <typename T> \ + struct GetTypeInfo<TypedDictionary<T, m_type>> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \ + } \ + }; \ + template <typename T> \ + struct GetTypeInfo<const TypedDictionary<T, m_type> &> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", T::get_class_static(), m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type))); \ + } \ + }; \ + template <typename T> \ + class TypedDictionary<m_type, T> : public Dictionary { \ + public: \ + _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \ + ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \ + Dictionary::operator=(p_dictionary); \ + } \ + _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \ + TypedDictionary(Dictionary(p_variant)) { \ + } \ + _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \ + set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \ + if (is_same_typed(p_dictionary)) { \ + Dictionary::operator=(p_dictionary); \ + } else { \ + assign(p_dictionary); \ + } \ + } \ + _FORCE_INLINE_ TypedDictionary() { \ + set_typed(m_variant_type, StringName(), Variant(), Variant::OBJECT, T::get_class_static(), Variant()); \ + } \ + }; \ + template <typename T> \ + struct GetTypeInfo<TypedDictionary<m_type, T>> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \ + } \ + }; \ + template <typename T> \ + struct GetTypeInfo<const TypedDictionary<m_type, T> &> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", m_variant_type == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type), T::get_class_static())); \ + } \ + }; + +#define MAKE_TYPED_DICTIONARY_EXPANDED(m_type_key, m_variant_type_key, m_type_value, m_variant_type_value) \ + template <> \ + class TypedDictionary<m_type_key, m_type_value> : public Dictionary { \ + public: \ + _FORCE_INLINE_ void operator=(const Dictionary &p_dictionary) { \ + ERR_FAIL_COND_MSG(!is_same_typed(p_dictionary), "Cannot assign an dictionary with a different element type."); \ + Dictionary::operator=(p_dictionary); \ + } \ + _FORCE_INLINE_ TypedDictionary(const Variant &p_variant) : \ + TypedDictionary(Dictionary(p_variant)) { \ + } \ + _FORCE_INLINE_ TypedDictionary(const Dictionary &p_dictionary) { \ + set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \ + if (is_same_typed(p_dictionary)) { \ + Dictionary::operator=(p_dictionary); \ + } else { \ + assign(p_dictionary); \ + } \ + } \ + _FORCE_INLINE_ TypedDictionary() { \ + set_typed(m_variant_type_key, StringName(), Variant(), m_variant_type_value, StringName(), Variant()); \ + } \ + }; \ + template <> \ + struct GetTypeInfo<TypedDictionary<m_type_key, m_type_value>> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \ + m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \ + } \ + }; \ + template <> \ + struct GetTypeInfo<const TypedDictionary<m_type_key, m_type_value> &> { \ + static const Variant::Type VARIANT_TYPE = Variant::DICTIONARY; \ + static const GodotTypeInfo::Metadata METADATA = GodotTypeInfo::METADATA_NONE; \ + static inline PropertyInfo get_class_info() { \ + return PropertyInfo(Variant::DICTIONARY, String(), PROPERTY_HINT_DICTIONARY_TYPE, \ + vformat("%s;%s", m_variant_type_key == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_key), \ + m_variant_type_value == Variant::NIL ? "Variant" : Variant::get_type_name(m_variant_type_value))); \ + } \ + }; + +#define MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type) \ + MAKE_TYPED_DICTIONARY_WITH_OBJECT(m_type, m_variant_type) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, bool, Variant::BOOL) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint8_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int8_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint16_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int16_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint32_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int32_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, uint64_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, int64_t, Variant::INT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, float, Variant::FLOAT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, double, Variant::FLOAT) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, String, Variant::STRING) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2, Variant::VECTOR2) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector2i, Variant::VECTOR2I) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2, Variant::RECT2) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Rect2i, Variant::RECT2I) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3, Variant::VECTOR3) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Vector3i, Variant::VECTOR3I) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform2D, Variant::TRANSFORM2D) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Plane, Variant::PLANE) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Quaternion, Variant::QUATERNION) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, AABB, Variant::AABB) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Basis, Variant::BASIS) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Transform3D, Variant::TRANSFORM3D) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Color, Variant::COLOR) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, StringName, Variant::STRING_NAME) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, NodePath, Variant::NODE_PATH) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, RID, Variant::RID) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Callable, Variant::CALLABLE) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Signal, Variant::SIGNAL) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Dictionary, Variant::DICTIONARY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Array, Variant::ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedByteArray, Variant::PACKED_BYTE_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt32Array, Variant::PACKED_INT32_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedInt64Array, Variant::PACKED_INT64_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedStringArray, Variant::PACKED_STRING_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedColorArray, Variant::PACKED_COLOR_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, IPAddress, Variant::STRING) + +#define MAKE_TYPED_DICTIONARY(m_type, m_variant_type) \ + MAKE_TYPED_DICTIONARY_EXPANDED(m_type, m_variant_type, Variant, Variant::NIL) \ + MAKE_TYPED_DICTIONARY_NIL(m_type, m_variant_type) + +MAKE_TYPED_DICTIONARY_NIL(Variant, Variant::NIL) +MAKE_TYPED_DICTIONARY(bool, Variant::BOOL) +MAKE_TYPED_DICTIONARY(uint8_t, Variant::INT) +MAKE_TYPED_DICTIONARY(int8_t, Variant::INT) +MAKE_TYPED_DICTIONARY(uint16_t, Variant::INT) +MAKE_TYPED_DICTIONARY(int16_t, Variant::INT) +MAKE_TYPED_DICTIONARY(uint32_t, Variant::INT) +MAKE_TYPED_DICTIONARY(int32_t, Variant::INT) +MAKE_TYPED_DICTIONARY(uint64_t, Variant::INT) +MAKE_TYPED_DICTIONARY(int64_t, Variant::INT) +MAKE_TYPED_DICTIONARY(float, Variant::FLOAT) +MAKE_TYPED_DICTIONARY(double, Variant::FLOAT) +MAKE_TYPED_DICTIONARY(String, Variant::STRING) +MAKE_TYPED_DICTIONARY(Vector2, Variant::VECTOR2) +MAKE_TYPED_DICTIONARY(Vector2i, Variant::VECTOR2I) +MAKE_TYPED_DICTIONARY(Rect2, Variant::RECT2) +MAKE_TYPED_DICTIONARY(Rect2i, Variant::RECT2I) +MAKE_TYPED_DICTIONARY(Vector3, Variant::VECTOR3) +MAKE_TYPED_DICTIONARY(Vector3i, Variant::VECTOR3I) +MAKE_TYPED_DICTIONARY(Transform2D, Variant::TRANSFORM2D) +MAKE_TYPED_DICTIONARY(Plane, Variant::PLANE) +MAKE_TYPED_DICTIONARY(Quaternion, Variant::QUATERNION) +MAKE_TYPED_DICTIONARY(AABB, Variant::AABB) +MAKE_TYPED_DICTIONARY(Basis, Variant::BASIS) +MAKE_TYPED_DICTIONARY(Transform3D, Variant::TRANSFORM3D) +MAKE_TYPED_DICTIONARY(Color, Variant::COLOR) +MAKE_TYPED_DICTIONARY(StringName, Variant::STRING_NAME) +MAKE_TYPED_DICTIONARY(NodePath, Variant::NODE_PATH) +MAKE_TYPED_DICTIONARY(RID, Variant::RID) +MAKE_TYPED_DICTIONARY(Callable, Variant::CALLABLE) +MAKE_TYPED_DICTIONARY(Signal, Variant::SIGNAL) +MAKE_TYPED_DICTIONARY(Dictionary, Variant::DICTIONARY) +MAKE_TYPED_DICTIONARY(Array, Variant::ARRAY) +MAKE_TYPED_DICTIONARY(PackedByteArray, Variant::PACKED_BYTE_ARRAY) +MAKE_TYPED_DICTIONARY(PackedInt32Array, Variant::PACKED_INT32_ARRAY) +MAKE_TYPED_DICTIONARY(PackedInt64Array, Variant::PACKED_INT64_ARRAY) +MAKE_TYPED_DICTIONARY(PackedFloat32Array, Variant::PACKED_FLOAT32_ARRAY) +MAKE_TYPED_DICTIONARY(PackedFloat64Array, Variant::PACKED_FLOAT64_ARRAY) +MAKE_TYPED_DICTIONARY(PackedStringArray, Variant::PACKED_STRING_ARRAY) +MAKE_TYPED_DICTIONARY(PackedVector2Array, Variant::PACKED_VECTOR2_ARRAY) +MAKE_TYPED_DICTIONARY(PackedVector3Array, Variant::PACKED_VECTOR3_ARRAY) +MAKE_TYPED_DICTIONARY(PackedColorArray, Variant::PACKED_COLOR_ARRAY) +MAKE_TYPED_DICTIONARY(PackedVector4Array, Variant::PACKED_VECTOR4_ARRAY) +MAKE_TYPED_DICTIONARY(IPAddress, Variant::STRING) + +#undef MAKE_TYPED_DICTIONARY +#undef MAKE_TYPED_DICTIONARY_NIL +#undef MAKE_TYPED_DICTIONARY_EXPANDED +#undef MAKE_TYPED_DICTIONARY_WITH_OBJECT + +#endif // TYPED_DICTIONARY_H diff --git a/core/variant/variant_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 5afdb884f6..68210a9451 100644 --- a/core/variant/variant_construct.h +++ b/core/variant/variant_construct.h @@ -232,7 +232,7 @@ template <typename T> class VariantConstructorFromString { public: static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) { - if (p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING; @@ -240,7 +240,7 @@ public: } VariantTypeChanger<T>::change(&r_ret); - const String &src_str = *VariantGetInternalPtr<String>::get_ptr(p_args[0]); + const String src_str = *p_args[0]; if (r_ret.get_type() == Variant::Type::INT) { r_ret = src_str.to_int(); @@ -400,6 +400,112 @@ public: } }; +class VariantConstructorTypedDictionary { +public: + static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) { + if (p_args[0]->get_type() != Variant::DICTIONARY) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::DICTIONARY; + return; + } + + if (p_args[1]->get_type() != Variant::INT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::INT; + return; + } + + if (p_args[2]->get_type() != Variant::STRING_NAME) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 2; + r_error.expected = Variant::STRING_NAME; + return; + } + + if (p_args[4]->get_type() != Variant::INT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 4; + r_error.expected = Variant::INT; + return; + } + + if (p_args[5]->get_type() != Variant::STRING_NAME) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 5; + r_error.expected = Variant::STRING_NAME; + return; + } + + const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]); + const uint32_t key_type = p_args[1]->operator uint32_t(); + const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]); + const uint32_t value_type = p_args[4]->operator uint32_t(); + const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]); + r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]); + } + + static inline void validated_construct(Variant *r_ret, const Variant **p_args) { + const Dictionary &base_dict = *VariantGetInternalPtr<Dictionary>::get_ptr(p_args[0]); + const uint32_t key_type = p_args[1]->operator uint32_t(); + const StringName &key_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]); + const uint32_t value_type = p_args[4]->operator uint32_t(); + const StringName &value_class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[5]); + *r_ret = Dictionary(base_dict, key_type, key_class_name, *p_args[3], value_type, value_class_name, *p_args[6]); + } + + static void ptr_construct(void *base, const void **p_args) { + const Dictionary &base_dict = PtrToArg<Dictionary>::convert(p_args[0]); + const uint32_t key_type = PtrToArg<uint32_t>::convert(p_args[1]); + const StringName &key_class_name = PtrToArg<StringName>::convert(p_args[2]); + const Variant &key_script = PtrToArg<Variant>::convert(p_args[3]); + const uint32_t value_type = PtrToArg<uint32_t>::convert(p_args[4]); + const StringName &value_class_name = PtrToArg<StringName>::convert(p_args[5]); + const Variant &value_script = PtrToArg<Variant>::convert(p_args[6]); + Dictionary dst_arr = Dictionary(base_dict, key_type, key_class_name, key_script, value_type, value_class_name, value_script); + + PtrConstruct<Dictionary>::construct(dst_arr, base); + } + + static int get_argument_count() { + return 7; + } + + static Variant::Type get_argument_type(int p_arg) { + switch (p_arg) { + case 0: { + return Variant::DICTIONARY; + } break; + case 1: { + return Variant::INT; + } break; + case 2: { + return Variant::STRING_NAME; + } break; + case 3: { + return Variant::NIL; + } break; + case 4: { + return Variant::INT; + } break; + case 5: { + return Variant::STRING_NAME; + } break; + case 6: { + return Variant::NIL; + } break; + default: { + return Variant::NIL; + } break; + } + } + + static Variant::Type get_base_type() { + return Variant::DICTIONARY; + } +}; + class VariantConstructorTypedArray { public: static void construct(Variant &r_ret, const Variant **p_args, Callable::CallError &r_error) { @@ -417,7 +523,7 @@ public: return; } - if (p_args[2]->get_type() != Variant::STRING_NAME) { + if (!p_args[2]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 2; r_error.expected = Variant::STRING_NAME; @@ -426,8 +532,7 @@ public: const Array &base_arr = *VariantGetInternalPtr<Array>::get_ptr(p_args[0]); const uint32_t type = p_args[1]->operator uint32_t(); - const StringName &class_name = *VariantGetInternalPtr<StringName>::get_ptr(p_args[2]); - r_ret = Array(base_arr, type, class_name, *p_args[3]); + r_ret = Array(base_arr, type, *p_args[2], *p_args[3]); } static inline void validated_construct(Variant *r_ret, const Variant **p_args) { diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index 9a0dd712ed..f5f96456d3 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -1140,6 +1140,146 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, return ERR_PARSE_ERROR; } } + } else if (id == "Dictionary") { + Error err = OK; + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_BRACKET_OPEN) { + r_err_str = "Expected '['"; + return ERR_PARSE_ERROR; + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_IDENTIFIER) { + r_err_str = "Expected type identifier for key"; + return ERR_PARSE_ERROR; + } + + static HashMap<StringName, Variant::Type> builtin_types; + if (builtin_types.is_empty()) { + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; + } + } + + Dictionary dict; + Variant::Type key_type = Variant::NIL; + StringName key_class_name; + Variant key_script; + bool got_comma_token = false; + if (builtin_types.has(token.value)) { + key_type = builtin_types.get(token.value); + } else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") { + Variant resource; + err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser); + if (err) { + if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_COMMA) { + err = OK; + r_err_str = String(); + key_type = Variant::OBJECT; + key_class_name = token.value; + got_comma_token = true; + } else { + return err; + } + } else { + Ref<Script> script = resource; + if (script.is_valid() && script->is_valid()) { + key_type = Variant::OBJECT; + key_class_name = script->get_instance_base_type(); + key_script = script; + } + } + } else if (ClassDB::class_exists(token.value)) { + key_type = Variant::OBJECT; + key_class_name = token.value; + } + + if (!got_comma_token) { + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_COMMA) { + r_err_str = "Expected ',' after key type"; + return ERR_PARSE_ERROR; + } + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_IDENTIFIER) { + r_err_str = "Expected type identifier for value"; + return ERR_PARSE_ERROR; + } + + Variant::Type value_type = Variant::NIL; + StringName value_class_name; + Variant value_script; + bool got_bracket_token = false; + if (builtin_types.has(token.value)) { + value_type = builtin_types.get(token.value); + } else if (token.value == "Resource" || token.value == "SubResource" || token.value == "ExtResource") { + Variant resource; + err = parse_value(token, resource, p_stream, line, r_err_str, p_res_parser); + if (err) { + if (token.value == "Resource" && err == ERR_PARSE_ERROR && r_err_str == "Expected '('" && token.type == TK_BRACKET_CLOSE) { + err = OK; + r_err_str = String(); + value_type = Variant::OBJECT; + value_class_name = token.value; + got_comma_token = true; + } else { + return err; + } + } else { + Ref<Script> script = resource; + if (script.is_valid() && script->is_valid()) { + value_type = Variant::OBJECT; + value_class_name = script->get_instance_base_type(); + value_script = script; + } + } + } else if (ClassDB::class_exists(token.value)) { + value_type = Variant::OBJECT; + value_class_name = token.value; + } + + if (key_type != Variant::NIL || value_type != Variant::NIL) { + dict.set_typed(key_type, key_class_name, key_script, value_type, value_class_name, value_script); + } + + if (!got_bracket_token) { + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_BRACKET_CLOSE) { + r_err_str = "Expected ']'"; + return ERR_PARSE_ERROR; + } + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_PARENTHESIS_OPEN) { + r_err_str = "Expected '('"; + return ERR_PARSE_ERROR; + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_CURLY_BRACKET_OPEN) { + r_err_str = "Expected '{'"; + return ERR_PARSE_ERROR; + } + + Dictionary values; + err = _parse_dictionary(values, p_stream, line, r_err_str, p_res_parser); + if (err) { + return err; + } + + get_token(p_stream, token, line, r_err_str); + if (token.type != TK_PARENTHESIS_CLOSE) { + r_err_str = "Expected ')'"; + return ERR_PARSE_ERROR; + } + + dict.assign(values); + + value = dict; } else if (id == "Array") { Error err = OK; @@ -2036,40 +2176,109 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str case Variant::DICTIONARY: { Dictionary dict = p_variant; + + if (dict.is_typed()) { + p_store_string_func(p_store_string_ud, "Dictionary["); + + Variant::Type key_builtin_type = (Variant::Type)dict.get_typed_key_builtin(); + StringName key_class_name = dict.get_typed_key_class_name(); + Ref<Script> key_script = dict.get_typed_key_script(); + + if (key_script.is_valid()) { + String resource_text; + if (p_encode_res_func) { + resource_text = p_encode_res_func(p_encode_res_ud, key_script); + } + if (resource_text.is_empty() && key_script->get_path().is_resource_file()) { + resource_text = "Resource(\"" + key_script->get_path() + "\")"; + } + + if (!resource_text.is_empty()) { + p_store_string_func(p_store_string_ud, resource_text); + } else { + ERR_PRINT("Failed to encode a path to a custom script for a dictionary key type."); + p_store_string_func(p_store_string_ud, key_class_name); + } + } else if (key_class_name != StringName()) { + p_store_string_func(p_store_string_ud, key_class_name); + } else if (key_builtin_type == Variant::NIL) { + p_store_string_func(p_store_string_ud, "Variant"); + } else { + p_store_string_func(p_store_string_ud, Variant::get_type_name(key_builtin_type)); + } + + p_store_string_func(p_store_string_ud, ", "); + + Variant::Type value_builtin_type = (Variant::Type)dict.get_typed_value_builtin(); + StringName value_class_name = dict.get_typed_value_class_name(); + Ref<Script> value_script = dict.get_typed_value_script(); + + if (value_script.is_valid()) { + String resource_text; + if (p_encode_res_func) { + resource_text = p_encode_res_func(p_encode_res_ud, value_script); + } + if (resource_text.is_empty() && value_script->get_path().is_resource_file()) { + resource_text = "Resource(\"" + value_script->get_path() + "\")"; + } + + if (!resource_text.is_empty()) { + p_store_string_func(p_store_string_ud, resource_text); + } else { + ERR_PRINT("Failed to encode a path to a custom script for a dictionary value type."); + p_store_string_func(p_store_string_ud, value_class_name); + } + } else if (value_class_name != StringName()) { + p_store_string_func(p_store_string_ud, value_class_name); + } else if (value_builtin_type == Variant::NIL) { + p_store_string_func(p_store_string_ud, "Variant"); + } else { + p_store_string_func(p_store_string_ud, Variant::get_type_name(value_builtin_type)); + } + + p_store_string_func(p_store_string_ud, "]("); + } + if (unlikely(p_recursion_count > MAX_RECURSION)) { ERR_PRINT("Max recursion reached"); p_store_string_func(p_store_string_ud, "{}"); } else { - p_recursion_count++; - List<Variant> keys; dict.get_key_list(&keys); keys.sort(); - if (keys.is_empty()) { // Avoid unnecessary line break. + if (keys.is_empty()) { + // Avoid unnecessary line break. p_store_string_func(p_store_string_ud, "{}"); - break; - } + } else { + p_recursion_count++; - p_store_string_func(p_store_string_ud, "{\n"); - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); - p_store_string_func(p_store_string_ud, ": "); - write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); - if (E->next()) { - p_store_string_func(p_store_string_ud, ",\n"); - } else { - p_store_string_func(p_store_string_ud, "\n"); + p_store_string_func(p_store_string_ud, "{\n"); + + for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + write(E->get(), p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); + p_store_string_func(p_store_string_ud, ": "); + write(dict[E->get()], p_store_string_func, p_store_string_ud, p_encode_res_func, p_encode_res_ud, p_recursion_count, p_compat); + if (E->next()) { + p_store_string_func(p_store_string_ud, ",\n"); + } else { + p_store_string_func(p_store_string_ud, "\n"); + } } + + p_store_string_func(p_store_string_ud, "}"); } + } - p_store_string_func(p_store_string_ud, "}"); + if (dict.is_typed()) { + p_store_string_func(p_store_string_ud, ")"); } } break; case Variant::ARRAY: { Array array = p_variant; - if (array.get_typed_builtin() != Variant::NIL) { + + if (array.is_typed()) { p_store_string_func(p_store_string_ud, "Array["); Variant::Type builtin_type = (Variant::Type)array.get_typed_builtin(); @@ -2107,6 +2316,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str p_recursion_count++; p_store_string_func(p_store_string_ud, "["); + bool first = true; for (const Variant &var : array) { if (first) { @@ -2120,7 +2330,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str p_store_string_func(p_store_string_ud, "]"); } - if (array.get_typed_builtin() != Variant::NIL) { + if (array.is_typed()) { p_store_string_func(p_store_string_ud, ")"); } } break; diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index 48176163a1..b60ff83cf1 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -703,6 +703,50 @@ struct VariantIndexedSetGet_Array { static uint64_t get_indexed_size(const Variant *base) { return 0; } }; +struct VariantIndexedSetGet_Dictionary { + static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { + const Variant *ptr = VariantGetInternalPtr<Dictionary>::get_ptr(base)->getptr(index); + if (!ptr) { + *oob = true; + return; + } + *value = *ptr; + *oob = false; + } + static void ptr_get(const void *base, int64_t index, void *member) { + // Avoid ptrconvert for performance. + const Dictionary &v = *reinterpret_cast<const Dictionary *>(base); + const Variant *ptr = v.getptr(index); + NULL_TEST(ptr); + PtrToArg<Variant>::encode(*ptr, member); + } + static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) { + if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) { + *valid = false; + *oob = true; + return; + } + (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value; + *oob = false; + *valid = true; + } + static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) { + if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) { + *oob = true; + return; + } + (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value; + *oob = false; + } + static void ptr_set(void *base, int64_t index, const void *member) { + Dictionary &v = *reinterpret_cast<Dictionary *>(base); + v[index] = PtrToArg<Variant>::convert(member); + } + static Variant::Type get_index_type() { return Variant::NIL; } + static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; } + static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<Dictionary>::get_ptr(base)->size(); } +}; + struct VariantIndexedSetGet_String { static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { int64_t length = VariantGetInternalPtr<String>::get_ptr(base)->length(); @@ -789,51 +833,6 @@ struct VariantIndexedSetGet_String { static uint64_t get_indexed_size(const Variant *base) { return VariantInternal::get_string(base)->length(); } }; -#define INDEXED_SETGET_STRUCT_DICT(m_base_type) \ - struct VariantIndexedSetGet_##m_base_type { \ - static void get(const Variant *base, int64_t index, Variant *value, bool *oob) { \ - const Variant *ptr = VariantGetInternalPtr<m_base_type>::get_ptr(base)->getptr(index); \ - if (!ptr) { \ - *oob = true; \ - return; \ - } \ - *value = *ptr; \ - *oob = false; \ - } \ - static void ptr_get(const void *base, int64_t index, void *member) { \ - /* avoid ptrconvert for performance*/ \ - const m_base_type &v = *reinterpret_cast<const m_base_type *>(base); \ - const Variant *ptr = v.getptr(index); \ - NULL_TEST(ptr); \ - PtrToArg<Variant>::encode(*ptr, member); \ - } \ - static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) { \ - if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \ - *valid = false; \ - *oob = true; \ - return; \ - } \ - (*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \ - *oob = false; \ - *valid = true; \ - } \ - static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) { \ - if (VariantGetInternalPtr<m_base_type>::get_ptr(base)->is_read_only()) { \ - *oob = true; \ - return; \ - } \ - (*VariantGetInternalPtr<m_base_type>::get_ptr(base))[index] = *value; \ - *oob = false; \ - } \ - static void ptr_set(void *base, int64_t index, const void *member) { \ - m_base_type &v = *reinterpret_cast<m_base_type *>(base); \ - v[index] = PtrToArg<Variant>::convert(member); \ - } \ - static Variant::Type get_index_type() { return Variant::NIL; } \ - static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; } \ - static uint64_t get_indexed_size(const Variant *base) { return VariantGetInternalPtr<m_base_type>::get_ptr(base)->size(); } \ - }; - INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2, double, real_t, 2) INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector2i, int64_t, int32_t, 2) INDEXED_SETGET_STRUCT_BULTIN_NUMERIC(Vector3, double, real_t, 3) @@ -858,8 +857,6 @@ INDEXED_SETGET_STRUCT_TYPED(PackedStringArray, String) INDEXED_SETGET_STRUCT_TYPED(PackedColorArray, Color) INDEXED_SETGET_STRUCT_TYPED(PackedVector4Array, Vector4) -INDEXED_SETGET_STRUCT_DICT(Dictionary) - struct VariantIndexedSetterGetterInfo { void (*setter)(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) = nullptr; void (*getter)(const Variant *base, int64_t index, Variant *value, bool *oob) = nullptr; @@ -1288,8 +1285,8 @@ void Variant::get_property_list(List<PropertyInfo> *p_list) const { List<Variant> keys; dic->get_key_list(&keys); for (const Variant &E : keys) { - if (E.get_type() == Variant::STRING) { - p_list->push_back(PropertyInfo(Variant::STRING, E)); + if (E.is_string()) { + p_list->push_back(PropertyInfo(dic->get_valid(E).get_type(), E)); } } } else if (type == OBJECT) { diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 7534a154a1..384fe6c4a6 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -452,12 +452,14 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do case Variant::QUATERNION: case Variant::BASIS: case Variant::COLOR: + case Variant::TRANSFORM2D: + case Variant::TRANSFORM3D: break; default: r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::NIL; - return R"(Argument "from" must be "int", "float", "Vector2", "Vector3", "Vector4", "Quaternion", "Basis, or "Color".)"; + return R"(Argument "from" must be "int", "float", "Vector2", "Vector3", "Vector4", "Color", "Quaternion", "Basis", "Transform2D", or "Transform3D".)"; } if (from.get_type() != to.get_type()) { @@ -490,6 +492,12 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do case Variant::BASIS: { return VariantInternalAccessor<Basis>::get(&from).slerp(VariantInternalAccessor<Basis>::get(&to), weight); } break; + case Variant::TRANSFORM2D: { + return VariantInternalAccessor<Transform2D>::get(&from).interpolate_with(VariantInternalAccessor<Transform2D>::get(&to), weight); + } break; + case Variant::TRANSFORM3D: { + return VariantInternalAccessor<Transform3D>::get(&from).interpolate_with(VariantInternalAccessor<Transform3D>::get(&to), weight); + } break; case Variant::COLOR: { return VariantInternalAccessor<Color>::get(&from).lerp(VariantInternalAccessor<Color>::get(&to), weight); } break; diff --git a/doc/class.xsd b/doc/class.xsd index f0e1241053..28e02e870d 100644 --- a/doc/class.xsd +++ b/doc/class.xsd @@ -246,6 +246,8 @@ <xs:attribute type="xs:string" name="data_type" /> <xs:attribute type="xs:string" name="type" /> <xs:attribute type="xs:string" name="default" use="optional" /> + <xs:attribute type="xs:string" name="deprecated" use="optional" /> + <xs:attribute type="xs:string" name="experimental" use="optional" /> <xs:attribute type="xs:string" name="keywords" use="optional" /> </xs:extension> </xs:simpleContent> diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index 63f5947280..f222cbc969 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -90,7 +90,7 @@ <param index="0" name="from" type="float" /> <param index="1" name="to" type="float" /> <description> - Returns the difference between the two angles, in the range of [code][-PI, +PI][/code]. When [param from] and [param to] are opposite, returns [code]-PI[/code] if [param from] is smaller than [param to], or [code]PI[/code] otherwise. + Returns the difference between the two angles (in radians), in the range of [code][-PI, +PI][/code]. When [param from] and [param to] are opposite, returns [code]-PI[/code] if [param from] is smaller than [param to], or [code]PI[/code] otherwise. </description> </method> <method name="asin"> @@ -621,13 +621,13 @@ <param index="1" name="to" type="Variant" /> <param index="2" name="weight" type="Variant" /> <description> - Linearly interpolates between two values by the factor defined in [param weight]. To perform interpolation, [param weight] should be between [code]0.0[/code] and [code]1.0[/code] (inclusive). However, values outside this range are allowed and can be used to perform [i]extrapolation[/i]. If this is not desired, use [method clamp] on the result of this function. - Both [param from] and [param to] must be the same type. Supported types: [int], [float], [Vector2], [Vector3], [Vector4], [Color], [Quaternion], [Basis]. + Linearly interpolates between two values by the factor defined in [param weight]. To perform interpolation, [param weight] should be between [code]0.0[/code] and [code]1.0[/code] (inclusive). However, values outside this range are allowed and can be used to perform [i]extrapolation[/i]. If this is not desired, use [method clampf] to limit [param weight]. + Both [param from] and [param to] must be the same type. Supported types: [int], [float], [Vector2], [Vector3], [Vector4], [Color], [Quaternion], [Basis], [Transform2D], [Transform3D]. [codeblock] lerp(0, 4, 0.75) # Returns 3.0 [/codeblock] See also [method inverse_lerp] which performs the reverse of this operation. To perform eased interpolation with [method lerp], combine it with [method ease] or [method smoothstep]. See also [method remap] to map a continuous series of values to another. - [b]Note:[/b] For better type safety, use [method lerpf], [method Vector2.lerp], [method Vector3.lerp], [method Vector4.lerp], [method Color.lerp], [method Quaternion.slerp] or [method Basis.slerp]. + [b]Note:[/b] For better type safety, use [method lerpf], [method Vector2.lerp], [method Vector3.lerp], [method Vector4.lerp], [method Color.lerp], [method Quaternion.slerp], [method Basis.slerp], [method Transform2D.interpolate_with], or [method Transform3D.interpolate_with]. </description> </method> <method name="lerp_angle" keywords="interpolate"> @@ -2915,6 +2915,9 @@ <constant name="PROPERTY_HINT_ARRAY_TYPE" value="31" enum="PropertyHint"> Hints that a property is an [Array] with the stored type specified in the hint string. </constant> + <constant name="PROPERTY_HINT_DICTIONARY_TYPE" value="38" enum="PropertyHint"> + Hints that a property is a [Dictionary] with the stored types specified in the hint string. + </constant> <constant name="PROPERTY_HINT_LOCALE_ID" value="32" enum="PropertyHint"> Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country. </constant> @@ -2930,7 +2933,7 @@ <constant name="PROPERTY_HINT_PASSWORD" value="36" enum="PropertyHint"> Hints that a string property is a password, and every character is replaced with the secret character. </constant> - <constant name="PROPERTY_HINT_MAX" value="38" enum="PropertyHint"> + <constant name="PROPERTY_HINT_MAX" value="39" enum="PropertyHint"> Represents the size of the [enum PropertyHint] enum. </constant> <constant name="PROPERTY_USAGE_NONE" value="0" enum="PropertyUsageFlags" is_bitfield="true"> diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml index dc1bee4336..d762ffa5a6 100644 --- a/doc/classes/AnimationMixer.xml +++ b/doc/classes/AnimationMixer.xml @@ -376,7 +376,19 @@ </constant> <constant name="ANIMATION_CALLBACK_MODE_DISCRETE_FORCE_CONTINUOUS" value="2" enum="AnimationCallbackModeDiscrete"> Always treat the [constant Animation.UPDATE_DISCRETE] track value as [constant Animation.UPDATE_CONTINUOUS] with [constant Animation.INTERPOLATION_NEAREST]. This is the default behavior for [AnimationTree]. - If a value track has non-numeric type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE] with [constant Animation.UPDATE_DISCRETE]. + If a value track has un-interpolatable type key values, it is internally converted to use [constant ANIMATION_CALLBACK_MODE_DISCRETE_RECESSIVE] with [constant Animation.UPDATE_DISCRETE]. + Un-interpolatable type list: + - [constant @GlobalScope.TYPE_NIL] + - [constant @GlobalScope.TYPE_NODE_PATH] + - [constant @GlobalScope.TYPE_RID] + - [constant @GlobalScope.TYPE_OBJECT] + - [constant @GlobalScope.TYPE_CALLABLE] + - [constant @GlobalScope.TYPE_SIGNAL] + - [constant @GlobalScope.TYPE_DICTIONARY] + - [constant @GlobalScope.TYPE_PACKED_BYTE_ARRAY] + [constant @GlobalScope.TYPE_BOOL] and [constant @GlobalScope.TYPE_INT] are treated as [constant @GlobalScope.TYPE_FLOAT] during blending and rounded when the result is retrieved. + It is same for arrays and vectors with them such as [constant @GlobalScope.TYPE_PACKED_INT32_ARRAY] or [constant @GlobalScope.TYPE_VECTOR2I], they are treated as [constant @GlobalScope.TYPE_PACKED_FLOAT32_ARRAY] or [constant @GlobalScope.TYPE_VECTOR2]. Also note that for arrays, the size is also interpolated. + [constant @GlobalScope.TYPE_STRING] and [constant @GlobalScope.TYPE_STRING_NAME] are interpolated between character codes and lengths, but note that there is a difference in algorithm between interpolation between keys and interpolation by blending. </constant> </constants> </class> diff --git a/doc/classes/CPUParticles3D.xml b/doc/classes/CPUParticles3D.xml index 27726ff8a2..d7770f2cd5 100644 --- a/doc/classes/CPUParticles3D.xml +++ b/doc/classes/CPUParticles3D.xml @@ -168,6 +168,10 @@ <member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis"> The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING]. </member> + <member name="emission_ring_cone_angle" type="float" setter="set_emission_ring_cone_angle" getter="get_emission_ring_cone_angle"> + The angle of the cone when using the emitter [constant EMISSION_SHAPE_RING]. The default angle of 90 degrees results in a ring, while an angle of 0 degrees results in a cone. Intermediate values will result in a ring where one end is larger than the other. + [b]Note:[/b] Depending on [member emission_ring_height], the angle may be clamped if the ring's end is reached to form a perfect cone. + </member> <member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height"> The height of the ring when using the emitter [constant EMISSION_SHAPE_RING]. </member> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 186ee1b9c4..0a0223c550 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -96,6 +96,7 @@ <param index="3" name="texture" type="Texture2D" default="null" /> <description> Draws a colored polygon of any number of points, convex or concave. Unlike [method draw_polygon], a single color must be specified for the whole polygon. + [b]Note:[/b] If you frequently redraw the same polygon with a large number of vertices, consider pre-calculating the triangulation with [method Geometry2D.triangulate_polygon] and using [method draw_mesh], [method draw_multimesh], or [method RenderingServer.canvas_item_add_triangle_array]. </description> </method> <method name="draw_dashed_line"> @@ -251,6 +252,7 @@ <param index="3" name="texture" type="Texture2D" default="null" /> <description> Draws a solid polygon of any number of points, convex or concave. Unlike [method draw_colored_polygon], each point's color can be changed individually. See also [method draw_polyline] and [method draw_polyline_colors]. If you need more flexibility (such as being able to use bones), use [method RenderingServer.canvas_item_add_triangle_array] instead. + [b]Note:[/b] If you frequently redraw the same polygon with a large number of vertices, consider pre-calculating the triangulation with [method Geometry2D.triangulate_polygon] and using [method draw_mesh], [method draw_multimesh], or [method RenderingServer.canvas_item_add_triangle_array]. </description> </method> <method name="draw_polyline"> diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml index b8b4fc7b08..feb2a07e4a 100644 --- a/doc/classes/Dictionary.xml +++ b/doc/classes/Dictionary.xml @@ -150,6 +150,19 @@ </constructor> <constructor name="Dictionary"> <return type="Dictionary" /> + <param index="0" name="base" type="Dictionary" /> + <param index="1" name="key_type" type="int" /> + <param index="2" name="key_class_name" type="StringName" /> + <param index="3" name="key_script" type="Variant" /> + <param index="4" name="value_type" type="int" /> + <param index="5" name="value_class_name" type="StringName" /> + <param index="6" name="value_script" type="Variant" /> + <description> + Creates a typed dictionary from the [param base] dictionary. A typed dictionary can only contain keys and values of the given types, or that inherit from the given classes, as described by this constructor's parameters. + </description> + </constructor> + <constructor name="Dictionary"> + <return type="Dictionary" /> <param index="0" name="from" type="Dictionary" /> <description> Returns the same dictionary as [param from]. If you need a copy of the dictionary, use [method duplicate]. @@ -157,6 +170,13 @@ </constructor> </constructors> <methods> + <method name="assign"> + <return type="void" /> + <param index="0" name="dictionary" type="Dictionary" /> + <description> + Assigns elements of another [param dictionary] into the dictionary. Resizes the dictionary to match [param dictionary]. Performs type conversions if the dictionary is typed. + </description> + </method> <method name="clear"> <return type="void" /> <description> @@ -202,6 +222,42 @@ Gets a value and ensures the key is set. If the [param key] exists in the dictionary, this behaves like [method get]. Otherwise, the [param default] value is inserted into the dictionary and returned. </description> </method> + <method name="get_typed_key_builtin" qualifiers="const"> + <return type="int" /> + <description> + Returns the built-in [Variant] type of the typed dictionary's keys as a [enum Variant.Type] constant. If the keys are not typed, returns [constant TYPE_NIL]. See also [method is_typed_key]. + </description> + </method> + <method name="get_typed_key_class_name" qualifiers="const"> + <return type="StringName" /> + <description> + Returns the [b]built-in[/b] class name of the typed dictionary's keys, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_key] and [method Object.get_class]. + </description> + </method> + <method name="get_typed_key_script" qualifiers="const"> + <return type="Variant" /> + <description> + Returns the [Script] instance associated with this typed dictionary's keys, or [code]null[/code] if it does not exist. See also [method is_typed_key]. + </description> + </method> + <method name="get_typed_value_builtin" qualifiers="const"> + <return type="int" /> + <description> + Returns the built-in [Variant] type of the typed dictionary's values as a [enum Variant.Type] constant. If the values are not typed, returns [constant TYPE_NIL]. See also [method is_typed_value]. + </description> + </method> + <method name="get_typed_value_class_name" qualifiers="const"> + <return type="StringName" /> + <description> + Returns the [b]built-in[/b] class name of the typed dictionary's values, if the built-in [Variant] type is [constant TYPE_OBJECT]. Otherwise, returns an empty [StringName]. See also [method is_typed_value] and [method Object.get_class]. + </description> + </method> + <method name="get_typed_value_script" qualifiers="const"> + <return type="Variant" /> + <description> + Returns the [Script] instance associated with this typed dictionary's values, or [code]null[/code] if it does not exist. See also [method is_typed_value]. + </description> + </method> <method name="has" qualifiers="const"> <return type="bool" /> <param index="0" name="key" type="Variant" /> @@ -284,6 +340,45 @@ Returns [code]true[/code] if the dictionary is read-only. See [method make_read_only]. Dictionaries are automatically read-only if declared with [code]const[/code] keyword. </description> </method> + <method name="is_same_typed" qualifiers="const"> + <return type="bool" /> + <param index="0" name="dictionary" type="Dictionary" /> + <description> + Returns [code]true[/code] if the dictionary is typed the same as [param dictionary]. + </description> + </method> + <method name="is_same_typed_key" qualifiers="const"> + <return type="bool" /> + <param index="0" name="dictionary" type="Dictionary" /> + <description> + Returns [code]true[/code] if the dictionary's keys are typed the same as [param dictionary]'s keys. + </description> + </method> + <method name="is_same_typed_value" qualifiers="const"> + <return type="bool" /> + <param index="0" name="dictionary" type="Dictionary" /> + <description> + Returns [code]true[/code] if the dictionary's values are typed the same as [param dictionary]'s values. + </description> + </method> + <method name="is_typed" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the dictionary is typed. Typed dictionaries can only store keys/values of their associated type and provide type safety for the [code][][/code] operator. Methods of typed dictionary still return [Variant]. + </description> + </method> + <method name="is_typed_key" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the dictionary's keys are typed. + </description> + </method> + <method name="is_typed_value" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if the dictionary's values are typed. + </description> + </method> <method name="keys" qualifiers="const"> <return type="Array" /> <description> diff --git a/doc/classes/EditorContextMenuPlugin.xml b/doc/classes/EditorContextMenuPlugin.xml new file mode 100644 index 0000000000..7eeee3d7fd --- /dev/null +++ b/doc/classes/EditorContextMenuPlugin.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorContextMenuPlugin" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Plugin for adding custom context menus in the editor. + </brief_description> + <description> + [EditorContextMenuPlugin] allows for the addition of custom options in the editor's context menu. + Currently, context menus are supported for three commonly used areas: the file system, scene tree, and editor script list panel. + </description> + <tutorials> + </tutorials> + <methods> + <method name="_popup_menu" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="paths" type="PackedStringArray" /> + <description> + Called when creating a context menu, custom options can be added by using the [method add_context_menu_item] function. + </description> + </method> + <method name="add_context_menu_item"> + <return type="void" /> + <param index="0" name="name" type="String" /> + <param index="1" name="callback" type="Callable" /> + <param index="2" name="icon" type="Texture2D" default="null" /> + <param index="3" name="shortcut" type="Shortcut" default="null" /> + <description> + Add custom options to the context menu of the currently specified slot. + To trigger a [param shortcut] before the context menu is created, please additionally call the [method add_menu_shortcut] function. + [codeblock] + func _popup_menu(paths): + add_context_menu_item("File Custom options", handle, ICON) + [/codeblock] + </description> + </method> + <method name="add_menu_shortcut"> + <return type="void" /> + <param index="0" name="shortcut" type="Shortcut" /> + <param index="1" name="callback" type="Callable" /> + <description> + To register the shortcut for the context menu, call this function within the [method Object._init] function, even if the context menu has not been created yet. + Note that this method should only be invoked from [method Object._init]; otherwise, the shortcut will not be registered correctly. + [codeblock] + func _init(): + add_menu_shortcut(SHORTCUT, handle); + [/codeblock] + </description> + </method> + </methods> +</class> diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index 6809d4ac93..795c5c1c2f 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -305,8 +305,9 @@ <return type="void" /> <param index="0" name="callback" type="Callable" /> <param index="1" name="valid_types" type="StringName[]" default="[]" /> + <param index="2" name="current_value" type="Node" default="null" /> <description> - Pops up an editor dialog for selecting a [Node] from the edited scene. The [param callback] must take a single argument of type [NodePath]. It is called on the selected [NodePath] or the empty path [code]^""[/code] if the dialog is canceled. If [param valid_types] is provided, the dialog will only show Nodes that match one of the listed Node types. + Pops up an editor dialog for selecting a [Node] from the edited scene. The [param callback] must take a single argument of type [NodePath]. It is called on the selected [NodePath] or the empty path [code]^""[/code] if the dialog is canceled. If [param valid_types] is provided, the dialog will only show Nodes that match one of the listed Node types. If [param current_value] is provided, the Node will be automatically selected in the tree, if it exists. [b]Example:[/b] Display the node selection dialog as soon as this node is added to the tree for the first time: [codeblock] func _ready(): @@ -326,8 +327,9 @@ <param index="0" name="object" type="Object" /> <param index="1" name="callback" type="Callable" /> <param index="2" name="type_filter" type="PackedInt32Array" default="PackedInt32Array()" /> + <param index="3" name="current_value" type="String" default="""" /> <description> - Pops up an editor dialog for selecting properties from [param object]. The [param callback] must take a single argument of type [NodePath]. It is called on the selected property path (see [method NodePath.get_as_property_path]) or the empty path [code]^""[/code] if the dialog is canceled. If [param type_filter] is provided, the dialog will only show properties that match one of the listed [enum Variant.Type] values. + Pops up an editor dialog for selecting properties from [param object]. The [param callback] must take a single argument of type [NodePath]. It is called on the selected property path (see [method NodePath.get_as_property_path]) or the empty path [code]^""[/code] if the dialog is canceled. If [param type_filter] is provided, the dialog will only show properties that match one of the listed [enum Variant.Type] values. If [param current_value] is provided, the property will be selected automatically in the property list, if it exists. [codeblock] func _ready(): if Engine.is_editor_hint(): diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index 37f8b2213b..b3191e5378 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -401,6 +401,15 @@ Adds a script at [param path] to the Autoload list as [param name]. </description> </method> + <method name="add_context_menu_plugin"> + <return type="void" /> + <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" /> + <param index="1" name="plugin" type="EditorContextMenuPlugin" /> + <description> + Adds a plugin to the context menu. [param slot] is the position in the context menu where the plugin will be added. + Context menus are supported for three commonly used areas: the file system, scene tree, and editor script list panel. + </description> + </method> <method name="add_control_to_bottom_panel"> <return type="Button" /> <param index="0" name="control" type="Control" /> @@ -624,6 +633,14 @@ Removes an Autoload [param name] from the list. </description> </method> + <method name="remove_context_menu_plugin"> + <return type="void" /> + <param index="0" name="slot" type="int" enum="EditorPlugin.ContextMenuSlot" /> + <param index="1" name="plugin" type="EditorContextMenuPlugin" /> + <description> + Removes a context menu plugin from the specified slot. + </description> + </method> <method name="remove_control_from_bottom_panel"> <return type="void" /> <param index="0" name="control" type="Control" /> @@ -874,5 +891,17 @@ <constant name="AFTER_GUI_INPUT_CUSTOM" value="2" enum="AfterGUIInput"> Pass the [InputEvent] to other editor plugins except the main [Node3D] one. This can be used to prevent node selection changes and work with sub-gizmos instead. </constant> + <constant name="CONTEXT_SLOT_SCENE_TREE" value="0" enum="ContextMenuSlot"> + Context menu slot for the SceneTree. + </constant> + <constant name="CONTEXT_SLOT_FILESYSTEM" value="1" enum="ContextMenuSlot"> + Context menu slot for the FileSystem. + </constant> + <constant name="CONTEXT_SLOT_SCRIPT_EDITOR" value="2" enum="ContextMenuSlot"> + Context menu slot for the ScriptEditor file list. + </constant> + <constant name="CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE" value="3" enum="ContextMenuSlot"> + Context menu slot for the FileSystem create submenu. + </constant> </constants> </class> diff --git a/doc/classes/EditorResourcePreviewGenerator.xml b/doc/classes/EditorResourcePreviewGenerator.xml index fcfdbb5c44..9c9b6d11b2 100644 --- a/doc/classes/EditorResourcePreviewGenerator.xml +++ b/doc/classes/EditorResourcePreviewGenerator.xml @@ -23,7 +23,7 @@ <param index="2" name="metadata" type="Dictionary" /> <description> Generate a preview from a given resource with the specified size. This must always be implemented. - Returning an empty texture is an OK way to fail and let another generator take care. + Returning [code]null[/code] is an OK way to fail and let another generator take care. Care must be taken because this function is always called from a thread (not the main thread). [param metadata] dictionary can be modified to store file-specific metadata that can be used in [method EditorResourceTooltipPlugin._make_tooltip_for_path] (like image size, sample length etc.). </description> @@ -35,7 +35,7 @@ <param index="2" name="metadata" type="Dictionary" /> <description> Generate a preview directly from a path with the specified size. Implementing this is optional, as default code will load and call [method _generate]. - Returning an empty texture is an OK way to fail and let another generator take care. + Returning [code]null[/code] is an OK way to fail and let another generator take care. Care must be taken because this function is always called from a thread (not the main thread). [param metadata] dictionary can be modified to store file-specific metadata that can be used in [method EditorResourceTooltipPlugin._make_tooltip_for_path] (like image size, sample length etc.). </description> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index b6565b81f2..1de63b4a39 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -194,6 +194,9 @@ The maximum number of script functions that can be displayed per frame in the profiler. If there are more script functions called in a given profiler frame, these functions will be discarded from the profiling results entirely. [b]Note:[/b] This setting is only read when the profiler is first started, so changing it during profiling will have no effect. </member> + <member name="debugger/profiler_target_fps" type="int" setter="" getter=""> + The target frame rate shown in the visual profiler graph, in frames per second. + </member> <member name="debugger/remote_inspect_refresh_interval" type="float" setter="" getter=""> The refresh interval for the remote inspector's properties (in seconds). Lower values are more reactive, but may cause stuttering while the project is running from the editor and the [b]Remote[/b] scene tree is selected in the Scene tree dock. </member> @@ -204,8 +207,11 @@ If [code]true[/code], displays folders in the FileSystem dock's bottom pane when split mode is enabled. If [code]false[/code], only files will be displayed in the bottom pane. Split mode can be toggled by pressing the icon next to the [code]res://[/code] folder path. [b]Note:[/b] This setting has no effect when split mode is disabled (which is the default). </member> + <member name="docks/filesystem/other_file_extensions" type="String" setter="" getter=""> + A comma separated list of unsupported file extensions to show in the FileSystem dock, e.g. [code]"ico,icns"[/code]. + </member> <member name="docks/filesystem/textfile_extensions" type="String" setter="" getter=""> - List of file extensions to consider as editable text files in the FileSystem dock (by double-clicking on the files). + A comma separated list of file extensions to consider as editable text files in the FileSystem dock (by double-clicking on the files), e.g. [code]"txt,md,cfg,ini,log,json,yml,yaml,toml,xml"[/code]. </member> <member name="docks/filesystem/thumbnail_size" type="int" setter="" getter=""> The thumbnail size to use in the FileSystem dock (in pixels). See also [member filesystem/file_dialog/thumbnail_size]. @@ -967,6 +973,7 @@ - [b]Auto (based on screen size)[/b] (default) will automatically choose how to launch the Play window based on the device and screen metrics. Defaults to [b]Same as Editor[/b] on phones and [b]Side-by-side with Editor[/b] on tablets. - [b]Same as Editor[/b] will launch the Play window in the same window as the Editor. - [b]Side-by-side with Editor[/b] will launch the Play window side-by-side with the Editor window. + - [b]Launch in PiP mode[/b] will launch the Play window directly in picture-in-picture (PiP) mode if PiP mode is supported and enabled. When maximized, the Play window will occupy the same window as the Editor. [b]Note:[/b] Only available in the Android editor. </member> <member name="run/window_placement/play_window_pip_mode" type="int" setter="" getter=""> diff --git a/doc/classes/JavaClass.xml b/doc/classes/JavaClass.xml index ecfcaa8781..9a6c30df10 100644 --- a/doc/classes/JavaClass.xml +++ b/doc/classes/JavaClass.xml @@ -1,13 +1,33 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="JavaClass" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Represents an object from the Java Native Interface. + Represents a class from the Java Native Interface. </brief_description> <description> - Represents an object from the Java Native Interface. It is returned from [method JavaClassWrapper.wrap]. - [b]Note:[/b] This class only works on Android. For any other build, this class does nothing. + Represents a class from the Java Native Interface. It is returned from [method JavaClassWrapper.wrap]. + [b]Note:[/b] This class only works on Android. On any other platform, this class does nothing. [b]Note:[/b] This class is not to be confused with [JavaScriptObject]. </description> <tutorials> </tutorials> + <methods> + <method name="get_java_class_name" qualifiers="const"> + <return type="String" /> + <description> + Returns the Java class name. + </description> + </method> + <method name="get_java_method_list" qualifiers="const"> + <return type="Dictionary[]" /> + <description> + Returns the object's Java methods and their signatures as an [Array] of dictionaries, in the same format as [method Object.get_method_list]. + </description> + </method> + <method name="get_java_parent_class" qualifiers="const"> + <return type="JavaClass" /> + <description> + Returns a [JavaClass] representing the Java parent class of this class. + </description> + </method> + </methods> </class> diff --git a/doc/classes/JavaClassWrapper.xml b/doc/classes/JavaClassWrapper.xml index 01c3392b04..b43e149e9f 100644 --- a/doc/classes/JavaClassWrapper.xml +++ b/doc/classes/JavaClassWrapper.xml @@ -6,6 +6,15 @@ <description> The JavaClassWrapper singleton provides a way for the Godot application to send and receive data through the [url=https://developer.android.com/training/articles/perf-jni]Java Native Interface[/url] (JNI). [b]Note:[/b] This singleton is only available in Android builds. + [codeblock] + var LocalDateTime = JavaClassWrapper.wrap("java.time.LocalDateTime") + var DateTimeFormatter = JavaClassWrapper.wrap("java.time.format.DateTimeFormatter") + + var datetime = LocalDateTime.now() + var formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss") + + print(datetime.format(formatter)) + [/codeblock] </description> <tutorials> </tutorials> diff --git a/doc/classes/JavaObject.xml b/doc/classes/JavaObject.xml new file mode 100644 index 0000000000..f38070e7d9 --- /dev/null +++ b/doc/classes/JavaObject.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="JavaObject" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Represents an object from the Java Native Interface. + </brief_description> + <description> + Represents an object from the Java Native Interface. It can be returned from Java methods called on [JavaClass] or other [JavaObject]s. See [JavaClassWrapper] for an example. + [b]Note:[/b] This class only works on Android. On any other platform, this class does nothing. + [b]Note:[/b] This class is not to be confused with [JavaScriptObject]. + </description> + <tutorials> + </tutorials> + <methods> + <method name="get_java_class" qualifiers="const"> + <return type="JavaClass" /> + <description> + Returns the [JavaClass] that this object is an instance of. + </description> + </method> + </methods> +</class> diff --git a/doc/classes/MenuBar.xml b/doc/classes/MenuBar.xml index 9e4287331c..bcf63f0610 100644 --- a/doc/classes/MenuBar.xml +++ b/doc/classes/MenuBar.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="MenuBar" inherits="Control" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - A horizontal menu bar that creates a [MenuButton] for each [PopupMenu] child. + A horizontal menu bar that creates a menu for each [PopupMenu] child. </brief_description> <description> - A horizontal menu bar that creates a [MenuButton] for each [PopupMenu] child. New items are created by adding [PopupMenu]s to this node. + A horizontal menu bar that creates a menu for each [PopupMenu] child. New items are created by adding [PopupMenu]s to this node. </description> <tutorials> </tutorials> @@ -105,9 +105,11 @@ </member> <member name="prefer_global_menu" type="bool" setter="set_prefer_global_menu" getter="is_prefer_global_menu" default="true"> If [code]true[/code], [MenuBar] will use system global menu when supported. + [b]Note:[/b] If [code]true[/code] and global menu is supported, this node is not displayed, has zero size, and all its child nodes except [PopupMenu]s are inaccessible. + [b]Note:[/b] This property overrides the value of the [member PopupMenu.prefer_native_menu] property of the child nodes. </member> <member name="start_index" type="int" setter="set_start_index" getter="get_start_index" default="-1"> - Position in the global menu to insert first [MenuBar] item at. + Position order in the global menu to insert [MenuBar] items at. All menu items in the [MenuBar] are always inserted as a continuous range. Menus with lower [member start_index] are inserted first. Menus with [member start_index] equal to [code]-1[/code] are inserted last. </member> <member name="switch_on_hover" type="bool" setter="set_switch_on_hover" getter="is_switch_on_hover" default="true"> If [code]true[/code], when the cursor hovers above menu item, it will close the current [PopupMenu] and open the other one. diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml index abbb4c4eeb..d8e2c43566 100644 --- a/doc/classes/MeshInstance3D.xml +++ b/doc/classes/MeshInstance3D.xml @@ -21,6 +21,14 @@ [b]Performance:[/b] [Mesh] data needs to be received from the GPU, stalling the [RenderingServer] in the process. </description> </method> + <method name="bake_mesh_from_current_skeleton_pose"> + <return type="ArrayMesh" /> + <param index="0" name="existing" type="ArrayMesh" default="null" /> + <description> + Takes a snapshot of the current animated skeleton pose of the skinned mesh and bakes it to the provided [param existing] mesh. If no [param existing] mesh is provided a new [ArrayMesh] is created, baked, and returned. Requires a skeleton with a registered skin to work. Blendshapes are ignored. Mesh surface materials are not copied. + [b]Performance:[/b] [Mesh] data needs to be retrieved from the GPU, stalling the [RenderingServer] in the process. + </description> + </method> <method name="create_convex_collision"> <return type="void" /> <param index="0" name="clean" type="bool" default="true" /> diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 9675f5af50..777950c075 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -132,8 +132,10 @@ <return type="Dictionary" /> <param index="0" name="path" type="String" /> <param index="1" name="arguments" type="PackedStringArray" /> + <param index="2" name="blocking" type="bool" default="true" /> <description> Creates a new process that runs independently of Godot with redirected IO. It will not terminate when Godot terminates. The path specified in [param path] must exist and be an executable file or macOS [code].app[/code] bundle. The path is resolved based on the current platform. The [param arguments] are used in the given order and separated by a space. + If [param blocking] is [code]false[/code], created pipes work in non-blocking mode, i.e. read and write operations will return immediately. Use [method FileAccess.get_error] to check if the last read/write operation was successful. If the process cannot be created, this method returns an empty [Dictionary]. Otherwise, this method returns a [Dictionary] with the following keys: - [code]"stdio"[/code] - [FileAccess] to access the process stdin and stdout pipes (read/write). - [code]"stderr"[/code] - [FileAccess] to access the process stderr pipe (read only). diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index 0cfa3a5d4a..a331c05e47 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -192,6 +192,55 @@ [b]Note:[/b] If [method _init] is defined with [i]required[/i] parameters, the Object with script may only be created directly. If any other means (such as [method PackedScene.instantiate] or [method Node.duplicate]) are used, the script's initialization will fail. </description> </method> + <method name="_iter_get" qualifiers="virtual"> + <return type="Variant" /> + <param index="0" name="iter" type="Variant" /> + <description> + Returns the current iterable value. [param iter] stores the iteration state, but unlike [method _iter_init] and [method _iter_next] the state is supposed to be read-only, so there is no [Array] wrapper. + </description> + </method> + <method name="_iter_init" qualifiers="virtual"> + <return type="bool" /> + <param index="0" name="iter" type="Array" /> + <description> + Initializes the iterator. [param iter] stores the iteration state. Since GDScript does not support passing arguments by reference, a single-element array is used as a wrapper. Returns [code]true[/code] so long as the iterator has not reached the end. + Example: + [codeblock] + class MyRange: + var _from + var _to + + func _init(from, to): + assert(from <= to) + _from = from + _to = to + + func _iter_init(iter): + iter[0] = _from + return iter[0] < _to + + func _iter_next(iter): + iter[0] += 1 + return iter[0] < _to + + func _iter_get(iter): + return iter + + func _ready(): + var my_range = MyRange.new(2, 5) + for x in my_range: + print(x) # Prints 2, 3, 4. + [/codeblock] + [b]Note:[/b] Alternatively, you can ignore [param iter] and use the object's state instead, see [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_advanced.html#custom-iterators]online docs[/url] for an example. Note that in this case you will not be able to reuse the same iterator instance in nested loops. Also, make sure you reset the iterator state in this method if you want to reuse the same instance multiple times. + </description> + </method> + <method name="_iter_next" qualifiers="virtual"> + <return type="bool" /> + <param index="0" name="iter" type="Array" /> + <description> + Moves the iterator to the next iteration. [param iter] stores the iteration state. Since GDScript does not support passing arguments by reference, a single-element array is used as a wrapper. Returns [code]true[/code] so long as the iterator has not reached the end. + </description> + </method> <method name="_notification" qualifiers="virtual"> <return type="void" /> <param index="0" name="what" type="int" /> diff --git a/doc/classes/ParticleProcessMaterial.xml b/doc/classes/ParticleProcessMaterial.xml index 1502690b45..28c60194c8 100644 --- a/doc/classes/ParticleProcessMaterial.xml +++ b/doc/classes/ParticleProcessMaterial.xml @@ -207,6 +207,10 @@ <member name="emission_ring_axis" type="Vector3" setter="set_emission_ring_axis" getter="get_emission_ring_axis"> The axis of the ring when using the emitter [constant EMISSION_SHAPE_RING]. </member> + <member name="emission_ring_cone_angle" type="float" setter="set_emission_ring_cone_angle" getter="get_emission_ring_cone_angle"> + The angle of the cone when using the emitter [constant EMISSION_SHAPE_RING]. The default angle of 90 degrees results in a ring, while an angle of 0 degrees results in a cone. Intermediate values will result in a ring where one end is larger than the other. + [b]Note:[/b] Depending on [member emission_ring_height], the angle may be clamped if the ring's end is reached to form a perfect cone. + </member> <member name="emission_ring_height" type="float" setter="set_emission_ring_height" getter="get_emission_ring_height"> The height of the ring when using the emitter [constant EMISSION_SHAPE_RING]. </member> diff --git a/doc/classes/PathFollow2D.xml b/doc/classes/PathFollow2D.xml index 61db34fb02..7444bfc23a 100644 --- a/doc/classes/PathFollow2D.xml +++ b/doc/classes/PathFollow2D.xml @@ -26,6 +26,7 @@ </member> <member name="progress_ratio" type="float" setter="set_progress_ratio" getter="get_progress_ratio" default="0.0"> The distance along the path as a number in the range 0.0 (for the first vertex) to 1.0 (for the last). This is just another way of expressing the progress within the path, as the offset supplied is multiplied internally by the path's length. + It can be set or get only if the [PathFollow2D] is the child of a [Path2D] which is part of the scene tree, and that this [Path2D] has a [Curve2D] with a non-zero length. Otherwise, trying to set this field will print an error, and getting this field will return [code]0.0[/code]. </member> <member name="rotates" type="bool" setter="set_rotates" getter="is_rotating" default="true"> If [code]true[/code], this node rotates to follow the path, with the +X direction facing forward on the path. diff --git a/doc/classes/PathFollow3D.xml b/doc/classes/PathFollow3D.xml index b505c6ea8b..bcd04d51ca 100644 --- a/doc/classes/PathFollow3D.xml +++ b/doc/classes/PathFollow3D.xml @@ -36,6 +36,7 @@ </member> <member name="progress_ratio" type="float" setter="set_progress_ratio" getter="get_progress_ratio" default="0.0"> The distance from the first vertex, considering 0.0 as the first vertex and 1.0 as the last. This is just another way of expressing the progress within the path, as the progress supplied is multiplied internally by the path's length. + It can be set or get only if the [PathFollow3D] is the child of a [Path3D] which is part of the scene tree, and that this [Path3D] has a [Curve3D] with a non-zero length. Otherwise, trying to set this field will print an error, and getting this field will return [code]0.0[/code]. </member> <member name="rotation_mode" type="int" setter="set_rotation_mode" getter="get_rotation_mode" enum="PathFollow3D.RotationMode" default="3"> Allows or forbids rotation on one or more axes, depending on the [enum RotationMode] constants being used. diff --git a/doc/classes/PolygonPathFinder.xml b/doc/classes/PolygonPathFinder.xml index f37a8a05e4..b70633883c 100644 --- a/doc/classes/PolygonPathFinder.xml +++ b/doc/classes/PolygonPathFinder.xml @@ -42,6 +42,30 @@ <return type="bool" /> <param index="0" name="point" type="Vector2" /> <description> + Returns [code]true[/code] if [param point] falls inside the polygon area. + [codeblocks] + [gdscript] + var polygon_path_finder = PolygonPathFinder.new() + var points = [Vector2(0.0, 0.0), Vector2(1.0, 0.0), Vector2(0.0, 1.0)] + var connections = [0, 1, 1, 2, 2, 0] + polygon_path_finder.setup(points, connections) + print(polygon_path_finder.is_point_inside(Vector2(0.2, 0.2))) # Prints true + print(polygon_path_finder.is_point_inside(Vector2(1.0, 1.0))) # Prints false + [/gdscript] + [csharp] + var polygonPathFinder = new PolygonPathFinder(); + var points = new Vector2[] + { + new Vector2(0.0f, 0.0f), + new Vector2(1.0f, 0.0f), + new Vector2(0.0f, 1.0f) + }; + var connections = new int[] { 0, 1, 1, 2, 2, 0 }; + polygonPathFinder.Setup(points, connections); + GD.Print(polygonPathFinder.IsPointInside(new Vector2(0.2f, 0.2f))); // Prints true + GD.Print(polygonPathFinder.IsPointInside(new Vector2(1.0f, 1.0f))); // Prints false + [/csharp] + [/codeblocks] </description> </method> <method name="set_point_penalty"> @@ -56,6 +80,27 @@ <param index="0" name="points" type="PackedVector2Array" /> <param index="1" name="connections" type="PackedInt32Array" /> <description> + Sets up [PolygonPathFinder] with an array of points that define the vertices of the polygon, and an array of indices that determine the edges of the polygon. + The length of [param connections] must be even, returns an error if odd. + [codeblocks] + [gdscript] + var polygon_path_finder = PolygonPathFinder.new() + var points = [Vector2(0.0, 0.0), Vector2(1.0, 0.0), Vector2(0.0, 1.0)] + var connections = [0, 1, 1, 2, 2, 0] + polygon_path_finder.setup(points, connections) + [/gdscript] + [csharp] + var polygonPathFinder = new PolygonPathFinder(); + var points = new Vector2[] + { + new Vector2(0.0f, 0.0f), + new Vector2(1.0f, 0.0f), + new Vector2(0.0f, 1.0f) + }; + var connections = new int[] { 0, 1, 1, 2, 2, 0 }; + polygonPathFinder.Setup(points, connections); + [/csharp] + [/codeblocks] </description> </method> </methods> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 1f31fef5ca..497070fa81 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2909,7 +2909,9 @@ The default compression factor for lossless WebP. Decompression speed is mostly unaffected by the compression factor. Supported values are 0 to 100. </member> <member name="rendering/viewport/hdr_2d" type="bool" setter="" getter="" default="false"> - If [code]true[/code], enables [member Viewport.use_hdr_2d] on the root viewport. This allows 2D rendering to take advantage of effects requiring high dynamic range (e.g. 2D glow). + If [code]true[/code], enables [member Viewport.use_hdr_2d] on the root viewport. 2D rendering will use an high dynamic range (HDR) format framebuffer matching the bit depth of the 3D framebuffer. When using the Forward+ renderer this will be an [code]RGBA16[/code] framebuffer, while when using the Mobile renderer it will be an [code]RGB10_A2[/code] framebuffer. Additionally, 2D rendering will take place in linear color space and will be converted to sRGB space immediately before blitting to the screen. Practically speaking, this means that the end result of the Viewport will not be clamped into the [code]0-1[/code] range and can be used in 3D rendering without color space adjustments. This allows 2D rendering to take advantage of effects requiring high dynamic range (e.g. 2D glow) as well as substantially improves the appearance of effects requiring highly detailed gradients. + [b]Note:[/b] This setting will have no effect when using the GL Compatibility renderer as the GL Compatibility renderer always renders in low dynamic range for performance reasons. + [b]Note:[/b] This property is only read when the project starts. To toggle HDR 2D at runtime, set [member Viewport.use_hdr_2d] on the root [Viewport]. </member> <member name="rendering/viewport/transparent_background" type="bool" setter="" getter="" default="false"> If [code]true[/code], enables [member Viewport.transparent_bg] on the root viewport. This allows per-pixel transparency to be effective after also enabling [member display/window/size/transparent] and [member display/window/per_pixel_transparency/allowed]. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index c81d5d4fab..cea9a4cec4 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -329,6 +329,7 @@ <param index="4" name="texture" type="RID" default="RID()" /> <description> Draws a 2D polygon on the [CanvasItem] pointed to by the [param item] [RID]. If you need more flexibility (such as being able to use bones), use [method canvas_item_add_triangle_array] instead. See also [method CanvasItem.draw_polygon]. + [b]Note:[/b] If you frequently redraw the same polygon with a large number of vertices, consider pre-calculating the triangulation with [method Geometry2D.triangulate_polygon] and using [method CanvasItem.draw_mesh], [method CanvasItem.draw_multimesh], or [method canvas_item_add_triangle_array]. </description> </method> <method name="canvas_item_add_polyline"> @@ -412,6 +413,14 @@ [b]Note:[/b] [param count] is unused and can be left unspecified. </description> </method> + <method name="canvas_item_attach_skeleton"> + <return type="void" /> + <param index="0" name="item" type="RID" /> + <param index="1" name="skeleton" type="RID" /> + <description> + Attaches a skeleton to the [CanvasItem]. Removes the previous skeleton. + </description> + </method> <method name="canvas_item_clear"> <return type="void" /> <param index="0" name="item" type="RID" /> diff --git a/doc/classes/ResourceImporterWAV.xml b/doc/classes/ResourceImporterWAV.xml index 4362b38042..3caa66d262 100644 --- a/doc/classes/ResourceImporterWAV.xml +++ b/doc/classes/ResourceImporterWAV.xml @@ -4,13 +4,14 @@ Imports a WAV audio file for playback. </brief_description> <description> - WAV is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means high numbers of WAV sounds can be played at the same time, even on low-end deviceS. + WAV is an uncompressed format, which can provide higher quality compared to Ogg Vorbis and MP3. It also has the lowest CPU cost to decode. This means high numbers of WAV sounds can be played at the same time, even on low-end devices. + By default, Godot imports WAV files using the lossy Quite OK Audio compression. You may change this by setting the [member compress/mode] property. </description> <tutorials> <link title="Importing audio samples">$DOCS_URL/tutorials/assets_pipeline/importing_audio_samples.html</link> </tutorials> <members> - <member name="compress/mode" type="int" setter="" getter="" default="0"> + <member name="compress/mode" type="int" setter="" getter="" default="2"> The compression mode to use on import. - [b]PCM (Uncompressed):[/b] Imports audio data without any form of compression, preserving the highest possible quality. It has the lowest CPU cost, but the highest memory usage. - [b]IMA ADPCM:[/b] Applies fast, lossy compression during import, noticeably decreasing the quality, but with low CPU cost and memory usage. Does not support seeking and only Forward loop mode is supported. diff --git a/doc/classes/SpinBox.xml b/doc/classes/SpinBox.xml index 3fb30a81b8..6da6a301fe 100644 --- a/doc/classes/SpinBox.xml +++ b/doc/classes/SpinBox.xml @@ -98,12 +98,12 @@ Vertical separation between the up and down buttons. </theme_item> <theme_item name="buttons_width" data_type="constant" type="int" default="16"> - Width of the up and down buttons. If smaller than any icon set on the buttons, the respective icon may overlap neighboring elements, unless [theme_item set_min_buttons_width_from_icons] is different than [code]0[/code]. + Width of the up and down buttons. If smaller than any icon set on the buttons, the respective icon may overlap neighboring elements. If smaller than [code]0[/code], the width is automatically adjusted from the icon size. </theme_item> <theme_item name="field_and_buttons_separation" data_type="constant" type="int" default="2"> Width of the horizontal separation between the text input field ([LineEdit]) and the buttons. </theme_item> - <theme_item name="set_min_buttons_width_from_icons" data_type="constant" type="int" default="1"> + <theme_item name="set_min_buttons_width_from_icons" data_type="constant" type="int" default="1" deprecated="This property exists only for compatibility with older themes. Setting [theme_item buttons_width] to a negative value has the same effect."> If not [code]0[/code], the minimum button width corresponds to the widest of all icons set on those buttons, even if [theme_item buttons_width] is smaller. </theme_item> <theme_item name="down" data_type="icon" type="Texture2D"> diff --git a/doc/classes/SpriteFrames.xml b/doc/classes/SpriteFrames.xml index fd8c15c560..b891f4adcd 100644 --- a/doc/classes/SpriteFrames.xml +++ b/doc/classes/SpriteFrames.xml @@ -39,6 +39,14 @@ Removes all animations. An empty [code]default[/code] animation will be created. </description> </method> + <method name="duplicate_animation"> + <return type="void" /> + <param index="0" name="anim_from" type="StringName" /> + <param index="1" name="anim_to" type="StringName" /> + <description> + Duplicates the animation [param anim_from] to a new animation named [param anim_to]. Fails if [param anim_to] already exists, or if [param anim_from] does not exist. + </description> + </method> <method name="get_animation_loop" qualifiers="const"> <return type="bool" /> <param index="0" name="anim" type="StringName" /> diff --git a/doc/classes/TileData.xml b/doc/classes/TileData.xml index 9468763b6e..f9505124ef 100644 --- a/doc/classes/TileData.xml +++ b/doc/classes/TileData.xml @@ -16,6 +16,13 @@ Adds a collision polygon to the tile on the given TileSet physics layer. </description> </method> + <method name="add_occluder_polygon"> + <return type="void" /> + <param index="0" name="layer_id" type="int" /> + <description> + Adds an occlusion polygon to the tile on the TileSet occlusion layer with index [param layer_id]. + </description> + </method> <method name="get_collision_polygon_one_way_margin" qualifiers="const"> <return type="float" /> <param index="0" name="layer_id" type="int" /> @@ -78,7 +85,7 @@ [param flip_h], [param flip_v], and [param transpose] allow transforming the returned polygon. </description> </method> - <method name="get_occluder" qualifiers="const"> + <method name="get_occluder" qualifiers="const" deprecated="Use [method get_occluder_polygon] instead."> <return type="OccluderPolygon2D" /> <param index="0" name="layer_id" type="int" /> <param index="1" name="flip_h" type="bool" default="false" /> @@ -89,6 +96,25 @@ [param flip_h], [param flip_v], and [param transpose] allow transforming the returned polygon. </description> </method> + <method name="get_occluder_polygon" qualifiers="const"> + <return type="OccluderPolygon2D" /> + <param index="0" name="layer_id" type="int" /> + <param index="1" name="polygon_index" type="int" /> + <param index="2" name="flip_h" type="bool" default="false" /> + <param index="3" name="flip_v" type="bool" default="false" /> + <param index="4" name="transpose" type="bool" default="false" /> + <description> + Returns the occluder polygon at index [param polygon_index] from the TileSet occlusion layer with index [param layer_id]. + The [param flip_h], [param flip_v], and [param transpose] parameters can be [code]true[/code] to transform the returned polygon. + </description> + </method> + <method name="get_occluder_polygons_count" qualifiers="const"> + <return type="int" /> + <param index="0" name="layer_id" type="int" /> + <description> + Returns the number of occluder polygons of the tile in the TileSet occlusion layer with index [param layer_id]. + </description> + </method> <method name="get_terrain_peering_bit" qualifiers="const"> <return type="int" /> <param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" /> @@ -119,6 +145,14 @@ Removes the polygon at index [param polygon_index] for TileSet physics layer with index [param layer_id]. </description> </method> + <method name="remove_occluder_polygon"> + <return type="void" /> + <param index="0" name="layer_id" type="int" /> + <param index="1" name="polygon_index" type="int" /> + <description> + Removes the polygon at index [param polygon_index] for TileSet occlusion layer with index [param layer_id]. + </description> + </method> <method name="set_collision_polygon_one_way"> <return type="void" /> <param index="0" name="layer_id" type="int" /> @@ -194,7 +228,7 @@ Sets the navigation polygon for the TileSet navigation layer with index [param layer_id]. </description> </method> - <method name="set_occluder"> + <method name="set_occluder" deprecated="Use [method set_occluder_polygon] instead."> <return type="void" /> <param index="0" name="layer_id" type="int" /> <param index="1" name="occluder_polygon" type="OccluderPolygon2D" /> @@ -202,6 +236,23 @@ Sets the occluder for the TileSet occlusion layer with index [param layer_id]. </description> </method> + <method name="set_occluder_polygon"> + <return type="void" /> + <param index="0" name="layer_id" type="int" /> + <param index="1" name="polygon_index" type="int" /> + <param index="2" name="polygon" type="OccluderPolygon2D" /> + <description> + Sets the occluder for polygon with index [param polygon_index] in the TileSet occlusion layer with index [param layer_id]. + </description> + </method> + <method name="set_occluder_polygons_count"> + <return type="void" /> + <param index="0" name="layer_id" type="int" /> + <param index="1" name="polygons_count" type="int" /> + <description> + Sets the occluder polygon count in the TileSet occlusion layer with index [param layer_id]. + </description> + </method> <method name="set_terrain_peering_bit"> <return type="void" /> <param index="0" name="peering_bit" type="int" enum="TileSet.CellNeighbor" /> diff --git a/doc/classes/TreeItem.xml b/doc/classes/TreeItem.xml index 78a703c213..bd46c54990 100644 --- a/doc/classes/TreeItem.xml +++ b/doc/classes/TreeItem.xml @@ -19,7 +19,7 @@ <param index="3" name="disabled" type="bool" default="false" /> <param index="4" name="tooltip_text" type="String" default="""" /> <description> - Adds a button with [Texture2D] [param button] at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text]. + Adds a button with [Texture2D] [param button] to the end of the cell at column [param column]. The [param id] is used to identify the button in the according [signal Tree.button_clicked] signal and can be different from the buttons index. If not specified, the next available index is used, which may be retrieved by calling [method get_button_count] immediately before this method. Optionally, the button can be [param disabled] and have a [param tooltip_text]. </description> </method> <method name="add_child"> @@ -643,7 +643,7 @@ <param index="0" name="column" type="int" /> <param index="1" name="texture" type="Texture2D" /> <description> - Sets the given cell's icon [Texture2D]. The cell has to be in [constant CELL_MODE_ICON] mode. + Sets the given cell's icon [Texture2D]. If the cell is in [constant CELL_MODE_ICON] mode, the icon is displayed in the center of the cell. Otherwise, the icon is displayed before the cell's text. [constant CELL_MODE_RANGE] does not display an icon. </description> </method> <method name="set_icon_max_width"> @@ -811,17 +811,17 @@ </members> <constants> <constant name="CELL_MODE_STRING" value="0" enum="TreeCellMode"> - Cell shows a string label. When editable, the text can be edited using a [LineEdit], or a [TextEdit] popup if [method set_edit_multiline] is used. + Cell shows a string label, optionally with an icon. When editable, the text can be edited using a [LineEdit], or a [TextEdit] popup if [method set_edit_multiline] is used. </constant> <constant name="CELL_MODE_CHECK" value="1" enum="TreeCellMode"> - Cell shows a checkbox, optionally with text. The checkbox can be pressed, released, or indeterminate (via [method set_indeterminate]). The checkbox can't be clicked unless the cell is editable. + Cell shows a checkbox, optionally with text and an icon. The checkbox can be pressed, released, or indeterminate (via [method set_indeterminate]). The checkbox can't be clicked unless the cell is editable. </constant> <constant name="CELL_MODE_RANGE" value="2" enum="TreeCellMode"> Cell shows a numeric range. When editable, it can be edited using a range slider. Use [method set_range] to set the value and [method set_range_config] to configure the range. This cell can also be used in a text dropdown mode when you assign a text with [method set_text]. Separate options with a comma, e.g. [code]"Option1,Option2,Option3"[/code]. </constant> <constant name="CELL_MODE_ICON" value="3" enum="TreeCellMode"> - Cell shows an icon. It can't be edited nor display text. + Cell shows an icon. It can't be edited nor display text. The icon is always centered within the cell. </constant> <constant name="CELL_MODE_CUSTOM" value="4" enum="TreeCellMode"> Cell shows as a clickable button. It will display an arrow similar to [OptionButton], but doesn't feature a dropdown (for that you can use [constant CELL_MODE_RANGE]). Clicking the button emits the [signal Tree.item_edited] signal. The button is flat by default, you can use [method set_custom_as_button] to display it with a [StyleBox]. diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index b24f26a764..350fd65197 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -117,6 +117,12 @@ Returns the visible rectangle in global screen coordinates. </description> </method> + <method name="gui_cancel_drag"> + <return type="void" /> + <description> + Cancels the drag operation that was previously started through [method Control._get_drag_data] or forced with [method Control.force_drag]. + </description> + </method> <method name="gui_get_drag_data" qualifiers="const"> <return type="Variant" /> <description> diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index 761a7f8f4a..101660881b 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -1311,9 +1311,6 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: if property_def.text is not None and property_def.text.strip() != "": f.write(f"{format_text_block(property_def.text.strip(), property_def, state)}\n\n") - if property_def.type_name.type_name in PACKED_ARRAY_TYPES: - tmp = f"[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [{property_def.type_name.type_name}] for more details." - f.write(f"{format_text_block(tmp, property_def, state)}\n\n") elif property_def.deprecated is None and property_def.experimental is None: f.write(".. container:: contribute\n\n\t") f.write( @@ -1323,6 +1320,11 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: + "\n\n" ) + # Add copy note to built-in properties returning `Packed*Array`. + if property_def.type_name.type_name in PACKED_ARRAY_TYPES: + copy_note = f"[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [{property_def.type_name.type_name}] for more details." + f.write(f"{format_text_block(copy_note, property_def, state)}\n\n") + index += 1 # Constructor, Method, Operator descriptions @@ -1507,24 +1509,23 @@ def make_type(klass: str, state: State) -> str: if klass.find("*") != -1: # Pointer, ignore return f"``{klass}``" - link_type = klass - is_array = False + def resolve_type(link_type: str) -> str: + if link_type in state.classes: + return f":ref:`{link_type}<class_{link_type}>`" + else: + print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state) + return f"``{link_type}``" - if link_type.endswith("[]"): # Typed array, strip [] to link to contained type. - link_type = link_type[:-2] - is_array = True + if klass.endswith("[]"): # Typed array, strip [] to link to contained type. + return f":ref:`Array<class_Array>`\\[{resolve_type(klass[:-len('[]')])}\\]" - if link_type in state.classes: - type_rst = f":ref:`{link_type}<class_{link_type}>`" - if is_array: - type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]" - return type_rst + if klass.startswith("Dictionary["): # Typed dictionary, split elements to link contained types. + parts = klass[len("Dictionary[") : -len("]")].partition(", ") + key = parts[0] + value = parts[2] + return f":ref:`Dictionary<class_Dictionary>`\\[{resolve_type(key)}, {resolve_type(value)}\\]" - print_error(f'{state.current_class}.xml: Unresolved type "{link_type}".', state) - type_rst = f"``{link_type}``" - if is_array: - type_rst = f":ref:`Array<class_Array>`\\[{type_rst}\\]" - return type_rst + return resolve_type(klass) def make_enum(t: str, is_bitfield: bool, state: State) -> str: diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index b9206f310e..df7aba265b 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -468,7 +468,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ update_skeletons = false; } // Canvas group begins here, render until before this item - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info, material_screen_texture_mipmaps_cached); item_count = 0; if (ci->canvas_group_owner->canvas_group->mode != RS::CANVAS_GROUP_MODE_TRANSPARENT) { @@ -499,7 +499,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ mesh_storage->update_mesh_instances(); update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true, r_render_info, material_screen_texture_mipmaps_cached); item_count = 0; if (ci->canvas_group->blur_mipmaps) { @@ -523,7 +523,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ } //render anything pending, including clearing if no items - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, false, r_render_info, material_screen_texture_mipmaps_cached); item_count = 0; texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps); @@ -553,7 +553,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ mesh_storage->update_mesh_instances(); update_skeletons = false; } - _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info); + _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, canvas_group_owner != nullptr, r_render_info, material_screen_texture_mipmaps_cached); //then reset item_count = 0; } @@ -573,10 +573,10 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ state.current_instance_buffer_index = 0; } -void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info) { +void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer, RenderingMethod::RenderInfo *r_render_info, bool p_backbuffer_has_mipmaps) { GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton(); - canvas_begin(p_to_render_target, p_to_backbuffer); + canvas_begin(p_to_render_target, p_to_backbuffer, p_backbuffer_has_mipmaps); if (p_item_count <= 0) { // Nothing to draw, just call canvas_begin() to clear the render target and return. @@ -2169,7 +2169,7 @@ bool RasterizerCanvasGLES3::free(RID p_rid) { void RasterizerCanvasGLES3::update() { } -void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backbuffer) { +void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backbuffer, bool p_backbuffer_has_mipmaps) { GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton(); GLES3::Config *config = GLES3::Config::get_singleton(); @@ -2184,6 +2184,7 @@ void RasterizerCanvasGLES3::canvas_begin(RID p_to_render_target, bool p_to_backb glBindFramebuffer(GL_FRAMEBUFFER, render_target->fbo); glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 4); glBindTexture(GL_TEXTURE_2D, render_target->backbuffer); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, p_backbuffer_has_mipmaps ? render_target->mipmap_count - 1 : 0); } if (render_target->is_transparent || p_to_backbuffer) { diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h index 027f717eb7..9c0d0abccb 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.h +++ b/drivers/gles3/rasterizer_canvas_gles3.h @@ -335,7 +335,7 @@ public: typedef void Texture; - void canvas_begin(RID p_to_render_target, bool p_to_backbuffer); + void canvas_begin(RID p_to_render_target, bool p_to_backbuffer, bool p_backbuffer_has_mipmaps); //virtual void draw_window_margins(int *black_margin, RID *black_image) override; void draw_lens_distortion_rect(const Rect2 &p_rect, float p_k1, float p_k2, const Vector2 &p_eye_center, float p_oversample); @@ -361,7 +361,7 @@ public: void _prepare_canvas_texture(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, uint32_t &r_index, Size2 &r_texpixel_size); void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used, RenderingMethod::RenderInfo *r_render_info = nullptr) override; - void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr); + void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false, RenderingMethod::RenderInfo *r_render_info = nullptr, bool p_backbuffer_has_mipmaps = false); void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *¤t_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used, const Point2 &p_repeat_offset); void _render_batch(Light *p_lights, uint32_t p_index, RenderingMethod::RenderInfo *r_render_info = nullptr); bool _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization); diff --git a/drivers/gles3/rasterizer_gles3.h b/drivers/gles3/rasterizer_gles3.h index 80a4a792bb..92c96519d2 100644 --- a/drivers/gles3/rasterizer_gles3.h +++ b/drivers/gles3/rasterizer_gles3.h @@ -113,6 +113,7 @@ public: static void make_current(bool p_gles_over_gl) { gles_over_gl = p_gles_over_gl; + OS::get_singleton()->set_gles_over_gl(gles_over_gl); _create_func = _create_current; low_end = true; } diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 3ed8042f3f..8b6d3e3268 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -912,7 +912,7 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p RS::SkyMode sky_mode = sky->mode; if (sky_mode == RS::SKY_MODE_AUTOMATIC) { - if (shader_data->uses_time || shader_data->uses_position) { + if ((shader_data->uses_time || shader_data->uses_position) && sky->radiance_size == 256) { update_single_frame = true; sky_mode = RS::SKY_MODE_REALTIME; } else if (shader_data->uses_light || shader_data->ubo_size > 0) { diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index 6143ce2167..393ba014c6 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -1803,22 +1803,22 @@ void main() { #ifdef LIGHTMAP_BICUBIC_FILTER vec3 lm_light_l0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), lightmap_texture_size).rgb; - vec3 lm_light_l1n1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), lightmap_texture_size).rgb; - vec3 lm_light_l1_0 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), lightmap_texture_size).rgb; - vec3 lm_light_l1p1 = textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), lightmap_texture_size).rgb; + vec3 lm_light_l1n1 = (textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), lightmap_texture_size).rgb - vec3(0.5)) * 2.0; + vec3 lm_light_l1_0 = (textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), lightmap_texture_size).rgb - vec3(0.5)) * 2.0; + vec3 lm_light_l1p1 = (textureArray_bicubic(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), lightmap_texture_size).rgb - vec3(0.5)) * 2.0; #else vec3 lm_light_l0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb; - vec3 lm_light_l1n1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb; - vec3 lm_light_l1_0 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb; - vec3 lm_light_l1p1 = textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb; + vec3 lm_light_l1n1 = (textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb - vec3(0.5)) * 2.0; + vec3 lm_light_l1_0 = (textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb - vec3(0.5)) * 2.0; + vec3 lm_light_l1p1 = (textureLod(lightmap_textures, uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb - vec3(0.5)) * 2.0; #endif vec3 n = normalize(lightmap_normal_xform * normal); ambient_light += lm_light_l0 * lightmap_exposure_normalization; - ambient_light += lm_light_l1n1 * n.y * lightmap_exposure_normalization; - ambient_light += lm_light_l1_0 * n.z * lightmap_exposure_normalization; - ambient_light += lm_light_l1p1 * n.x * lightmap_exposure_normalization; + ambient_light += lm_light_l1n1 * n.y * (lm_light_l0 * lightmap_exposure_normalization * 4.0); + ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * lightmap_exposure_normalization * 4.0); + ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * lightmap_exposure_normalization * 4.0); #else #ifdef LIGHTMAP_BICUBIC_FILTER ambient_light += textureArray_bicubic(lightmap_textures, uvw, lightmap_texture_size).rgb * lightmap_exposure_normalization; diff --git a/drivers/gles3/shaders/stdlib_inc.glsl b/drivers/gles3/shaders/stdlib_inc.glsl index 029084c34c..f88c218506 100644 --- a/drivers/gles3/shaders/stdlib_inc.glsl +++ b/drivers/gles3/shaders/stdlib_inc.glsl @@ -9,19 +9,17 @@ // Floating point pack/unpack functions are part of the GLSL ES 300 specification used by web and mobile. uint float2half(uint f) { - uint e = f & uint(0x7f800000); - if (e <= uint(0x38000000)) { - return uint(0); - } else { - return ((f >> uint(16)) & uint(0x8000)) | - (((e - uint(0x38000000)) >> uint(13)) & uint(0x7c00)) | - ((f >> uint(13)) & uint(0x03ff)); - } + uint b = f + uint(0x00001000); + uint e = (b & uint(0x7F800000)) >> 23; + uint m = b & uint(0x007FFFFF); + return (b & uint(0x80000000)) >> uint(16) | uint(e > uint(112)) * ((((e - uint(112)) << uint(10)) & uint(0x7C00)) | m >> uint(13)) | (uint(e < uint(113)) & uint(e > uint(101))) * ((((uint(0x007FF000) + m) >> (uint(125) - e)) + uint(1)) >> uint(1)) | uint(e > uint(143)) * uint(0x7FFF); } uint half2float(uint h) { - uint h_e = h & uint(0x7c00); - return ((h & uint(0x8000)) << uint(16)) | uint((h_e >> uint(10)) != uint(0)) * (((h_e + uint(0x1c000)) << uint(13)) | ((h & uint(0x03ff)) << uint(13))); + uint e = (h & uint(0x7C00)) >> uint(10); + uint m = (h & uint(0x03FF)) << uint(13); + uint v = m >> uint(23); + return (h & uint(0x8000)) << uint(16) | uint(e != uint(0)) * ((e + uint(112)) << uint(23) | m) | (uint(e == uint(0)) & uint(m != uint(0))) * ((v - uint(37)) << uint(23) | ((m << (uint(150) - v)) & uint(0x007FE000))); } uint godot_packHalf2x16(vec2 v) { diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp index 2b3c19dbb8..d6472c44c1 100644 --- a/drivers/gles3/storage/config.cpp +++ b/drivers/gles3/storage/config.cpp @@ -121,7 +121,7 @@ Config::Config() { #ifdef WEB_ENABLED msaa_supported = (msaa_max_samples > 0); #else - msaa_supported = extensions.has("GL_EXT_framebuffer_multisample"); + msaa_supported = true; #endif #ifndef IOS_ENABLED #ifdef WEB_ENABLED diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index b55a2e0a8a..de82d74aff 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -743,6 +743,7 @@ String MeshStorage::mesh_get_path(RID p_mesh) const { } void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) { + ERR_FAIL_COND_MSG(p_mesh == p_shadow_mesh, "Cannot set a mesh as its own shadow mesh."); Mesh *mesh = mesh_owner.get_or_null(p_mesh); ERR_FAIL_NULL(mesh); diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 57fe96fb6f..36393dde86 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -1389,8 +1389,22 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *r_info) { tinfo.format = t->format; tinfo.width = t->alloc_width; tinfo.height = t->alloc_height; - tinfo.depth = t->depth; tinfo.bytes = t->total_data_size; + + switch (t->type) { + case Texture::TYPE_3D: + tinfo.depth = t->depth; + break; + + case Texture::TYPE_LAYERED: + tinfo.depth = t->layers; + break; + + default: + tinfo.depth = 0; + break; + } + r_info->push_back(tinfo); } } @@ -1521,7 +1535,11 @@ void TextureStorage::_texture_set_data(RID p_texture, const Ref<Image> &p_image, h = MAX(1, h >> 1); } - texture->total_data_size = tsize; + if (texture->target == GL_TEXTURE_CUBE_MAP || texture->target == GL_TEXTURE_2D_ARRAY) { + texture->total_data_size = tsize * texture->layers; + } else { + texture->total_data_size = tsize; + } texture->stored_cube_sides |= (1 << p_layer); diff --git a/drivers/metal/pixel_formats.mm b/drivers/metal/pixel_formats.mm index ac737b3f0a..36edbab99a 100644 --- a/drivers/metal/pixel_formats.mm +++ b/drivers/metal/pixel_formats.mm @@ -1200,7 +1200,7 @@ void PixelFormats::modifyMTLFormatCapabilities(id<MTLDevice> p_device) { // Disable for iOS simulator last. #if TARGET_OS_SIMULATOR - if (![mtlDevice supportsFamily:MTLGPUFamilyApple5]) { + if (![p_device supportsFamily:MTLGPUFamilyApple5]) { disableAllMTLPixFmtCaps(R8Unorm_sRGB); disableAllMTLPixFmtCaps(RG8Unorm_sRGB); disableAllMTLPixFmtCaps(B5G6R5Unorm); diff --git a/drivers/metal/rendering_context_driver_metal.h b/drivers/metal/rendering_context_driver_metal.h index 0363ab111a..7e0b09186d 100644 --- a/drivers/metal/rendering_context_driver_metal.h +++ b/drivers/metal/rendering_context_driver_metal.h @@ -33,22 +33,36 @@ #ifdef METAL_ENABLED -#import "rendering_device_driver_metal.h" - #import "servers/rendering/rendering_context_driver.h" +#import "servers/rendering/rendering_device_driver.h" #import <CoreGraphics/CGGeometry.h> + +#ifdef __OBJC__ +#import "metal_objects.h" + #import <Metal/Metal.h> #import <QuartzCore/CALayer.h> @class CAMetalLayer; @protocol CAMetalDrawable; +#else +typedef enum MTLPixelFormat { + MTLPixelFormatBGRA8Unorm = 80, +} MTLPixelFormat; +class MDCommandBuffer; +#endif + class PixelFormats; class MDResourceCache; class API_AVAILABLE(macos(11.0), ios(14.0)) RenderingContextDriverMetal : public RenderingContextDriver { protected: - id<MTLDevice> metal_device = nil; +#ifdef __OBJC__ + id<MTLDevice> metal_device = nullptr; +#else + void *metal_device = nullptr; +#endif Device device; // There is only one device on Apple Silicon. public: @@ -73,12 +87,20 @@ public: // Platform-specific data for the Windows embedded in this driver. struct WindowPlatformData { +#ifdef __OBJC__ CAMetalLayer *__unsafe_unretained layer; +#else + void *layer; +#endif }; - class Surface { + class API_AVAILABLE(macos(11.0), ios(14.0)) Surface { protected: +#ifdef __OBJC__ id<MTLDevice> device; +#else + void *device; +#endif public: uint32_t width = 0; @@ -86,8 +108,15 @@ public: DisplayServer::VSyncMode vsync_mode = DisplayServer::VSYNC_ENABLED; bool needs_resize = false; - Surface(id<MTLDevice> p_device) : - device(p_device) {} + Surface( +#ifdef __OBJC__ + id<MTLDevice> p_device +#else + void *p_device +#endif + ) : + device(p_device) { + } virtual ~Surface() = default; MTLPixelFormat get_pixel_format() const { return MTLPixelFormatBGRA8Unorm; } @@ -96,104 +125,14 @@ public: virtual void present(MDCommandBuffer *p_cmd_buffer) = 0; }; - class SurfaceLayer : public Surface { - CAMetalLayer *__unsafe_unretained layer = nil; - LocalVector<MDFrameBuffer> frame_buffers; - LocalVector<id<MTLDrawable>> drawables; - uint32_t rear = -1; - uint32_t front = 0; - uint32_t count = 0; - - public: - SurfaceLayer(CAMetalLayer *p_layer, id<MTLDevice> p_device) : - Surface(p_device), layer(p_layer) { - layer.allowsNextDrawableTimeout = YES; - layer.framebufferOnly = YES; - layer.opaque = OS::get_singleton()->is_layered_allowed() ? NO : YES; - layer.pixelFormat = get_pixel_format(); - layer.device = p_device; - } - - ~SurfaceLayer() override { - layer = nil; - } - - Error resize(uint32_t p_desired_framebuffer_count) override final { - if (width == 0 || height == 0) { - // Very likely the window is minimized, don't create a swap chain. - return ERR_SKIP; - } - - CGSize drawableSize = CGSizeMake(width, height); - CGSize current = layer.drawableSize; - if (!CGSizeEqualToSize(current, drawableSize)) { - layer.drawableSize = drawableSize; - } - - // Metal supports a maximum of 3 drawables. - p_desired_framebuffer_count = MIN(3U, p_desired_framebuffer_count); - layer.maximumDrawableCount = p_desired_framebuffer_count; - -#if TARGET_OS_OSX - // Display sync is only supported on macOS. - switch (vsync_mode) { - case DisplayServer::VSYNC_MAILBOX: - case DisplayServer::VSYNC_ADAPTIVE: - case DisplayServer::VSYNC_ENABLED: - layer.displaySyncEnabled = YES; - break; - case DisplayServer::VSYNC_DISABLED: - layer.displaySyncEnabled = NO; - break; - } +#ifdef __OBJC__ + id<MTLDevice> +#else + void * #endif - drawables.resize(p_desired_framebuffer_count); - frame_buffers.resize(p_desired_framebuffer_count); - for (uint32_t i = 0; i < p_desired_framebuffer_count; i++) { - // Reserve space for the drawable texture. - frame_buffers[i].textures.resize(1); - } - - return OK; - } - - RDD::FramebufferID acquire_next_frame_buffer() override final { - if (count == frame_buffers.size()) { - return RDD::FramebufferID(); - } - - rear = (rear + 1) % frame_buffers.size(); - count++; - - MDFrameBuffer &frame_buffer = frame_buffers[rear]; - frame_buffer.size = Size2i(width, height); - - id<CAMetalDrawable> drawable = layer.nextDrawable; - ERR_FAIL_NULL_V_MSG(drawable, RDD::FramebufferID(), "no drawable available"); - drawables[rear] = drawable; - frame_buffer.textures.write[0] = drawable.texture; - - return RDD::FramebufferID(&frame_buffer); - } - - void present(MDCommandBuffer *p_cmd_buffer) override final { - if (count == 0) { - return; - } - - // Release texture and drawable. - frame_buffers[front].textures.write[0] = nil; - id<MTLDrawable> drawable = drawables[front]; - drawables[front] = nil; - - count--; - front = (front + 1) % frame_buffers.size(); - - [p_cmd_buffer->get_command_buffer() presentDrawable:drawable]; - } - }; - - id<MTLDevice> get_metal_device() const { return metal_device; } + get_metal_device() const { + return metal_device; + } #pragma mark - Initialization diff --git a/drivers/metal/rendering_context_driver_metal.mm b/drivers/metal/rendering_context_driver_metal.mm index b257d7142a..b97b586352 100644 --- a/drivers/metal/rendering_context_driver_metal.mm +++ b/drivers/metal/rendering_context_driver_metal.mm @@ -30,6 +30,8 @@ #import "rendering_context_driver_metal.h" +#import "rendering_device_driver_metal.h" + @protocol MTLDeviceEx <MTLDevice> #if TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED < 130300 - (void)setShouldMaximizeConcurrentCompilation:(BOOL)v; @@ -77,6 +79,103 @@ void RenderingContextDriverMetal::driver_free(RenderingDeviceDriver *p_driver) { memdelete(p_driver); } +class API_AVAILABLE(macos(11.0), ios(14.0)) SurfaceLayer : public RenderingContextDriverMetal::Surface { + CAMetalLayer *__unsafe_unretained layer = nil; + LocalVector<MDFrameBuffer> frame_buffers; + LocalVector<id<MTLDrawable>> drawables; + uint32_t rear = -1; + uint32_t front = 0; + uint32_t count = 0; + +public: + SurfaceLayer(CAMetalLayer *p_layer, id<MTLDevice> p_device) : + Surface(p_device), layer(p_layer) { + layer.allowsNextDrawableTimeout = YES; + layer.framebufferOnly = YES; + layer.opaque = OS::get_singleton()->is_layered_allowed() ? NO : YES; + layer.pixelFormat = get_pixel_format(); + layer.device = p_device; + } + + ~SurfaceLayer() override { + layer = nil; + } + + Error resize(uint32_t p_desired_framebuffer_count) override final { + if (width == 0 || height == 0) { + // Very likely the window is minimized, don't create a swap chain. + return ERR_SKIP; + } + + CGSize drawableSize = CGSizeMake(width, height); + CGSize current = layer.drawableSize; + if (!CGSizeEqualToSize(current, drawableSize)) { + layer.drawableSize = drawableSize; + } + + // Metal supports a maximum of 3 drawables. + p_desired_framebuffer_count = MIN(3U, p_desired_framebuffer_count); + layer.maximumDrawableCount = p_desired_framebuffer_count; + +#if TARGET_OS_OSX + // Display sync is only supported on macOS. + switch (vsync_mode) { + case DisplayServer::VSYNC_MAILBOX: + case DisplayServer::VSYNC_ADAPTIVE: + case DisplayServer::VSYNC_ENABLED: + layer.displaySyncEnabled = YES; + break; + case DisplayServer::VSYNC_DISABLED: + layer.displaySyncEnabled = NO; + break; + } +#endif + drawables.resize(p_desired_framebuffer_count); + frame_buffers.resize(p_desired_framebuffer_count); + for (uint32_t i = 0; i < p_desired_framebuffer_count; i++) { + // Reserve space for the drawable texture. + frame_buffers[i].textures.resize(1); + } + + return OK; + } + + RDD::FramebufferID acquire_next_frame_buffer() override final { + if (count == frame_buffers.size()) { + return RDD::FramebufferID(); + } + + rear = (rear + 1) % frame_buffers.size(); + count++; + + MDFrameBuffer &frame_buffer = frame_buffers[rear]; + frame_buffer.size = Size2i(width, height); + + id<CAMetalDrawable> drawable = layer.nextDrawable; + ERR_FAIL_NULL_V_MSG(drawable, RDD::FramebufferID(), "no drawable available"); + drawables[rear] = drawable; + frame_buffer.textures.write[0] = drawable.texture; + + return RDD::FramebufferID(&frame_buffer); + } + + void present(MDCommandBuffer *p_cmd_buffer) override final { + if (count == 0) { + return; + } + + // Release texture and drawable. + frame_buffers[front].textures.write[0] = nil; + id<MTLDrawable> drawable = drawables[front]; + drawables[front] = nil; + + count--; + front = (front + 1) % frame_buffers.size(); + + [p_cmd_buffer->get_command_buffer() presentDrawable:drawable]; + } +}; + RenderingContextDriver::SurfaceID RenderingContextDriverMetal::surface_create(const void *p_platform_data) { const WindowPlatformData *wpd = (const WindowPlatformData *)(p_platform_data); Surface *surface = memnew(SurfaceLayer(wpd->layer, metal_device)); diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index a4208829b7..489181a025 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -397,12 +397,18 @@ Error DirAccessUnix::rename(String p_path, String p_new_path) { } p_path = fix_path(p_path); + if (p_path.ends_with("/")) { + p_path = p_path.left(-1); + } if (p_new_path.is_relative_path()) { p_new_path = get_current_dir().path_join(p_new_path); } p_new_path = fix_path(p_new_path); + if (p_new_path.ends_with("/")) { + p_new_path = p_new_path.left(-1); + } return ::rename(p_path.utf8().get_data(), p_new_path.utf8().get_data()) == 0 ? OK : FAILED; } diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index ea8b42b2e4..32f2d7dd79 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -218,67 +218,13 @@ bool FileAccessUnix::eof_reached() const { return last_error == ERR_FILE_EOF; } -uint8_t FileAccessUnix::get_8() const { - ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use."); - uint8_t b; - if (fread(&b, 1, 1, f) == 0) { - check_errors(); - b = '\0'; - } - return b; -} - -uint16_t FileAccessUnix::get_16() const { - ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use."); - - uint16_t b = 0; - if (fread(&b, 1, 2, f) != 2) { - check_errors(); - } - - if (big_endian) { - b = BSWAP16(b); - } - - return b; -} - -uint32_t FileAccessUnix::get_32() const { - ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use."); - - uint32_t b = 0; - if (fread(&b, 1, 4, f) != 4) { - check_errors(); - } - - if (big_endian) { - b = BSWAP32(b); - } - - return b; -} - -uint64_t FileAccessUnix::get_64() const { - ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use."); - - uint64_t b = 0; - if (fread(&b, 1, 8, f) != 8) { - check_errors(); - } - - if (big_endian) { - b = BSWAP64(b); - } - - return b; -} - uint64_t FileAccessUnix::get_buffer(uint8_t *p_dst, uint64_t p_length) const { - ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_NULL_V_MSG(f, -1, "File must be opened before use."); + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); uint64_t read = fread(p_dst, 1, p_length, f); check_errors(); + return read; } @@ -308,41 +254,6 @@ void FileAccessUnix::flush() { fflush(f); } -void FileAccessUnix::store_8(uint8_t p_dest) { - ERR_FAIL_NULL_MSG(f, "File must be opened before use."); - ERR_FAIL_COND(fwrite(&p_dest, 1, 1, f) != 1); -} - -void FileAccessUnix::store_16(uint16_t p_dest) { - ERR_FAIL_NULL_MSG(f, "File must be opened before use."); - - if (big_endian) { - p_dest = BSWAP16(p_dest); - } - - ERR_FAIL_COND(fwrite(&p_dest, 1, 2, f) != 2); -} - -void FileAccessUnix::store_32(uint32_t p_dest) { - ERR_FAIL_NULL_MSG(f, "File must be opened before use."); - - if (big_endian) { - p_dest = BSWAP32(p_dest); - } - - ERR_FAIL_COND(fwrite(&p_dest, 1, 4, f) != 4); -} - -void FileAccessUnix::store_64(uint64_t p_dest) { - ERR_FAIL_NULL_MSG(f, "File must be opened before use."); - - if (big_endian) { - p_dest = BSWAP64(p_dest); - } - - ERR_FAIL_COND(fwrite(&p_dest, 1, 8, f) != 8); -} - void FileAccessUnix::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_NULL_MSG(f, "File must be opened before use."); ERR_FAIL_COND(!p_src && p_length > 0); diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h index c0286dbff3..76f629f7c2 100644 --- a/drivers/unix/file_access_unix.h +++ b/drivers/unix/file_access_unix.h @@ -67,20 +67,12 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte - virtual uint16_t get_16() const override; - virtual uint32_t get_32() const override; - virtual uint64_t get_64() const override; virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override; virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte - virtual void store_16(uint16_t p_dest) override; - virtual void store_32(uint32_t p_dest) override; - virtual void store_64(uint64_t p_dest) override; virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_path) override; ///< return true if a file exists diff --git a/drivers/unix/file_access_unix_pipe.cpp b/drivers/unix/file_access_unix_pipe.cpp index 5d9a27ad05..0a78429dec 100644 --- a/drivers/unix/file_access_unix_pipe.cpp +++ b/drivers/unix/file_access_unix_pipe.cpp @@ -41,7 +41,7 @@ #include <sys/types.h> #include <unistd.h> -Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) { +Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd, bool p_blocking) { // Open pipe using handles created by pipe(fd) call in the OS.execute_with_pipe. _close(); @@ -51,6 +51,11 @@ Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) { fd[0] = p_rfd; fd[1] = p_wfd; + if (!p_blocking) { + fcntl(fd[0], F_SETFL, fcntl(fd[0], F_GETFL) | O_NONBLOCK); + fcntl(fd[1], F_SETFL, fcntl(fd[1], F_GETFL) | O_NONBLOCK); + } + last_error = OK; return OK; } @@ -74,7 +79,7 @@ Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags) ERR_FAIL_COND_V_MSG(!S_ISFIFO(st.st_mode), ERR_ALREADY_IN_USE, "Pipe name is already used by file."); } - int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC); + int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC | O_NONBLOCK); if (f < 0) { switch (errno) { case ENOENT: { @@ -125,25 +130,15 @@ String FileAccessUnixPipe::get_path_absolute() const { return path_src; } -uint8_t FileAccessUnixPipe::get_8() const { - ERR_FAIL_COND_V_MSG(fd[0] < 0, 0, "Pipe must be opened before use."); - - uint8_t b; - if (::read(fd[0], &b, 1) == 0) { - last_error = ERR_FILE_CANT_READ; - b = '\0'; - } else { - last_error = OK; - } - return b; -} - uint64_t FileAccessUnixPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const { - ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V_MSG(fd[0] < 0, -1, "Pipe must be opened before use."); + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); - uint64_t read = ::read(fd[0], p_dst, p_length); - if (read == p_length) { + ssize_t read = ::read(fd[0], p_dst, p_length); + if (read == -1) { + last_error = ERR_FILE_CANT_READ; + read = 0; + } else if (read != (ssize_t)p_length) { last_error = ERR_FILE_CANT_READ; } else { last_error = OK; @@ -155,18 +150,10 @@ Error FileAccessUnixPipe::get_error() const { return last_error; } -void FileAccessUnixPipe::store_8(uint8_t p_src) { - ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use."); - if (::write(fd[1], &p_src, 1) != 1) { - last_error = ERR_FILE_CANT_WRITE; - } else { - last_error = OK; - } -} - void FileAccessUnixPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_COND_MSG(fd[1] < 0, "Pipe must be opened before use."); ERR_FAIL_COND(!p_src && p_length > 0); + if (::write(fd[1], p_src, p_length) != (ssize_t)p_length) { last_error = ERR_FILE_CANT_WRITE; } else { diff --git a/drivers/unix/file_access_unix_pipe.h b/drivers/unix/file_access_unix_pipe.h index 8e7988791b..1a4199f239 100644 --- a/drivers/unix/file_access_unix_pipe.h +++ b/drivers/unix/file_access_unix_pipe.h @@ -50,7 +50,7 @@ class FileAccessUnixPipe : public FileAccess { void _close(); public: - Error open_existing(int p_rfd, int p_wfd); + Error open_existing(int p_rfd, int p_wfd, bool p_blocking); virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file virtual bool is_open() const override; ///< true when file is open @@ -65,14 +65,12 @@ public: virtual bool eof_reached() const override { return false; } - virtual uint8_t get_8() const override; ///< get a byte virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override {} - virtual void store_8(uint8_t p_src) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_path) override { return false; } diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index ce2553456d..8a9b130068 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -493,7 +493,7 @@ Dictionary OS_Unix::get_memory_info() const { return meminfo; } -Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { +Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) { #define CLEAN_PIPES \ if (pipe_in[0] >= 0) { \ ::close(pipe_in[0]); \ @@ -578,11 +578,11 @@ Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> & Ref<FileAccessUnixPipe> main_pipe; main_pipe.instantiate(); - main_pipe->open_existing(pipe_out[0], pipe_in[1]); + main_pipe->open_existing(pipe_out[0], pipe_in[1], p_blocking); Ref<FileAccessUnixPipe> err_pipe; err_pipe.instantiate(); - err_pipe->open_existing(pipe_err[0], 0); + err_pipe->open_existing(pipe_err[0], 0, p_blocking); ProcessInfo pi; process_map_mutex.lock(); diff --git a/drivers/unix/os_unix.h b/drivers/unix/os_unix.h index df269a59d3..3add5df055 100644 --- a/drivers/unix/os_unix.h +++ b/drivers/unix/os_unix.h @@ -83,7 +83,7 @@ public: virtual Dictionary get_memory_info() const override; 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) override; - virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; + virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error kill(const ProcessID &p_pid) override; virtual int get_process_id() const override; diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 2ba353868b..4ea46e8214 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -497,6 +497,7 @@ Error RenderingDeviceDriverVulkan::_initialize_device_extensions() { _register_requested_device_extension(VK_KHR_MAINTENANCE_2_EXTENSION_NAME, false); _register_requested_device_extension(VK_EXT_PIPELINE_CREATION_CACHE_CONTROL_EXTENSION_NAME, false); _register_requested_device_extension(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME, false); + _register_requested_device_extension(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, false); if (Engine::get_singleton()->is_generate_spirv_debug_info_enabled()) { _register_requested_device_extension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, true); @@ -1705,6 +1706,16 @@ RDD::TextureID RenderingDeviceDriverVulkan::texture_create(const TextureFormat & image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; } + VkImageViewASTCDecodeModeEXT decode_mode; + if (enabled_device_extension_names.has(VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME)) { + if (image_view_create_info.format >= VK_FORMAT_ASTC_4x4_UNORM_BLOCK && image_view_create_info.format <= VK_FORMAT_ASTC_12x12_SRGB_BLOCK) { + decode_mode.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_ASTC_DECODE_MODE_EXT; + decode_mode.pNext = nullptr; + decode_mode.decodeMode = VK_FORMAT_R8G8B8A8_UNORM; + image_view_create_info.pNext = &decode_mode; + } + } + VkImageView vk_image_view = VK_NULL_HANDLE; err = vkCreateImageView(vk_device, &image_view_create_info, VKC::get_allocation_callbacks(VK_OBJECT_TYPE_IMAGE_VIEW), &vk_image_view); if (err) { diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index f6f721639c..0243d863f8 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -323,93 +323,9 @@ bool FileAccessWindows::eof_reached() const { return last_error == ERR_FILE_EOF; } -uint8_t FileAccessWindows::get_8() const { - ERR_FAIL_NULL_V(f, 0); - - if (flags == READ_WRITE || flags == WRITE_READ) { - if (prev_op == WRITE) { - fflush(f); - } - prev_op = READ; - } - uint8_t b; - if (fread(&b, 1, 1, f) == 0) { - check_errors(); - b = '\0'; - } - - return b; -} - -uint16_t FileAccessWindows::get_16() const { - ERR_FAIL_NULL_V(f, 0); - - if (flags == READ_WRITE || flags == WRITE_READ) { - if (prev_op == WRITE) { - fflush(f); - } - prev_op = READ; - } - - uint16_t b = 0; - if (fread(&b, 1, 2, f) != 2) { - check_errors(); - } - - if (big_endian) { - b = BSWAP16(b); - } - - return b; -} - -uint32_t FileAccessWindows::get_32() const { - ERR_FAIL_NULL_V(f, 0); - - if (flags == READ_WRITE || flags == WRITE_READ) { - if (prev_op == WRITE) { - fflush(f); - } - prev_op = READ; - } - - uint32_t b = 0; - if (fread(&b, 1, 4, f) != 4) { - check_errors(); - } - - if (big_endian) { - b = BSWAP32(b); - } - - return b; -} - -uint64_t FileAccessWindows::get_64() const { - ERR_FAIL_NULL_V(f, 0); - - if (flags == READ_WRITE || flags == WRITE_READ) { - if (prev_op == WRITE) { - fflush(f); - } - prev_op = READ; - } - - uint64_t b = 0; - if (fread(&b, 1, 8, f) != 8) { - check_errors(); - } - - if (big_endian) { - b = BSWAP64(b); - } - - return b; -} - uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const { - ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_NULL_V(f, -1); + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); if (flags == READ_WRITE || flags == WRITE_READ) { if (prev_op == WRITE) { @@ -417,8 +333,10 @@ uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const } prev_op = READ; } + uint64_t read = fread(p_dst, 1, p_length, f); check_errors(); + return read; } @@ -453,77 +371,6 @@ void FileAccessWindows::flush() { } } -void FileAccessWindows::store_8(uint8_t p_dest) { - ERR_FAIL_NULL(f); - - if (flags == READ_WRITE || flags == WRITE_READ) { - if (prev_op == READ) { - if (last_error != ERR_FILE_EOF) { - fseek(f, 0, SEEK_CUR); - } - } - prev_op = WRITE; - } - fwrite(&p_dest, 1, 1, f); -} - -void FileAccessWindows::store_16(uint16_t p_dest) { - ERR_FAIL_NULL(f); - - if (flags == READ_WRITE || flags == WRITE_READ) { - if (prev_op == READ) { - if (last_error != ERR_FILE_EOF) { - fseek(f, 0, SEEK_CUR); - } - } - prev_op = WRITE; - } - - if (big_endian) { - p_dest = BSWAP16(p_dest); - } - - fwrite(&p_dest, 1, 2, f); -} - -void FileAccessWindows::store_32(uint32_t p_dest) { - ERR_FAIL_NULL(f); - - if (flags == READ_WRITE || flags == WRITE_READ) { - if (prev_op == READ) { - if (last_error != ERR_FILE_EOF) { - fseek(f, 0, SEEK_CUR); - } - } - prev_op = WRITE; - } - - if (big_endian) { - p_dest = BSWAP32(p_dest); - } - - fwrite(&p_dest, 1, 4, f); -} - -void FileAccessWindows::store_64(uint64_t p_dest) { - ERR_FAIL_NULL(f); - - if (flags == READ_WRITE || flags == WRITE_READ) { - if (prev_op == READ) { - if (last_error != ERR_FILE_EOF) { - fseek(f, 0, SEEK_CUR); - } - } - prev_op = WRITE; - } - - if (big_endian) { - p_dest = BSWAP64(p_dest); - } - - fwrite(&p_dest, 1, 8, f); -} - void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_NULL(f); ERR_FAIL_COND(!p_src && p_length > 0); @@ -536,6 +383,7 @@ void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) { } prev_op = WRITE; } + ERR_FAIL_COND(fwrite(p_src, 1, p_length, f) != (size_t)p_length); } @@ -559,11 +407,10 @@ uint64_t FileAccessWindows::_get_modified_time(const String &p_file) { return 0; } - String file = p_file; - if (file.ends_with("/") && file != "/") { + String file = fix_path(p_file); + if (file.ends_with("\\") && file != "\\") { file = file.substr(0, file.length() - 1); } - file = fix_path(file); struct _stat st; int rv = _wstat((LPCWSTR)(file.utf16().get_data()), &st); diff --git a/drivers/windows/file_access_windows.h b/drivers/windows/file_access_windows.h index a25bbcfb3a..f458ff9c6c 100644 --- a/drivers/windows/file_access_windows.h +++ b/drivers/windows/file_access_windows.h @@ -69,20 +69,12 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF - virtual uint8_t get_8() const override; ///< get a byte - virtual uint16_t get_16() const override; - virtual uint32_t get_32() const override; - virtual uint64_t get_64() const override; virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override; virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte - virtual void store_16(uint16_t p_dest) override; - virtual void store_32(uint32_t p_dest) override; - virtual void store_64(uint64_t p_dest) override; virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/drivers/windows/file_access_windows_pipe.cpp b/drivers/windows/file_access_windows_pipe.cpp index 7902c8e1d8..9bf0f4d852 100644 --- a/drivers/windows/file_access_windows_pipe.cpp +++ b/drivers/windows/file_access_windows_pipe.cpp @@ -35,7 +35,7 @@ #include "core/os/os.h" #include "core/string/print_string.h" -Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) { +Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd, bool p_blocking) { // Open pipe using handles created by CreatePipe(rfd, wfd, NULL, 4096) call in the OS.execute_with_pipe. _close(); @@ -44,6 +44,12 @@ Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) { fd[0] = p_rfd; fd[1] = p_wfd; + if (!p_blocking) { + DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT; + SetNamedPipeHandleState(fd[0], &mode, nullptr, nullptr); + SetNamedPipeHandleState(fd[1], &mode, nullptr, nullptr); + } + last_error = OK; return OK; } @@ -58,7 +64,7 @@ Error FileAccessWindowsPipe::open_internal(const String &p_path, int p_mode_flag HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (h == INVALID_HANDLE_VALUE) { - h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, nullptr); + h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT, 1, 4096, 4096, 0, nullptr); if (h == INVALID_HANDLE_VALUE) { last_error = ERR_FILE_CANT_OPEN; return last_error; @@ -96,24 +102,11 @@ String FileAccessWindowsPipe::get_path_absolute() const { return path_src; } -uint8_t FileAccessWindowsPipe::get_8() const { - ERR_FAIL_COND_V_MSG(fd[0] == 0, 0, "Pipe must be opened before use."); - - uint8_t b; - if (!ReadFile(fd[0], &b, 1, nullptr, nullptr)) { - last_error = ERR_FILE_CANT_READ; - b = '\0'; - } else { - last_error = OK; - } - return b; -} - uint64_t FileAccessWindowsPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const { - ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V_MSG(fd[0] == 0, -1, "Pipe must be opened before use."); + ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); - DWORD read = -1; + DWORD read = 0; if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) { last_error = ERR_FILE_CANT_READ; } else { @@ -126,15 +119,6 @@ Error FileAccessWindowsPipe::get_error() const { return last_error; } -void FileAccessWindowsPipe::store_8(uint8_t p_src) { - ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use."); - if (!WriteFile(fd[1], &p_src, 1, nullptr, nullptr)) { - last_error = ERR_FILE_CANT_WRITE; - } else { - last_error = OK; - } -} - void FileAccessWindowsPipe::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_COND_MSG(fd[1] == 0, "Pipe must be opened before use."); ERR_FAIL_COND(!p_src && p_length > 0); diff --git a/drivers/windows/file_access_windows_pipe.h b/drivers/windows/file_access_windows_pipe.h index b885ef78e6..1eb3c6ef2f 100644 --- a/drivers/windows/file_access_windows_pipe.h +++ b/drivers/windows/file_access_windows_pipe.h @@ -49,7 +49,7 @@ class FileAccessWindowsPipe : public FileAccess { void _close(); public: - Error open_existing(HANDLE p_rfd, HANDLE p_wfd); + Error open_existing(HANDLE p_rfd, HANDLE p_wfd, bool p_blocking); virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file virtual bool is_open() const override; ///< true when file is open @@ -64,14 +64,12 @@ public: virtual bool eof_reached() const override { return false; } - virtual uint8_t get_8() const override; ///< get a byte virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } virtual void flush() override {} - virtual void store_8(uint8_t p_src) override; ///< store a byte virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_name) override { return false; } diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 5196857240..24fcfd3930 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -174,7 +174,7 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) { } if (lines.size() >= 2) { - draw_multiline(lines, p_color, Math::round(EDSCALE)); + draw_multiline(lines, p_color, Math::round(EDSCALE), true); } } } @@ -208,7 +208,7 @@ void AnimationBezierTrackEdit::_draw_line_clipped(const Vector2 &p_from, const V from = from.lerp(to, c); } - draw_line(from, to, p_color, Math::round(EDSCALE)); + draw_line(from, to, p_color, Math::round(EDSCALE), true); } void AnimationBezierTrackEdit::_notification(int p_what) { @@ -719,7 +719,7 @@ void AnimationBezierTrackEdit::set_root(Node *p_root) { void AnimationBezierTrackEdit::set_filtered(bool p_filtered) { is_filtered = p_filtered; - if (animation == nullptr) { + if (animation.is_null()) { return; } String base_path = animation->track_get_path(selected_track); @@ -1660,7 +1660,7 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) { switch (p_index) { case MENU_KEY_INSERT: { if (animation->get_track_count() > 0) { - if (editor->snap->is_pressed() && editor->step->get_value() != 0) { + if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) { time = editor->snap_time(time); } while (animation->track_find_key(selected_track, time, Animation::FIND_MODE_APPROX) != -1) { @@ -1736,7 +1736,7 @@ void AnimationBezierTrackEdit::duplicate_selected_keys(real_t p_ofs, bool p_ofs_ real_t insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position(); if (p_ofs_valid) { - if (editor->snap->is_pressed() && editor->step->get_value() != 0) { + if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) { insert_pos = editor->snap_time(insert_pos); } } @@ -1859,7 +1859,7 @@ void AnimationBezierTrackEdit::paste_keys(real_t p_ofs, bool p_ofs_valid) { float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position(); if (p_ofs_valid) { - if (editor->snap->is_pressed() && editor->step->get_value() != 0) { + if (editor->snap_keys->is_pressed() && editor->step->get_value() != 0) { insert_pos = editor->snap_time(insert_pos); } } diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 8c07cefc19..95ba301282 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -1553,14 +1553,18 @@ void AnimationTimelineEdit::_notification(int p_what) { max_digit_width = MAX(digit_width, max_digit_width); } const int max_sc = int(Math::ceil(zoomw / scale)); - const int max_sc_width = String::num(max_sc).length() * max_digit_width; + const int max_sc_width = String::num(max_sc).length() * Math::ceil(max_digit_width); + + const int min_margin = MAX(text_secondary_margin, text_primary_margin); while (!step_found) { int min = max_sc_width; if (decimals > 0) { - min += period_width + max_digit_width * decimals; + min += Math::ceil(period_width + max_digit_width * decimals); } + min += (min_margin * 2); + static const int _multp[3] = { 1, 2, 5 }; for (int i = 0; i < 3; i++) { step = (_multp[i] * dec); @@ -1616,10 +1620,11 @@ void AnimationTimelineEdit::_notification(int p_what) { int sc = int(Math::floor(pos * SC_ADJ)); int prev_sc = int(Math::floor(prev * SC_ADJ)); - bool sub = (sc % SC_ADJ); if ((sc / step) != (prev_sc / step) || (prev_sc < 0 && sc >= 0)) { int scd = sc < 0 ? prev_sc : sc; + bool sub = (((scd - (scd % step)) % (dec * 10)) != 0); + int line_margin = sub ? v_line_secondary_margin : v_line_primary_margin; int line_width = sub ? v_line_secondary_width : v_line_primary_width; Color line_color = sub ? v_line_secondary_color : v_line_primary_color; @@ -3610,7 +3615,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re _update_step_spinbox(); step->set_block_signals(false); step->set_read_only(false); - snap->set_disabled(false); + snap_keys->set_disabled(false); + snap_timeline->set_disabled(false); snap_mode->set_disabled(false); auto_fit->set_disabled(false); auto_fit_bezier->set_disabled(false); @@ -3631,7 +3637,8 @@ void AnimationTrackEditor::set_animation(const Ref<Animation> &p_anim, bool p_re step->set_value(0); step->set_block_signals(false); step->set_read_only(true); - snap->set_disabled(true); + snap_keys->set_disabled(true); + snap_timeline->set_disabled(true); snap_mode->set_disabled(true); bezier_edit_icon->set_disabled(true); auto_fit->set_disabled(true); @@ -4550,8 +4557,16 @@ bool AnimationTrackEditor::is_key_clipboard_active() const { return key_clipboard.keys.size(); } -bool AnimationTrackEditor::is_snap_enabled() const { - return snap->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL); +bool AnimationTrackEditor::is_snap_timeline_enabled() const { + return snap_timeline->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL); +} + +bool AnimationTrackEditor::is_snap_keys_enabled() const { + return snap_keys->is_pressed() ^ Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL); +} + +bool AnimationTrackEditor::is_bezier_editor_active() const { + return bezier_edit->is_visible(); } bool AnimationTrackEditor::can_add_reset_key() const { @@ -4887,7 +4902,8 @@ void AnimationTrackEditor::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: { zoom_icon->set_texture(get_editor_theme_icon(SNAME("Zoom"))); bezier_edit_icon->set_icon(get_editor_theme_icon(SNAME("EditBezier"))); - snap->set_icon(get_editor_theme_icon(SNAME("Snap"))); + snap_timeline->set_icon(get_editor_theme_icon(SNAME("SnapTimeline"))); + snap_keys->set_icon(get_editor_theme_icon(SNAME("SnapKeys"))); view_group->set_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup"))); selected_filter->set_icon(get_editor_theme_icon(SNAME("AnimationFilter"))); imported_anim_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning"))); @@ -5208,7 +5224,7 @@ int AnimationTrackEditor::_get_track_selected() { void AnimationTrackEditor::_insert_key_from_track(float p_ofs, int p_track) { ERR_FAIL_INDEX(p_track, animation->get_track_count()); - if (snap->is_pressed() && step->get_value() != 0) { + if (snap_keys->is_pressed() && step->get_value() != 0) { p_ofs = snap_time(p_ofs); } while (animation->track_find_key(p_track, p_ofs, Animation::FIND_MODE_APPROX) != -1) { // Make sure insertion point is valid. @@ -5831,7 +5847,7 @@ void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, i float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position(); if (p_ofs_valid) { - if (snap->is_pressed() && step->get_value() != 0) { + if (snap_keys->is_pressed() && step->get_value() != 0) { insert_pos = snap_time(insert_pos); } } @@ -5979,7 +5995,7 @@ void AnimationTrackEditor::_anim_paste_keys(float p_ofs, bool p_ofs_valid, int p float insert_pos = p_ofs_valid ? p_ofs : timeline->get_play_position(); if (p_ofs_valid) { - if (snap->is_pressed() && step->get_value() != 0) { + if (snap_keys->is_pressed() && step->get_value() != 0) { insert_pos = snap_time(insert_pos); } } @@ -7060,7 +7076,7 @@ void AnimationTrackEditor::_update_snap_unit() { } float AnimationTrackEditor::snap_time(float p_value, bool p_relative) { - if (is_snap_enabled()) { + if (is_snap_keys_enabled()) { if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { // Use more precise snapping when holding Shift. snap_unit *= 0.25; @@ -7314,13 +7330,21 @@ AnimationTrackEditor::AnimationTrackEditor() { bottom_hb->add_child(view_group); bottom_hb->add_child(memnew(VSeparator)); - snap = memnew(Button); - snap->set_flat(true); - snap->set_text(TTR("Snap:") + " "); - bottom_hb->add_child(snap); - snap->set_disabled(true); - snap->set_toggle_mode(true); - snap->set_pressed(true); + snap_timeline = memnew(Button); + snap_timeline->set_flat(true); + bottom_hb->add_child(snap_timeline); + snap_timeline->set_disabled(true); + snap_timeline->set_toggle_mode(true); + snap_timeline->set_pressed(false); + snap_timeline->set_tooltip_text(TTR("Apply snapping to timeline cursor.")); + + snap_keys = memnew(Button); + snap_keys->set_flat(true); + bottom_hb->add_child(snap_keys); + snap_keys->set_disabled(true); + snap_keys->set_toggle_mode(true); + snap_keys->set_pressed(true); + snap_keys->set_tooltip_text(TTR("Apply snapping to selected key(s).")); step = memnew(EditorSpinSlider); step->set_min(0); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index a517ba8b43..8a263d7d20 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -408,7 +408,8 @@ class AnimationTrackEditor : public VBoxContainer { HSlider *zoom = nullptr; EditorSpinSlider *step = nullptr; TextureRect *zoom_icon = nullptr; - Button *snap = nullptr; + Button *snap_keys = nullptr; + Button *snap_timeline = nullptr; Button *bezier_edit_icon = nullptr; OptionButton *snap_mode = nullptr; Button *auto_fit = nullptr; @@ -728,7 +729,9 @@ public: bool is_selection_active() const; bool is_key_clipboard_active() const; bool is_moving_selection() const; - bool is_snap_enabled() const; + bool is_snap_timeline_enabled() const; + bool is_snap_keys_enabled() const; + bool is_bezier_editor_active() const; bool can_add_reset_key() const; float get_moving_selection_offset() const; float snap_time(float p_value, bool p_relative = false); diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index eb0ab1174b..5273b61f37 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -486,11 +486,6 @@ void ConnectDialog::_notification(int p_what) { type_list->set_item_icon(i, get_editor_theme_icon(type_name)); } - Ref<StyleBox> style = get_theme_stylebox(CoreStringName(normal), "LineEdit")->duplicate(); - if (style.is_valid()) { - style->set_content_margin(SIDE_TOP, style->get_content_margin(SIDE_TOP) + 1.0); - from_signal->add_theme_style_override(CoreStringName(normal), style); - } method_search->set_right_icon(get_editor_theme_icon("Search")); open_method_tree->set_icon(get_editor_theme_icon("Edit")); } break; @@ -576,6 +571,22 @@ String ConnectDialog::get_signature(const MethodInfo &p_method, PackedStringArra type_name = "Array"; } break; + case Variant::DICTIONARY: + type_name = "Dictionary"; + if (pi.hint == PROPERTY_HINT_DICTIONARY_TYPE && !pi.hint_string.is_empty()) { + String key_hint = pi.hint_string.get_slice(";", 0); + String value_hint = pi.hint_string.get_slice(";", 1); + if (key_hint.is_empty() || key_hint.begins_with("res://")) { + key_hint = "Variant"; + } + if (value_hint.is_empty() || value_hint.begins_with("res://")) { + value_hint = "Variant"; + } + if (key_hint != "Variant" || value_hint != "Variant") { + type_name += "[" + key_hint + ", " + value_hint + "]"; + } + } + break; case Variant::OBJECT: if (pi.class_name != StringName()) { type_name = pi.class_name; diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp index 816bd6b72c..cb5e4375a6 100644 --- a/editor/debugger/editor_debugger_inspector.cpp +++ b/editor/debugger/editor_debugger_inspector.cpp @@ -151,7 +151,7 @@ ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) { Variant &var = property.second; if (pinfo.type == Variant::OBJECT) { - if (var.get_type() == Variant::STRING) { + if (var.is_string()) { String path = var; if (path.contains("::")) { // built-in resource diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp index f8ffce0c83..c4d7899b2d 100644 --- a/editor/debugger/editor_debugger_tree.cpp +++ b/editor/debugger/editor_debugger_tree.cpp @@ -124,6 +124,7 @@ void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, Mou item_menu->clear(); item_menu->add_icon_item(get_editor_theme_icon(SNAME("CreateNewSceneFrom")), TTR("Save Branch as Scene"), ITEM_MENU_SAVE_REMOTE_NODE); item_menu->add_icon_item(get_editor_theme_icon(SNAME("CopyNodePath")), TTR("Copy Node Path"), ITEM_MENU_COPY_NODE_PATH); + item_menu->add_icon_item(get_editor_theme_icon(SNAME("Collapse")), TTR("Expand/Collapse Branch"), ITEM_MENU_EXPAND_COLLAPSE); item_menu->set_position(get_screen_position() + get_local_mouse_position()); item_menu->reset_size(); item_menu->popup(); @@ -359,6 +360,21 @@ void EditorDebuggerTree::_item_menu_id_pressed(int p_option) { } DisplayServer::get_singleton()->clipboard_set(text); } break; + case ITEM_MENU_EXPAND_COLLAPSE: { + TreeItem *s_item = get_selected(); + + if (!s_item) { + s_item = get_root(); + if (!s_item) { + break; + } + } + + bool collapsed = s_item->is_any_collapsed(); + s_item->set_collapsed_recursive(!collapsed); + + ensure_cursor_is_visible(); + } } } diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h index dbffb0f219..705df17baf 100644 --- a/editor/debugger/editor_debugger_tree.h +++ b/editor/debugger/editor_debugger_tree.h @@ -43,6 +43,7 @@ private: enum ItemMenu { ITEM_MENU_SAVE_REMOTE_NODE, ITEM_MENU_COPY_NODE_PATH, + ITEM_MENU_EXPAND_COLLAPSE, }; ObjectID inspected_object_id; diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index 17977fcb29..56c9db44cd 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -103,6 +103,8 @@ void EditorVisualProfiler::clear() { variables->clear(); //activate->set_pressed(false); + graph_limit = 1000.0f / CLAMP(int(EDITOR_GET("debugger/profiler_target_fps")), 1, 1000); + updating_frame = true; cursor_metric_edit->set_min(0); cursor_metric_edit->set_max(0); @@ -812,6 +814,8 @@ EditorVisualProfiler::EditorVisualProfiler() { int metric_size = CLAMP(int(EDITOR_GET("debugger/profiler_frame_history_size")), 60, 10000); frame_metrics.resize(metric_size); + graph_limit = 1000.0f / CLAMP(int(EDITOR_GET("debugger/profiler_target_fps")), 1, 1000); + frame_delay = memnew(Timer); frame_delay->set_wait_time(0.1); frame_delay->set_one_shot(true); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index bf5b717c19..79e0c7ebd1 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -277,6 +277,10 @@ static void merge_theme_properties(Vector<DocData::ThemeItemDoc> &p_to, const Ve // Check found entry on name and data type. if (to.name == from.name && to.data_type == from.data_type) { to.description = from.description; + to.is_deprecated = from.is_deprecated; + to.deprecated_message = from.deprecated_message; + to.is_experimental = from.is_experimental; + to.experimental_message = from.experimental_message; to.keywords = from.keywords; } } @@ -571,6 +575,8 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) { prop.type = retinfo.class_name; } else if (retinfo.type == Variant::ARRAY && retinfo.hint == PROPERTY_HINT_ARRAY_TYPE) { prop.type = retinfo.hint_string + "[]"; + } else if (retinfo.type == Variant::DICTIONARY && retinfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + prop.type = "Dictionary[" + retinfo.hint_string.replace(";", ", ") + "]"; } else if (retinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { prop.type = retinfo.hint_string; } else if (retinfo.type == Variant::NIL && retinfo.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { @@ -1420,6 +1426,14 @@ Error DocTools::_load(Ref<XMLParser> parser) { prop2.type = parser->get_named_attribute_value("type"); ERR_FAIL_COND_V(!parser->has_attribute("data_type"), ERR_FILE_CORRUPT); prop2.data_type = parser->get_named_attribute_value("data_type"); + if (parser->has_attribute("deprecated")) { + prop2.is_deprecated = true; + prop2.deprecated_message = parser->get_named_attribute_value("deprecated"); + } + if (parser->has_attribute("experimental")) { + prop2.is_experimental = true; + prop2.experimental_message = parser->get_named_attribute_value("experimental"); + } if (parser->has_attribute("keywords")) { prop2.keywords = parser->get_named_attribute_value("keywords"); } @@ -1738,6 +1752,12 @@ Error DocTools::save_classes(const String &p_default_path, const HashMap<String, if (!ti.default_value.is_empty()) { additional_attributes += String(" default=\"") + ti.default_value.xml_escape(true) + "\""; } + if (ti.is_deprecated) { + additional_attributes += " deprecated=\"" + ti.deprecated_message.xml_escape(true) + "\""; + } + if (ti.is_experimental) { + additional_attributes += " experimental=\"" + ti.experimental_message.xml_escape(true) + "\""; + } if (!ti.keywords.is_empty()) { additional_attributes += String(" keywords=\"") + ti.keywords.xml_escape(true) + "\""; } diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp index 42726a8b12..9c38d55337 100644 --- a/editor/editor_build_profile.cpp +++ b/editor/editor_build_profile.cpp @@ -649,7 +649,7 @@ void EditorBuildProfileManager::_class_list_item_selected() { } Variant md = item->get_metadata(0); - if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + if (md.is_string()) { description_bit->parse_symbol("class|" + md.operator String() + "|"); } else if (md.get_type() == Variant::INT) { String build_option_description = EditorBuildProfile::get_build_option_description(EditorBuildProfile::BuildOption((int)md)); @@ -670,7 +670,7 @@ void EditorBuildProfileManager::_class_list_item_edited() { bool checked = item->is_checked(0); Variant md = item->get_metadata(0); - if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + if (md.is_string()) { String class_selected = md; edited->set_disable_class(class_selected, !checked); _update_edited_profile(); @@ -691,7 +691,7 @@ void EditorBuildProfileManager::_class_list_item_collapsed(Object *p_item) { } Variant md = item->get_metadata(0); - if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) { + if (!md.is_string()) { return; } @@ -706,7 +706,7 @@ void EditorBuildProfileManager::_update_edited_profile() { if (class_list->get_selected()) { Variant md = class_list->get_selected()->get_metadata(0); - if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + if (md.is_string()) { class_selected = md; } else if (md.get_type() == Variant::INT) { build_option_selected = md; diff --git a/editor/editor_data.cpp b/editor/editor_data.cpp index 80c4c49c87..e5caa6a352 100644 --- a/editor/editor_data.cpp +++ b/editor/editor_data.cpp @@ -36,11 +36,14 @@ #include "core/io/image_loader.h" #include "core/io/resource_loader.h" #include "editor/editor_node.h" +#include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" #include "editor/multi_node_edit.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/themes/editor_scale.h" +#include "scene/gui/popup_menu.h" #include "scene/resources/packed_scene.h" void EditorSelectionHistory::cleanup_history() { @@ -519,6 +522,138 @@ EditorPlugin *EditorData::get_extension_editor_plugin(const StringName &p_class_ return plugin == nullptr ? nullptr : *plugin; } +void EditorData::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { + ContextMenu cm; + cm.p_slot = p_slot; + cm.plugin = p_plugin; + p_plugin->start_idx = context_menu_plugins.size() * EditorContextMenuPlugin::MAX_ITEMS; + context_menu_plugins.push_back(cm); +} + +void EditorData::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { + for (int i = context_menu_plugins.size() - 1; i > -1; i--) { + if (context_menu_plugins[i].p_slot == p_slot && context_menu_plugins[i].plugin == p_plugin) { + context_menu_plugins.remove_at(i); + } + } +} + +int EditorData::match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event) { + for (ContextMenu &cm : context_menu_plugins) { + if (cm.p_slot != p_slot) { + continue; + } + HashMap<Ref<Shortcut>, Callable> &cms = cm.plugin->context_menu_shortcuts; + int shortcut_idx = 0; + for (KeyValue<Ref<Shortcut>, Callable> &E : cms) { + const Ref<Shortcut> &p_shortcut = E.key; + if (p_shortcut->matches_event(p_event)) { + return EditorData::CONTEXT_MENU_ITEM_ID_BASE + cm.plugin->start_idx + shortcut_idx; + } + shortcut_idx++; + } + } + return 0; +} + +void EditorData::add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths) { + bool add_separator = false; + + for (ContextMenu &cm : context_menu_plugins) { + if (cm.p_slot != p_slot) { + continue; + } + cm.plugin->clear_context_menu_items(); + cm.plugin->add_options(p_paths); + HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = cm.plugin->context_menu_items; + if (items.size() > 0 && !add_separator) { + add_separator = true; + p_popup->add_separator(); + } + for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) { + EditorContextMenuPlugin::ContextMenuItem &item = E.value; + + if (item.icon.is_valid()) { + p_popup->add_icon_item(item.icon, item.item_name, item.idx); + const int icon_size = p_popup->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)); + p_popup->set_item_icon_max_width(-1, icon_size); + } else { + p_popup->add_item(item.item_name, item.idx); + } + if (item.shortcut.is_valid()) { + p_popup->set_item_shortcut(-1, item.shortcut, true); + } + } + } +} + +template <typename T> +void EditorData::invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg) { + Variant arg = p_arg; + Variant *argptr = &arg; + + for (int i = 0; i < context_menu_plugins.size(); i++) { + if (context_menu_plugins[i].p_slot != p_slot || context_menu_plugins[i].plugin.is_null()) { + continue; + } + Ref<EditorContextMenuPlugin> plugin = context_menu_plugins[i].plugin; + + // Shortcut callback. + int shortcut_idx = 0; + int shortcut_base_idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + plugin->start_idx; + for (KeyValue<Ref<Shortcut>, Callable> &E : plugin->context_menu_shortcuts) { + if (shortcut_base_idx + shortcut_idx == p_option) { + const Callable &callable = E.value; + Callable::CallError ce; + Variant result; + callable.callp((const Variant **)&argptr, 1, result, ce); + } + shortcut_idx++; + } + if (p_option < shortcut_base_idx + shortcut_idx) { + return; + } + + HashMap<String, EditorContextMenuPlugin::ContextMenuItem> &items = plugin->context_menu_items; + for (KeyValue<String, EditorContextMenuPlugin::ContextMenuItem> &E : items) { + EditorContextMenuPlugin::ContextMenuItem &item = E.value; + + if (p_option != item.idx || !item.callable.is_valid()) { + continue; + } + + Callable::CallError ce; + Variant result; + item.callable.callp((const Variant **)&argptr, 1, result, ce); + + if (ce.error != Callable::CallError::CALL_OK) { + String err = Variant::get_callable_error_text(item.callable, nullptr, 0, ce); + ERR_PRINT("Error calling function from context menu: " + err); + } + } + } + // Invoke submenu items. + if (p_slot == CONTEXT_SLOT_FILESYSTEM) { + invoke_plugin_callback(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_option, p_arg); + } +} + +void EditorData::filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected) { + invoke_plugin_callback(p_slot, p_option, p_selected); +} + +void EditorData::scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected) { + TypedArray<Node> nodes; + for (Node *selected : p_selected) { + nodes.append(selected); + } + invoke_plugin_callback(p_slot, p_option, nodes); +} + +void EditorData::script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script) { + invoke_plugin_callback(p_slot, p_option, p_script); +} + void EditorData::add_custom_type(const String &p_type, const String &p_inherits, const Ref<Script> &p_script, const Ref<Texture2D> &p_icon) { ERR_FAIL_COND_MSG(p_script.is_null(), "It's not a reference to a valid Script object."); CustomType ct; diff --git a/editor/editor_data.h b/editor/editor_data.h index 524c93807b..754d44c479 100644 --- a/editor/editor_data.h +++ b/editor/editor_data.h @@ -37,6 +37,8 @@ class ConfigFile; class EditorPlugin; class EditorUndoRedoManager; +class EditorContextMenuPlugin; +class PopupMenu; /** * Stores the history of objects which have been selected for editing in the Editor & the Inspector. @@ -123,6 +125,22 @@ public: uint64_t last_checked_version = 0; }; + enum ContextMenuSlot { + CONTEXT_SLOT_SCENE_TREE, + CONTEXT_SLOT_FILESYSTEM, + CONTEXT_SLOT_SCRIPT_EDITOR, + CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, + }; + + inline static constexpr int CONTEXT_MENU_ITEM_ID_BASE = 1000; + + struct ContextMenu { + int p_slot; + Ref<EditorContextMenuPlugin> plugin; + }; + + Vector<ContextMenu> context_menu_plugins; + private: Vector<EditorPlugin *> editor_plugins; HashMap<StringName, EditorPlugin *> extension_editor_plugins; @@ -177,6 +195,18 @@ public: bool has_extension_editor_plugin(const StringName &p_class_name); EditorPlugin *get_extension_editor_plugin(const StringName &p_class_name); + // Context menu plugin. + void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); + void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); + int match_context_menu_shortcut(ContextMenuSlot p_slot, const Ref<InputEvent> &p_event); + + void add_options_from_plugins(PopupMenu *p_popup, ContextMenuSlot p_slot, const Vector<String> &p_paths); + void filesystem_options_pressed(ContextMenuSlot p_slot, int p_option, const Vector<String> &p_selected); + void scene_tree_options_pressed(ContextMenuSlot p_slot, int p_option, const List<Node *> &p_selected); + void script_editor_options_pressed(ContextMenuSlot p_slot, int p_option, const Ref<Resource> &p_script); + template <typename T> + void invoke_plugin_callback(ContextMenuSlot p_slot, int p_option, const T &p_arg); + void add_undo_redo_inspector_hook_callback(Callable p_callable); // Callbacks should have this signature: void (Object* undo_redo, Object *modified_object, String property, Variant new_value) void remove_undo_redo_inspector_hook_callback(Callable p_callable); const Vector<Callable> get_undo_redo_inspector_hook_callback(); diff --git a/editor/editor_dock_manager.cpp b/editor/editor_dock_manager.cpp index 75135532aa..0bdda41f26 100644 --- a/editor/editor_dock_manager.cpp +++ b/editor/editor_dock_manager.cpp @@ -509,7 +509,7 @@ void EditorDockManager::save_docks_to_config(Ref<ConfigFile> p_layout, const Str } for (int i = 0; i < hsplits.size(); i++) { - p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), hsplits[i]->get_split_offset()); + p_layout->set_value(p_section, "dock_hsplit_" + itos(i + 1), int(hsplits[i]->get_split_offset() / EDSCALE)); } FileSystemDock::get_singleton()->save_layout_to_config(p_layout, p_section); @@ -605,7 +605,7 @@ void EditorDockManager::load_docks_from_config(Ref<ConfigFile> p_layout, const S continue; } int ofs = p_layout->get_value(p_section, "dock_hsplit_" + itos(i + 1)); - hsplits[i]->set_split_offset(ofs); + hsplits[i]->set_split_offset(ofs * EDSCALE); } FileSystemDock::get_singleton()->load_layout_from_config(p_layout, p_section); diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp index 37cd74d2ac..44fc9e3702 100644 --- a/editor/editor_feature_profile.cpp +++ b/editor/editor_feature_profile.cpp @@ -558,7 +558,7 @@ void EditorFeatureProfileManager::_class_list_item_selected() { } Variant md = item->get_metadata(0); - if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + if (md.is_string()) { description_bit->parse_symbol("class|" + md.operator String() + "|"); } else if (md.get_type() == Variant::INT) { String feature_description = EditorFeatureProfile::get_feature_description(EditorFeatureProfile::Feature((int)md)); @@ -643,7 +643,7 @@ void EditorFeatureProfileManager::_class_list_item_edited() { bool checked = item->is_checked(0); Variant md = item->get_metadata(0); - if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + if (md.is_string()) { String class_selected = md; edited->set_disable_class(class_selected, !checked); _save_and_update(); @@ -666,7 +666,7 @@ void EditorFeatureProfileManager::_class_list_item_collapsed(Object *p_item) { } Variant md = item->get_metadata(0); - if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) { + if (!md.is_string()) { return; } @@ -686,7 +686,7 @@ void EditorFeatureProfileManager::_property_item_edited() { } Variant md = class_item->get_metadata(0); - if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) { + if (!md.is_string()) { return; } @@ -699,7 +699,7 @@ void EditorFeatureProfileManager::_property_item_edited() { bool checked = item->is_checked(0); md = item->get_metadata(0); - if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + if (md.is_string()) { String property_selected = md; edited->set_disable_class_property(class_name, property_selected, !checked); _save_and_update(); @@ -732,7 +732,7 @@ void EditorFeatureProfileManager::_update_selected_profile() { if (class_list->get_selected()) { Variant md = class_list->get_selected()->get_metadata(0); - if (md.get_type() == Variant::STRING || md.get_type() == Variant::STRING_NAME) { + if (md.is_string()) { class_selected = md; } else if (md.get_type() == Variant::INT) { feature_selected = md; diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index bae9062ff1..b1b64b5d60 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -33,8 +33,6 @@ #include "core/config/project_settings.h" #include "core/extension/gdextension_manager.h" #include "core/io/file_access.h" -#include "core/io/resource_importer.h" -#include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/object/worker_thread_pool.h" #include "core/os/os.h" @@ -412,11 +410,13 @@ void EditorFileSystem::_scan_filesystem() { new_filesystem->parent = nullptr; ScannedDirectory *sd; + HashSet<String> *processed_files = nullptr; // On the first scan, the first_scan_root_dir is created in _first_scan_filesystem. if (first_scan) { sd = first_scan_root_dir; // Will be updated on scan. ResourceUID::get_singleton()->clear(); + processed_files = memnew(HashSet<String>()); } else { Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); sd = memnew(ScannedDirectory); @@ -424,14 +424,18 @@ void EditorFileSystem::_scan_filesystem() { nb_files_total = _scan_new_dir(sd, d); } - _process_file_system(sd, new_filesystem, sp); + _process_file_system(sd, new_filesystem, sp, processed_files); + if (first_scan) { + _process_removed_files(*processed_files); + } dep_update_list.clear(); file_cache.clear(); //clear caches, no longer needed if (first_scan) { memdelete(first_scan_root_dir); first_scan_root_dir = nullptr; + memdelete(processed_files); } else { //on the first scan this is done from the main thread after re-importing _save_filesystem_cache(); @@ -903,6 +907,7 @@ void EditorFileSystem::scan() { // Set first_scan to false before the signals so the function doing_first_scan can return false // in editor_node to start the export if needed. first_scan = false; + ResourceImporter::load_on_startup = nullptr; emit_signal(SNAME("filesystem_changed")); emit_signal(SNAME("sources_changed"), sources_changed.size() > 0); } else { @@ -991,7 +996,7 @@ int EditorFileSystem::_scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da) return nb_files_total_scan; } -void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) { +void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, HashSet<String> *r_processed_files) { p_dir->modified_time = FileAccess::get_modified_time(p_scan_dir->full_path); for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) { @@ -999,7 +1004,7 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, sub_dir->parent = p_dir; sub_dir->name = scan_sub_dir->name; p_dir->subdirs.push_back(sub_dir); - _process_file_system(scan_sub_dir, sub_dir, p_progress); + _process_file_system(scan_sub_dir, sub_dir, p_progress, r_processed_files); } for (const String &scan_file : p_scan_dir->files) { @@ -1015,6 +1020,10 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, fi->file = scan_file; p_dir->files.push_back(fi); + if (r_processed_files) { + r_processed_files->insert(path); + } + FileCache *fc = file_cache.getptr(path); uint64_t mt = FileAccess::get_modified_time(path); @@ -1066,7 +1075,7 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); fi->modified_time = 0; fi->import_modified_time = 0; - fi->import_valid = fi->type == "TextFile" ? true : ResourceLoader::is_import_valid(path); + fi->import_valid = (fi->type == "TextFile" || fi->type == "OtherFile") ? true : ResourceLoader::is_import_valid(path); ItemAction ia; ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT; @@ -1109,6 +1118,9 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, if (fi->type == "" && textfile_extensions.has(ext)) { fi->type = "TextFile"; } + if (fi->type == "" && other_file_extensions.has(ext)) { + fi->type = "OtherFile"; + } fi->uid = ResourceLoader::get_resource_uid(path); fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); fi->deps = _get_dependencies(path); @@ -1140,6 +1152,20 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir, } } +void EditorFileSystem::_process_removed_files(const HashSet<String> &p_processed_files) { + for (const KeyValue<String, EditorFileSystem::FileCache> &kv : file_cache) { + if (!p_processed_files.has(kv.key)) { + if (ClassDB::is_parent_class(kv.value.type, SNAME("Script"))) { + // A script has been removed from disk since the last startup. The documentation needs to be updated. + // There's no need to add the path in update_script_paths since that is exclusively for updating global class names, + // which is handled in _first_scan_filesystem before the full scan to ensure plugins and autoloads can be created. + MutexLock update_script_lock(update_script_mutex); + update_script_paths_documentation.insert(kv.key); + } + } + } +} + void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanProgress &p_progress) { uint64_t current_mtime = FileAccess::get_modified_time(p_dir->get_path()); @@ -1207,7 +1233,7 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr int nb_files_dir = _scan_new_dir(&sd, d); p_progress.hi += nb_files_dir; diff_nb_files += nb_files_dir; - _process_file_system(&sd, efd, p_progress); + _process_file_system(&sd, efd, p_progress, nullptr); ItemAction ia; ia.action = ItemAction::ACTION_DIR_ADD; @@ -1240,8 +1266,11 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr if (fi->type == "" && textfile_extensions.has(ext)) { fi->type = "TextFile"; } + if (fi->type == "" && other_file_extensions.has(ext)) { + fi->type = "OtherFile"; + } fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path); - fi->import_valid = fi->type == "TextFile" ? true : ResourceLoader::is_import_valid(path); + fi->import_valid = (fi->type == "TextFile" || fi->type == "OtherFile") ? true : ResourceLoader::is_import_valid(path); fi->import_group_file = ResourceLoader::get_import_group_file(path); { @@ -1513,6 +1542,7 @@ void EditorFileSystem::_notification(int p_what) { // Set first_scan to false before the signals so the function doing_first_scan can return false // in editor_node to start the export if needed. first_scan = false; + ResourceImporter::load_on_startup = nullptr; if (changed) { emit_signal(SNAME("filesystem_changed")); } @@ -1535,6 +1565,7 @@ void EditorFileSystem::_notification(int p_what) { // Set first_scan to false before the signals so the function doing_first_scan can return false // in editor_node to start the export if needed. first_scan = false; + ResourceImporter::load_on_startup = nullptr; emit_signal(SNAME("filesystem_changed")); emit_signal(SNAME("sources_changed"), sources_changed.size() > 0); } @@ -1871,7 +1902,12 @@ void EditorFileSystem::_update_script_classes() { EditorProgress *ep = nullptr; if (update_script_paths.size() > 1) { - ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size())); + if (MessageQueue::get_singleton()->is_flushing()) { + // Use background progress when message queue is flushing. + ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size(), false, true)); + } else { + ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size())); + } } int step_count = 0; @@ -1910,7 +1946,12 @@ void EditorFileSystem::_update_script_documentation() { EditorProgress *ep = nullptr; if (update_script_paths_documentation.size() > 1) { - ep = memnew(EditorProgress("update_script_paths_documentation", TTR("Updating scripts documentation"), update_script_paths_documentation.size())); + if (MessageQueue::get_singleton()->is_flushing()) { + // Use background progress when message queue is flushing. + ep = memnew(EditorProgress("update_script_paths_documentation", TTR("Updating scripts documentation"), update_script_paths_documentation.size(), false, true)); + } else { + ep = memnew(EditorProgress("update_script_paths_documentation", TTR("Updating scripts documentation"), update_script_paths_documentation.size())); + } } int step_count = 0; @@ -2083,6 +2124,9 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { if (type.is_empty() && textfile_extensions.has(file.get_extension())) { type = "TextFile"; } + if (type.is_empty() && other_file_extensions.has(file.get_extension())) { + type = "OtherFile"; + } String script_class = ResourceLoader::get_resource_script_class(file); ResourceUID::ID uid = ResourceLoader::get_resource_uid(file); @@ -2102,7 +2146,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { EditorFileSystemDirectory::FileInfo *fi = memnew(EditorFileSystemDirectory::FileInfo); fi->file = file_name; fi->import_modified_time = 0; - fi->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file); + fi->import_valid = (type == "TextFile" || type == "OtherFile") ? true : ResourceLoader::is_import_valid(file); if (idx == fs->files.size()) { fs->files.push_back(fi); @@ -2126,7 +2170,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) { fs->files[cpos]->import_group_file = ResourceLoader::get_import_group_file(file); fs->files[cpos]->modified_time = FileAccess::get_modified_time(file); fs->files[cpos]->deps = _get_dependencies(file); - fs->files[cpos]->import_valid = type == "TextFile" ? true : ResourceLoader::is_import_valid(file); + fs->files[cpos]->import_valid = (type == "TextFile" || type == "OtherFile") ? true : ResourceLoader::is_import_valid(file); if (uid != ResourceUID::INVALID_ID) { if (ResourceUID::get_singleton()->has_id(uid)) { @@ -2384,6 +2428,9 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector if (fs->files[cpos]->type == "" && textfile_extensions.has(file.get_extension())) { fs->files[cpos]->type = "TextFile"; } + if (fs->files[cpos]->type == "" && other_file_extensions.has(file.get_extension())) { + fs->files[cpos]->type = "OtherFile"; + } fs->files[cpos]->import_valid = err == OK; if (ResourceUID::get_singleton()->has_id(uid)) { @@ -2410,14 +2457,16 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector return err; } -Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters) { +Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters, bool p_update_file_system) { print_verbose(vformat("EditorFileSystem: Importing file: %s", p_file)); uint64_t start_time = OS::get_singleton()->get_ticks_msec(); EditorFileSystemDirectory *fs = nullptr; int cpos = -1; - bool found = _find_file(p_file, &fs, cpos); - ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'."); + if (p_update_file_system) { + bool found = _find_file(p_file, &fs, cpos); + ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'."); + } //try to obtain existing params @@ -2471,11 +2520,13 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin if (importer_name == "keep" || importer_name == "skip") { //keep files, do nothing. - fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); - fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import"); - fs->files[cpos]->deps.clear(); - fs->files[cpos]->type = ""; - fs->files[cpos]->import_valid = false; + if (p_update_file_system) { + fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); + fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import"); + fs->files[cpos]->deps.clear(); + fs->files[cpos]->type = ""; + fs->files[cpos]->import_valid = false; + } EditorResourcePreview::get_singleton()->check_for_invalidation(p_file); return OK; } @@ -2635,16 +2686,18 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin } } - // Update cpos, newly created files could've changed the index of the reimported p_file. - _find_file(p_file, &fs, cpos); + if (p_update_file_system) { + // Update cpos, newly created files could've changed the index of the reimported p_file. + _find_file(p_file, &fs, cpos); - // Update modified times, to avoid reimport. - fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); - fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import"); - fs->files[cpos]->deps = _get_dependencies(p_file); - fs->files[cpos]->type = importer->get_resource_type(); - fs->files[cpos]->uid = uid; - fs->files[cpos]->import_valid = fs->files[cpos]->type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file); + // Update modified times, to avoid reimport. + fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file); + fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import"); + fs->files[cpos]->deps = _get_dependencies(p_file); + fs->files[cpos]->type = importer->get_resource_type(); + fs->files[cpos]->uid = uid; + fs->files[cpos]->import_valid = fs->files[cpos]->type == "TextFile" ? true : ResourceLoader::is_import_valid(p_file); + } if (ResourceUID::get_singleton()->has_id(uid)) { ResourceUID::get_singleton()->set_id(uid, p_file); @@ -2904,6 +2957,33 @@ Error EditorFileSystem::_resource_import(const String &p_path) { return OK; } +Ref<Resource> EditorFileSystem::_load_resource_on_startup(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode) { + ERR_FAIL_NULL_V(p_importer, Ref<Resource>()); + + if (!FileAccess::exists(p_path)) { + ERR_FAIL_V_MSG(Ref<Resource>(), vformat("Failed loading resource: %s. The file doesn't seem to exist.", p_path)); + } + + Ref<Resource> res; + bool can_retry = true; + bool retry = true; + while (retry) { + retry = false; + + res = p_importer->load_internal(p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode, can_retry); + + if (res.is_null() && can_retry) { + can_retry = false; + Error err = singleton->_reimport_file(p_path, HashMap<StringName, Variant>(), "", nullptr, false); + if (err == OK) { + retry = true; + } + } + } + + return res; +} + bool EditorFileSystem::_should_skip_directory(const String &p_path) { String project_data_path = ProjectSettings::get_singleton()->get_project_data_path(); if (p_path == project_data_path || p_path.begins_with(project_data_path + "/")) { @@ -3051,6 +3131,7 @@ void EditorFileSystem::_update_extensions() { valid_extensions.clear(); import_extensions.clear(); textfile_extensions.clear(); + other_file_extensions.clear(); List<String> extensionsl; ResourceLoader::get_recognized_extensions_for_type("", &extensionsl); @@ -3066,6 +3147,14 @@ void EditorFileSystem::_update_extensions() { valid_extensions.insert(E); textfile_extensions.insert(E); } + const Vector<String> other_file_ext = ((String)(EDITOR_GET("docks/filesystem/other_file_extensions"))).split(",", false); + for (const String &E : other_file_ext) { + if (valid_extensions.has(E)) { + continue; + } + valid_extensions.insert(E); + other_file_extensions.insert(E); + } extensionsl.clear(); ResourceFormatImporter::get_singleton()->get_recognized_extensions(&extensionsl); @@ -3101,6 +3190,10 @@ EditorFileSystem::EditorFileSystem() { scan_total = 0; ResourceSaver::set_get_resource_id_for_path(_resource_saver_get_resource_id_for_path); + + // Set the callback method that the ResourceFormatImporter will use + // if resources are loaded during the first scan. + ResourceImporter::load_on_startup = _load_resource_on_startup; } EditorFileSystem::~EditorFileSystem() { diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index ca4a64bfac..9adab1ed24 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -32,6 +32,8 @@ #define EDITOR_FILE_SYSTEM_H #include "core/io/dir_access.h" +#include "core/io/resource_importer.h" +#include "core/io/resource_loader.h" #include "core/os/thread.h" #include "core/os/thread_safe.h" #include "core/templates/hash_set.h" @@ -233,11 +235,12 @@ class EditorFileSystem : public Node { int _insert_actions_delete_files_directory(EditorFileSystemDirectory *p_dir); HashSet<String> textfile_extensions; + HashSet<String> other_file_extensions; HashSet<String> valid_extensions; HashSet<String> import_extensions; int _scan_new_dir(ScannedDirectory *p_dir, Ref<DirAccess> &da); - void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress); + void _process_file_system(const ScannedDirectory *p_scan_dir, EditorFileSystemDirectory *p_dir, ScanProgress &p_progress, HashSet<String> *p_processed_files); Thread thread_sources; bool scanning_changes = false; @@ -252,7 +255,7 @@ class EditorFileSystem : public Node { void _update_extensions(); - Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr); + Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr, bool p_update_file_system = true); Error _reimport_group(const String &p_group_file, const Vector<String> &p_files); bool _test_for_reimport(const String &p_path, bool p_only_imported_files); @@ -285,6 +288,7 @@ class EditorFileSystem : public Node { void _update_script_classes(); void _update_script_documentation(); void _process_update_pending(); + void _process_removed_files(const HashSet<String> &p_processed_files); Mutex update_scene_mutex; HashSet<String> update_scene_paths; @@ -296,6 +300,7 @@ class EditorFileSystem : public Node { String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const; static Error _resource_import(const String &p_path); + static Ref<Resource> _load_resource_on_startup(ResourceFormatImporter *p_importer, const String &p_path, Error *r_error, bool p_use_sub_threads, float *r_progress, ResourceFormatLoader::CacheMode p_cache_mode); bool using_fat32_or_exfat; // Workaround for projects in FAT32 or exFAT filesystem (pendrives, most of the time) diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 9b0c05d910..fe758bf99b 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -376,10 +376,10 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i } p_rt->push_color(type_color); - bool add_array = false; + bool add_typed_container = false; if (can_ref) { if (link_t.ends_with("[]")) { - add_array = true; + add_typed_container = true; link_t = link_t.trim_suffix("[]"); display_t = display_t.trim_suffix("[]"); @@ -387,6 +387,22 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i p_rt->add_text("Array"); p_rt->pop(); // meta p_rt->add_text("["); + } else if (link_t.begins_with("Dictionary[")) { + add_typed_container = true; + link_t = link_t.trim_prefix("Dictionary[").trim_suffix("]"); + display_t = display_t.trim_prefix("Dictionary[").trim_suffix("]"); + + p_rt->push_meta("#Dictionary", RichTextLabel::META_UNDERLINE_ON_HOVER); // class + p_rt->add_text("Dictionary"); + p_rt->pop(); // meta + p_rt->add_text("["); + p_rt->push_meta("#" + link_t.get_slice(", ", 0), RichTextLabel::META_UNDERLINE_ON_HOVER); // class + p_rt->add_text(_contextualize_class_specifier(display_t.get_slice(", ", 0), p_class)); + p_rt->pop(); // meta + p_rt->add_text(", "); + + link_t = link_t.get_slice(", ", 1); + display_t = _contextualize_class_specifier(display_t.get_slice(", ", 1), p_class); } else if (is_bitfield) { p_rt->push_color(Color(type_color, 0.5)); p_rt->push_hint(TTR("This value is an integer composed as a bitmask of the following flags.")); @@ -405,7 +421,7 @@ static void _add_type_to_rt(const String &p_type, const String &p_enum, bool p_i p_rt->add_text(display_t); if (can_ref) { p_rt->pop(); // meta - if (add_array) { + if (add_typed_container) { p_rt->add_text("]"); } else if (is_bitfield) { p_rt->push_color(Color(type_color, 0.5)); @@ -1456,10 +1472,31 @@ void EditorHelp::_update_doc() { _push_normal_font(); class_desc->push_color(theme_cache.comment_color); + bool has_prev_text = false; + + if (theme_item.is_deprecated) { + has_prev_text = true; + DEPRECATED_DOC_MSG(HANDLE_DOC(theme_item.deprecated_message), TTR("This theme property may be changed or removed in future versions.")); + } + + if (theme_item.is_experimental) { + if (has_prev_text) { + class_desc->add_newline(); + class_desc->add_newline(); + } + has_prev_text = true; + EXPERIMENTAL_DOC_MSG(HANDLE_DOC(theme_item.experimental_message), TTR("This theme property may be changed or removed in future versions.")); + } + const String descr = HANDLE_DOC(theme_item.description); if (!descr.is_empty()) { + if (has_prev_text) { + class_desc->add_newline(); + class_desc->add_newline(); + } + has_prev_text = true; _add_text(descr); - } else { + } else if (!has_prev_text) { class_desc->add_image(get_editor_theme_icon(SNAME("Error"))); class_desc->add_text(" "); class_desc->push_color(theme_cache.comment_color); @@ -2220,12 +2257,6 @@ void EditorHelp::_update_doc() { } has_prev_text = true; _add_text(descr); - // Add copy note to built-in properties returning Packed*Array. - if (!cd.is_script_doc && packed_array_types.has(prop.type)) { - class_desc->add_newline(); - class_desc->add_newline(); - _add_text(vformat(TTR("[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [%s] for more details."), prop.type)); - } } else if (!has_prev_text) { class_desc->add_image(get_editor_theme_icon(SNAME("Error"))); class_desc->add_text(" "); @@ -2238,6 +2269,13 @@ void EditorHelp::_update_doc() { class_desc->pop(); // color } + // Add copy note to built-in properties returning `Packed*Array`. + if (!cd.is_script_doc && packed_array_types.has(prop.type)) { + class_desc->add_newline(); + class_desc->add_newline(); + _add_text(vformat(TTR("[b]Note:[/b] The returned array is [i]copied[/i] and any changes to it will not update the original property value. See [%s] for more details."), prop.type)); + } + class_desc->pop(); // color _pop_normal_font(); class_desc->pop(); // indent @@ -3380,6 +3418,20 @@ EditorHelpBit::HelpData EditorHelpBit::_get_theme_item_help_data(const StringNam for (const DocData::ThemeItemDoc &theme_item : E->value.theme_properties) { HelpData current; current.description = HANDLE_DOC(theme_item.description); + if (theme_item.is_deprecated) { + if (theme_item.deprecated_message.is_empty()) { + current.deprecated_message = TTR("This theme property may be changed or removed in future versions."); + } else { + current.deprecated_message = HANDLE_DOC(theme_item.deprecated_message); + } + } + if (theme_item.is_experimental) { + if (theme_item.experimental_message.is_empty()) { + current.experimental_message = TTR("This theme property may be changed or removed in future versions."); + } else { + current.experimental_message = HANDLE_DOC(theme_item.experimental_message); + } + } if (theme_item.name == p_theme_item_name) { result = current; diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index eb97337b37..987c7f9c31 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -1248,7 +1248,7 @@ TreeItem *EditorHelpSearch::Runner::_create_property_item(TreeItem *p_parent, co TreeItem *EditorHelpSearch::Runner::_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match) { String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name; tooltip += _build_keywords_tooltip(p_match.doc->keywords); - return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberTheme"), p_match.doc->name, p_match.doc->name, TTRC("Theme Property"), "theme_item", p_match.doc->keywords, tooltip, false, false, p_match.name ? String() : p_match.keyword); + return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberTheme"), p_match.doc->name, p_match.doc->name, TTRC("Theme Property"), "theme_item", p_match.doc->keywords, tooltip, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const StringName &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_keywords, bool p_is_deprecated, bool p_is_experimental, const String &p_matching_keyword) { diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 1e5acce032..a1cae374aa 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -1500,12 +1500,6 @@ void EditorInspectorSection::_notification(int p_what) { draw_string(font, text_offset, label, text_align, available, font_size, font_color, TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_CONSTRAIN_ELLIPSIS); } - // Draw dropping highlight. - if (dropping && !vbox->is_visible_in_tree()) { - Color accent_color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor)); - draw_rect(Rect2(Point2(), get_size()), accent_color, false); - } - // Draw section indentation. if (section_indent_style.is_valid() && section_indent > 0) { Rect2 indent_rect = Rect2(Vector2(), Vector2(indent_depth * section_indent_size, get_size().height)); @@ -1527,14 +1521,14 @@ void EditorInspectorSection::_notification(int p_what) { } break; case NOTIFICATION_MOUSE_ENTER: { - if (dropping || dropping_for_unfold) { + if (dropping_for_unfold) { dropping_unfold_timer->start(); } queue_redraw(); } break; case NOTIFICATION_MOUSE_EXIT: { - if (dropping || dropping_for_unfold) { + if (dropping_for_unfold) { dropping_unfold_timer->stop(); } queue_redraw(); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 29ee234883..fda1443000 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -310,7 +310,6 @@ class EditorInspectorSection : public Container { int level = 1; Timer *dropping_unfold_timer = nullptr; - bool dropping = false; bool dropping_for_unfold = false; HashSet<StringName> revertable_properties; diff --git a/editor/editor_interface.compat.inc b/editor/editor_interface.compat.inc new file mode 100644 index 0000000000..f5b35931fe --- /dev/null +++ b/editor/editor_interface.compat.inc @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* editor_interface.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "editor_interface.h" + +#ifndef DISABLE_DEPRECATED + +void EditorInterface::_popup_node_selector_bind_compat_94323(const Callable &p_callback, const TypedArray<StringName> &p_valid_types) { + popup_node_selector(p_callback, p_valid_types, nullptr); +} + +void EditorInterface::_popup_property_selector_bind_compat_94323(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter) { + popup_property_selector(p_object, p_callback, p_type_filter, String()); +} + +void EditorInterface::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("popup_node_selector", "callback", "valid_types"), &EditorInterface::_popup_node_selector_bind_compat_94323, DEFVAL(TypedArray<StringName>())); + ClassDB::bind_compatibility_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter"), &EditorInterface::_popup_property_selector_bind_compat_94323, DEFVAL(PackedInt32Array())); +} + +#endif diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp index e699b486ee..86b66ef410 100644 --- a/editor/editor_interface.cpp +++ b/editor/editor_interface.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "editor_interface.h" +#include "editor_interface.compat.inc" #include "editor/editor_command_palette.h" #include "editor/editor_feature_profile.h" @@ -276,7 +277,7 @@ void EditorInterface::set_current_feature_profile(const String &p_profile_name) // Editor dialogs. -void EditorInterface::popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types) { +void EditorInterface::popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types, Node *p_current_value) { // TODO: Should reuse dialog instance instead of creating a fresh one, but need to rework set_valid_types first. if (node_selector) { node_selector->disconnect(SNAME("selected"), callable_mp(this, &EditorInterface::_node_selected).bind(p_callback)); @@ -296,7 +297,7 @@ void EditorInterface::popup_node_selector(const Callable &p_callback, const Type get_base_control()->add_child(node_selector); - node_selector->popup_scenetree_dialog(); + node_selector->popup_scenetree_dialog(p_current_value); const Callable selected_callback = callable_mp(this, &EditorInterface::_node_selected).bind(p_callback); node_selector->connect(SNAME("selected"), selected_callback, CONNECT_DEFERRED); @@ -305,7 +306,7 @@ void EditorInterface::popup_node_selector(const Callable &p_callback, const Type node_selector->connect(SNAME("canceled"), canceled_callback, CONNECT_DEFERRED); } -void EditorInterface::popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter) { +void EditorInterface::popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter, const String &p_current_value) { // TODO: Should reuse dialog instance instead of creating a fresh one, but need to rework set_type_filter first. if (property_selector) { property_selector->disconnect(SNAME("selected"), callable_mp(this, &EditorInterface::_property_selected).bind(p_callback)); @@ -325,7 +326,7 @@ void EditorInterface::popup_property_selector(Object *p_object, const Callable & get_base_control()->add_child(property_selector); - property_selector->select_property_from_instance(p_object); + property_selector->select_property_from_instance(p_object, p_current_value); const Callable selected_callback = callable_mp(this, &EditorInterface::_property_selected).bind(p_callback); property_selector->connect(SNAME("selected"), selected_callback, CONNECT_DEFERRED); @@ -564,8 +565,8 @@ void EditorInterface::_bind_methods() { // Editor dialogs. - ClassDB::bind_method(D_METHOD("popup_node_selector", "callback", "valid_types"), &EditorInterface::popup_node_selector, DEFVAL(TypedArray<StringName>())); - ClassDB::bind_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter"), &EditorInterface::popup_property_selector, DEFVAL(PackedInt32Array())); + ClassDB::bind_method(D_METHOD("popup_node_selector", "callback", "valid_types", "current_value"), &EditorInterface::popup_node_selector, DEFVAL(TypedArray<StringName>()), DEFVAL(Variant())); + ClassDB::bind_method(D_METHOD("popup_property_selector", "object", "callback", "type_filter", "current_value"), &EditorInterface::popup_property_selector, DEFVAL(PackedInt32Array()), DEFVAL(String())); // Editor docks. diff --git a/editor/editor_interface.h b/editor/editor_interface.h index 2538f97c77..20d66d71f5 100644 --- a/editor/editor_interface.h +++ b/editor/editor_interface.h @@ -81,6 +81,13 @@ class EditorInterface : public Object { protected: static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + void _popup_node_selector_bind_compat_94323(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>()); + void _popup_property_selector_bind_compat_94323(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array()); + + static void _bind_compatibility_methods(); +#endif + public: static EditorInterface *get_singleton() { return singleton; } @@ -128,9 +135,9 @@ public: // Editor dialogs. - void popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>()); + void popup_node_selector(const Callable &p_callback, const TypedArray<StringName> &p_valid_types = TypedArray<StringName>(), Node *p_current_value = nullptr); // Must use Vector<int> because exposing Vector<Variant::Type> is not supported. - void popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array()); + void popup_property_selector(Object *p_object, const Callable &p_callback, const PackedInt32Array &p_type_filter = PackedInt32Array(), const String &p_current_value = String()); // Editor docks. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index d3fa2ed716..39291138a6 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -175,7 +175,7 @@ static const String REMOVE_ANDROID_BUILD_TEMPLATE_MESSAGE = "The Android build t static const String INSTALL_ANDROID_BUILD_TEMPLATE_MESSAGE = "This will set up your project for gradle Android builds by installing the source template to \"%s\".\nNote that in order to make gradle builds instead of using pre-built APKs, the \"Use Gradle Build\" option should be enabled in the Android export preset."; bool EditorProgress::step(const String &p_state, int p_step, bool p_force_refresh) { - if (Thread::is_main_thread()) { + if (!force_background && Thread::is_main_thread()) { return EditorNode::progress_task_step(task, p_state, p_step, p_force_refresh); } else { EditorNode::progress_task_step_bg(task, p_step); @@ -183,17 +183,18 @@ bool EditorProgress::step(const String &p_state, int p_step, bool p_force_refres } } -EditorProgress::EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel) { - if (Thread::is_main_thread()) { +EditorProgress::EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel, bool p_force_background) { + if (!p_force_background && Thread::is_main_thread()) { EditorNode::progress_add_task(p_task, p_label, p_amount, p_can_cancel); } else { EditorNode::progress_add_task_bg(p_task, p_label, p_amount); } task = p_task; + force_background = p_force_background; } EditorProgress::~EditorProgress() { - if (Thread::is_main_thread()) { + if (!force_background && Thread::is_main_thread()) { EditorNode::progress_end_task(task); } else { EditorNode::progress_end_task_bg(task); @@ -824,6 +825,7 @@ void EditorNode::_notification(int p_what) { if (EditorSettings::get_singleton()->check_changed_settings_in_group("docks/filesystem")) { HashSet<String> updated_textfile_extensions; + HashSet<String> updated_other_file_extensions; bool extensions_match = true; const Vector<String> textfile_ext = ((String)(EDITOR_GET("docks/filesystem/textfile_extensions"))).split(",", false); for (const String &E : textfile_ext) { @@ -832,9 +834,17 @@ void EditorNode::_notification(int p_what) { extensions_match = false; } } + const Vector<String> other_file_ext = ((String)(EDITOR_GET("docks/filesystem/other_file_extensions"))).split(",", false); + for (const String &E : other_file_ext) { + updated_other_file_extensions.insert(E); + if (extensions_match && !other_file_extensions.has(E)) { + extensions_match = false; + } + } - if (!extensions_match || updated_textfile_extensions.size() < textfile_extensions.size()) { + if (!extensions_match || updated_textfile_extensions.size() < textfile_extensions.size() || updated_other_file_extensions.size() < other_file_extensions.size()) { textfile_extensions = updated_textfile_extensions; + other_file_extensions = updated_other_file_extensions; EditorFileSystem::get_singleton()->scan(); } } @@ -1325,6 +1335,9 @@ Error EditorNode::load_resource(const String &p_resource, bool p_ignore_broken_d res = ResourceLoader::load(p_resource, "", ResourceFormatLoader::CACHE_MODE_REUSE, &err); } else if (textfile_extensions.has(p_resource.get_extension())) { res = ScriptEditor::get_singleton()->open_file(p_resource); + } else if (other_file_extensions.has(p_resource.get_extension())) { + OS::get_singleton()->shell_open(ProjectSettings::get_singleton()->globalize_path(p_resource)); + return OK; } ERR_FAIL_COND_V(!res.is_valid(), ERR_CANT_OPEN); @@ -4391,6 +4404,21 @@ bool EditorNode::is_additional_node_in_scene(Node *p_edited_scene, Node *p_reimp return true; } +void EditorNode::get_scene_editor_data_for_node(Node *p_root, Node *p_node, HashMap<NodePath, SceneEditorDataEntry> &p_table) { + SceneEditorDataEntry new_entry; + new_entry.is_display_folded = p_node->is_displayed_folded(); + + if (p_root != p_node) { + new_entry.is_editable = p_root->is_editable_instance(p_node); + } + + p_table.insert(p_root->get_path_to(p_node), new_entry); + + for (int i = 0; i < p_node->get_child_count(); i++) { + get_scene_editor_data_for_node(p_root, p_node->get_child(i), p_table); + } +} + void EditorNode::get_preload_scene_modification_table( Node *p_edited_scene, Node *p_reimported_root, @@ -4497,7 +4525,7 @@ void EditorNode::get_preload_scene_modification_table( void EditorNode::get_preload_modifications_reference_to_nodes( Node *p_root, Node *p_node, - List<Node *> &p_excluded_nodes, + HashSet<Node *> &p_excluded_nodes, List<Node *> &p_instance_list_with_children, HashMap<NodePath, ModificationNodeEntry> &p_modification_table) { if (!p_excluded_nodes.find(p_node)) { @@ -5016,8 +5044,8 @@ String EditorNode::_get_system_info() const { #ifdef LINUXBSD_ENABLED const String display_server = OS::get_singleton()->get_environment("XDG_SESSION_TYPE").capitalize().replace(" ", ""); // `replace` is necessary, because `capitalize` introduces a whitespace between "x" and "11". #endif // LINUXBSD_ENABLED - String driver_name = GLOBAL_GET("rendering/rendering_device/driver"); - String rendering_method = GLOBAL_GET("rendering/renderer/rendering_method"); + String driver_name = OS::get_singleton()->get_current_rendering_driver_name().to_lower(); + String rendering_method = OS::get_singleton()->get_current_rendering_method().to_lower(); const String rendering_device_name = RenderingServer::get_singleton()->get_video_adapter_name(); @@ -5053,12 +5081,21 @@ String EditorNode::_get_system_info() const { rendering_method = "Mobile"; } else if (rendering_method == "gl_compatibility") { rendering_method = "Compatibility"; - driver_name = GLOBAL_GET("rendering/gl_compatibility/driver"); } if (driver_name == "vulkan") { driver_name = "Vulkan"; - } else if (driver_name.begins_with("opengl3")) { - driver_name = "GLES3"; + } else if (driver_name == "d3d12") { + driver_name = "Direct3D 12"; + } else if (driver_name == "opengl3_angle") { + driver_name = "OpenGL ES 3/ANGLE"; + } else if (driver_name == "opengl3_es") { + driver_name = "OpenGL ES 3"; + } else if (driver_name == "opengl3") { + if (OS::get_singleton()->get_gles_over_gl()) { + driver_name = "OpenGL 3"; + } else { + driver_name = "OpenGL ES 3"; + } } else if (driver_name == "metal") { driver_name = "Metal"; } @@ -5230,8 +5267,8 @@ void EditorNode::_copy_warning(const String &p_str) { } void EditorNode::_save_editor_layout() { - if (waiting_for_first_scan) { - return; // Scanning, do not touch docks. + if (!load_editor_layout_done) { + return; } Ref<ConfigFile> config; config.instantiate(); @@ -5287,22 +5324,22 @@ void EditorNode::_load_editor_layout() { if (overridden_default_layout >= 0) { _layout_menu_option(overridden_default_layout); } - return; - } - - ep.step(TTR("Loading docks..."), 1, true); - editor_dock_manager->load_docks_from_config(config, "docks"); + } else { + ep.step(TTR("Loading docks..."), 1, true); + editor_dock_manager->load_docks_from_config(config, "docks"); - ep.step(TTR("Reopening scenes..."), 2, true); - _load_open_scenes_from_config(config); + ep.step(TTR("Reopening scenes..."), 2, true); + _load_open_scenes_from_config(config); - ep.step(TTR("Loading central editor layout..."), 3, true); - _load_central_editor_layout_from_config(config); + ep.step(TTR("Loading central editor layout..."), 3, true); + _load_central_editor_layout_from_config(config); - ep.step(TTR("Loading plugin window layout..."), 4, true); - editor_data.set_plugin_window_layout(config); + ep.step(TTR("Loading plugin window layout..."), 4, true); + editor_data.set_plugin_window_layout(config); - ep.step(TTR("Editor layout ready."), 5, true); + ep.step(TTR("Editor layout ready."), 5, true); + } + load_editor_layout_done = true; } void EditorNode::_save_central_editor_layout_to_config(Ref<ConfigFile> p_config_file) { @@ -5962,12 +5999,14 @@ void EditorNode::reload_scene(const String &p_path) { scene_tabs->set_current_tab(current_tab); } -void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list) { +void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, HashSet<Node *> &p_instance_list) { String scene_file_path = p_node->get_scene_file_path(); - // This is going to get messy... + bool valid_instance_found = false; + + // Attempt to find all the instances matching path we're going to reload. if (p_node->get_scene_file_path() == p_instance_path) { - p_instance_list.push_back(p_node); + valid_instance_found = true; } else { Node *current_node = p_node; @@ -5975,7 +6014,7 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node * while (inherited_state.is_valid()) { String inherited_path = inherited_state->get_path(); if (inherited_path == p_instance_path) { - p_instance_list.push_back(p_node); + valid_instance_found = true; break; } @@ -5983,6 +6022,19 @@ void EditorNode::find_all_instances_inheriting_path_in_node(Node *p_root, Node * } } + // Instead of adding this instance directly, if its not owned by the scene, walk its ancestors + // and find the first node still owned by the scene. This is what we will reloading instead. + if (valid_instance_found) { + Node *current_node = p_node; + while (true) { + if (current_node->get_owner() == p_root || current_node->get_owner() == nullptr) { + p_instance_list.insert(current_node); + break; + } + current_node = current_node->get_parent(); + } + } + for (int i = 0; i < p_node->get_child_count(); i++) { find_all_instances_inheriting_path_in_node(p_root, p_node->get_child(i), p_instance_path, p_instance_list); } @@ -6001,20 +6053,20 @@ void EditorNode::preload_reimporting_with_path_in_edited_scenes(const List<Strin Node *edited_scene_root = editor_data.get_edited_scene_root(current_scene_idx); if (edited_scene_root) { - SceneModificationsEntry scene_motifications; + SceneModificationsEntry scene_modifications; for (const String &instance_path : p_scenes) { if (editor_data.get_scene_path(current_scene_idx) == instance_path) { continue; } - List<Node *> instance_list; - find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, instance_path, instance_list); - if (instance_list.size() > 0) { + HashSet<Node *> instances_to_reimport; + find_all_instances_inheriting_path_in_node(edited_scene_root, edited_scene_root, instance_path, instances_to_reimport); + if (instances_to_reimport.size() > 0) { editor_data.set_edited_scene(current_scene_idx); List<Node *> instance_list_with_children; - for (Node *original_node : instance_list) { + for (Node *original_node : instances_to_reimport) { InstanceModificationsEntry instance_modifications; // Fetching all the modified properties of the nodes reimported scene. @@ -6022,19 +6074,19 @@ void EditorNode::preload_reimporting_with_path_in_edited_scenes(const List<Strin instance_modifications.original_node = original_node; instance_modifications.instance_path = instance_path; - scene_motifications.instance_list.push_back(instance_modifications); + scene_modifications.instance_list.push_back(instance_modifications); instance_list_with_children.push_back(original_node); get_children_nodes(original_node, instance_list_with_children); } // Search the scene to find nodes that references the nodes will be recreated. - get_preload_modifications_reference_to_nodes(edited_scene_root, edited_scene_root, instance_list, instance_list_with_children, scene_motifications.other_instances_modifications); + get_preload_modifications_reference_to_nodes(edited_scene_root, edited_scene_root, instances_to_reimport, instance_list_with_children, scene_modifications.other_instances_modifications); } } - if (scene_motifications.instance_list.size() > 0) { - scenes_modification_table[current_scene_idx] = scene_motifications; + if (scene_modifications.instance_list.size() > 0) { + scenes_modification_table[current_scene_idx] = scene_modifications; } } } @@ -6144,7 +6196,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { base_packed_scene = current_packed_scene; } if (!local_scene_cache.find(path)) { - current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP, &err); + current_packed_scene = ResourceLoader::load(path, "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); local_scene_cache[path] = current_packed_scene; } else { current_packed_scene = local_scene_cache[path]; @@ -6157,10 +6209,10 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { // Instantiate early so that caches cleared on load in SceneState can be rebuilt early. Node *instantiated_node = nullptr; - // If we are in a inherit scene, it's easier to create a new base scene and + // If we are in a inherited scene, it's easier to create a new base scene and // grab the node from there. // When scene_path_to_node is '.' and we have scene_inherited_state, it's because - // it's a muli-level inheritance scene. We should use + // it's a multi-level inheritance scene. We should use NodePath scene_path_to_node = current_edited_scene->get_path_to(original_node); Ref<SceneState> scene_state = current_edited_scene->get_scene_inherited_state(); if (scene_path_to_node != "." && scene_state.is_valid() && scene_state->get_path() != instance_modifications.instance_path && scene_state->find_node_by_path(scene_path_to_node) >= 0) { @@ -6184,9 +6236,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { if (!instantiated_node) { // If no base scene was found to create the node, we will use the reimported packed scene directly. - // But, when the current edited scene is the reimported scene, it's because it's a inherited scene - // of the reimported scene. In that case, we will not instantiate current_packed_scene, because - // we would reinstanciate ourself. Using the base scene is better. + // But, when the current edited scene is the reimported scene, it's because it's an inherited scene + // derived from the reimported scene. In that case, we will not instantiate current_packed_scene, because + // we would reinstantiate ourself. Using the base scene is better. if (current_edited_scene == original_node) { if (base_packed_scene.is_valid()) { instantiated_node = base_packed_scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); @@ -6242,6 +6294,17 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { // crash when reimporting scenes with animations when "Editable children" was enabled. replace_history_reimported_nodes(original_node, instantiated_node, original_node); + // Reset the editable instance state. + HashMap<NodePath, SceneEditorDataEntry> scene_editor_data_table; + Node *owner = original_node->get_owner(); + if (!owner) { + owner = original_node; + } + + get_scene_editor_data_for_node(owner, original_node, scene_editor_data_table); + + bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder(); + // Delete all the remaining node children. while (original_node->get_child_count()) { Node *child = original_node->get_child(0); @@ -6250,16 +6313,6 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { child->queue_free(); } - // Reset the editable instance state. - bool is_editable = true; - Node *owner = original_node->get_owner(); - if (owner) { - is_editable = owner->is_editable_instance(original_node); - } - - bool original_node_is_displayed_folded = original_node->is_displayed_folded(); - bool original_node_scene_instance_load_placeholder = original_node->get_scene_instance_load_placeholder(); - // Update the name to match instantiated_node->set_name(original_node->get_name()); @@ -6290,19 +6343,9 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { // Mark the old node for deletion. original_node->queue_free(); - // Restore the folded and placeholder state from the original node. - instantiated_node->set_display_folded(original_node_is_displayed_folded); + // Restore the placeholder state from the original node. instantiated_node->set_scene_instance_load_placeholder(original_node_scene_instance_load_placeholder); - if (owner) { - Ref<SceneState> ss_inst = owner->get_scene_instance_state(); - if (ss_inst.is_valid()) { - ss_inst->update_instance_resource(instance_modifications.instance_path, current_packed_scene); - } - - owner->set_editable_instance(instantiated_node, is_editable); - } - // Attempt to re-add all the additional nodes. for (AdditiveNodeEntry additive_node_entry : instance_modifications.addition_list) { Node *parent_node = instantiated_node->get_node_or_null(additive_node_entry.parent); @@ -6334,6 +6377,17 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { } } + // Restore the scene's editable instance and folded states. + for (HashMap<NodePath, SceneEditorDataEntry>::Iterator I = scene_editor_data_table.begin(); I; ++I) { + Node *node = owner->get_node_or_null(I->key); + if (node) { + if (owner != node) { + owner->set_editable_instance(node, I->value.is_editable); + } + node->set_display_folded(I->value.is_display_folded); + } + } + // Restore the selection. if (selected_node_paths.size()) { for (NodePath selected_node_path : selected_node_paths) { @@ -6970,6 +7024,10 @@ EditorNode::EditorNode() { for (const String &E : textfile_ext) { textfile_extensions.insert(E); } + const Vector<String> other_file_ext = ((String)(EDITOR_GET("docks/filesystem/other_file_extensions"))).split(",", false); + for (const String &E : other_file_ext) { + other_file_extensions.insert(E); + } resource_preview = memnew(EditorResourcePreview); add_child(resource_preview); @@ -7150,6 +7208,7 @@ EditorNode::EditorNode() { } main_menu = memnew(MenuBar); + main_menu->set_mouse_filter(Control::MOUSE_FILTER_STOP); title_bar->add_child(main_menu); main_menu->set_theme_type_variation("MainMenuBar"); main_menu->set_start_index(0); // Main menu, add to the start of global menu. @@ -7332,6 +7391,7 @@ EditorNode::EditorNode() { } main_editor_button_hb = memnew(HBoxContainer); + main_editor_button_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP); title_bar->add_child(main_editor_button_hb); // Options are added and handled by DebuggerEditorPlugin. @@ -7426,11 +7486,13 @@ EditorNode::EditorNode() { title_bar->add_child(right_spacer); project_run_bar = memnew(EditorRunBar); + project_run_bar->set_mouse_filter(Control::MOUSE_FILTER_STOP); title_bar->add_child(project_run_bar); project_run_bar->connect("play_pressed", callable_mp(this, &EditorNode::_project_run_started)); project_run_bar->connect("stop_pressed", callable_mp(this, &EditorNode::_project_run_stopped)); HBoxContainer *right_menu_hb = memnew(HBoxContainer); + right_menu_hb->set_mouse_filter(Control::MOUSE_FILTER_STOP); title_bar->add_child(right_menu_hb); renderer = memnew(OptionButton); @@ -7555,8 +7617,8 @@ EditorNode::EditorNode() { default_layout->set_value(docks_section, "dock_split_" + itos(i + 1), 0); } default_layout->set_value(docks_section, "dock_hsplit_1", 0); - default_layout->set_value(docks_section, "dock_hsplit_2", 270 * EDSCALE); - default_layout->set_value(docks_section, "dock_hsplit_3", -270 * EDSCALE); + default_layout->set_value(docks_section, "dock_hsplit_2", 270); + default_layout->set_value(docks_section, "dock_hsplit_3", -270); default_layout->set_value(docks_section, "dock_hsplit_4", 0); _update_layouts_menu(); diff --git a/editor/editor_node.h b/editor/editor_node.h index bdf9b26a7a..f1b50cf3fa 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -124,9 +124,10 @@ class WindowWrapper; struct EditorProgress { String task; + bool force_background = false; bool step(const String &p_state, int p_step = -1, bool p_force_refresh = true); - EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false); + EditorProgress(const String &p_task, const String &p_label, int p_amount, bool p_can_cancel = false, bool p_force_background = false); ~EditorProgress(); }; @@ -461,6 +462,7 @@ private: bool requested_first_scan = false; bool waiting_for_first_scan = true; + bool load_editor_layout_done = false; int current_menu_option = 0; @@ -487,6 +489,7 @@ private: String import_reload_fn; HashSet<String> textfile_extensions; + HashSet<String> other_file_extensions; HashSet<FileDialog *> file_dialogs; HashSet<EditorFileDialog *> editor_file_dialogs; @@ -849,12 +852,19 @@ public: HashMap<NodePath, ModificationNodeEntry> other_instances_modifications; }; + struct SceneEditorDataEntry { + bool is_editable; + bool is_display_folded; + }; + HashMap<int, SceneModificationsEntry> scenes_modification_table; List<String> scenes_reimported; List<String> resources_reimported; void update_node_from_node_modification_entry(Node *p_node, ModificationNodeEntry &p_node_modification); + void get_scene_editor_data_for_node(Node *p_root, Node *p_node, HashMap<NodePath, SceneEditorDataEntry> &p_table); + void get_preload_scene_modification_table( Node *p_edited_scene, Node *p_reimported_root, @@ -863,7 +873,7 @@ public: void get_preload_modifications_reference_to_nodes( Node *p_root, Node *p_node, - List<Node *> &p_excluded_nodes, + HashSet<Node *> &p_excluded_nodes, List<Node *> &p_instance_list_with_children, HashMap<NodePath, ModificationNodeEntry> &p_modification_table); void get_children_nodes(Node *p_node, List<Node *> &p_nodes); @@ -924,7 +934,7 @@ public: void reload_scene(const String &p_path); - void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, List<Node *> &p_instance_list); + void find_all_instances_inheriting_path_in_node(Node *p_root, Node *p_node, const String &p_instance_path, HashSet<Node *> &p_instance_list); void preload_reimporting_with_path_in_edited_scenes(const List<String> &p_scenes); void reload_instances_with_path_in_edited_scenes(); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 19a4165041..123d903220 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -3771,7 +3771,7 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ return editor; } else { EditorPropertyDictionary *editor = memnew(EditorPropertyDictionary); - editor->setup(p_hint); + editor->setup(p_hint, p_hint_text); return editor; } } break; diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index f5d016629f..f03eef4d4d 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -863,6 +863,26 @@ EditorPropertyArray::EditorPropertyArray() { ///////////////////// DICTIONARY /////////////////////////// +void EditorPropertyDictionary::initialize_dictionary(Variant &p_dictionary) { + if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) { + Dictionary dict; + StringName key_subtype_class; + Ref<Script> key_subtype_script; + if (key_subtype == Variant::OBJECT && !key_subtype_hint_string.is_empty() && ClassDB::class_exists(key_subtype_hint_string)) { + key_subtype_class = key_subtype_hint_string; + } + StringName value_subtype_class; + Ref<Script> value_subtype_script; + if (value_subtype == Variant::OBJECT && !value_subtype_hint_string.is_empty() && ClassDB::class_exists(value_subtype_hint_string)) { + value_subtype_class = value_subtype_hint_string; + } + dict.set_typed(key_subtype, key_subtype_class, key_subtype_script, value_subtype, value_subtype_class, value_subtype_script); + p_dictionary = dict; + } else { + VariantInternal::initialize(&p_dictionary, Variant::DICTIONARY); + } +} + void EditorPropertyDictionary::_property_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { if (p_value.get_type() == Variant::OBJECT && p_value.is_null()) { p_value = Variant(); // `EditorResourcePicker` resets to `Ref<Resource>()`. See GH-82716. @@ -914,16 +934,29 @@ void EditorPropertyDictionary::_create_new_property_slot(int p_idx) { EditorProperty *prop = memnew(EditorPropertyNil); hbox->add_child(prop); - Button *edit_btn = memnew(Button); - edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit"))); - edit_btn->set_disabled(is_read_only()); - edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size())); - hbox->add_child(edit_btn); + bool use_key = p_idx == EditorPropertyDictionaryObject::NEW_KEY_INDEX; + bool is_untyped_dict = (use_key ? key_subtype : value_subtype) == Variant::NIL; + + if (is_untyped_dict) { + Button *edit_btn = memnew(Button); + edit_btn->set_icon(get_editor_theme_icon(SNAME("Edit"))); + edit_btn->set_disabled(is_read_only()); + edit_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_change_type).bind(edit_btn, slots.size())); + hbox->add_child(edit_btn); + } else if (p_idx >= 0) { + Button *remove_btn = memnew(Button); + remove_btn->set_icon(get_editor_theme_icon(SNAME("Remove"))); + remove_btn->set_disabled(is_read_only()); + remove_btn->connect(SceneStringName(pressed), callable_mp(this, &EditorPropertyDictionary::_remove_pressed).bind(slots.size())); + hbox->add_child(remove_btn); + } + if (add_panel) { add_panel->get_child(0)->add_child(hbox); } else { property_vbox->add_child(hbox); } + Slot slot; slot.prop = prop; slot.object = object; @@ -969,15 +1002,70 @@ void EditorPropertyDictionary::_change_type_menu(int p_index) { } } -void EditorPropertyDictionary::setup(PropertyHint p_hint) { - property_hint = p_hint; +void EditorPropertyDictionary::setup(PropertyHint p_hint, const String &p_hint_string) { + PackedStringArray types = p_hint_string.split(";"); + if (types.size() > 0 && !types[0].is_empty()) { + String key = types[0]; + int hint_key_subtype_separator = key.find(":"); + if (hint_key_subtype_separator >= 0) { + String key_subtype_string = key.substr(0, hint_key_subtype_separator); + int slash_pos = key_subtype_string.find("/"); + if (slash_pos >= 0) { + key_subtype_hint = PropertyHint(key_subtype_string.substr(slash_pos + 1, key_subtype_string.size() - slash_pos - 1).to_int()); + key_subtype_string = key_subtype_string.substr(0, slash_pos); + } + + key_subtype_hint_string = key.substr(hint_key_subtype_separator + 1, key.size() - hint_key_subtype_separator - 1); + key_subtype = Variant::Type(key_subtype_string.to_int()); + + Variant new_key = object->get_new_item_key(); + VariantInternal::initialize(&new_key, key_subtype); + object->set_new_item_key(new_key); + } + } + if (types.size() > 1 && !types[1].is_empty()) { + String value = types[1]; + int hint_value_subtype_separator = value.find(":"); + if (hint_value_subtype_separator >= 0) { + String value_subtype_string = value.substr(0, hint_value_subtype_separator); + int slash_pos = value_subtype_string.find("/"); + if (slash_pos >= 0) { + value_subtype_hint = PropertyHint(value_subtype_string.substr(slash_pos + 1, value_subtype_string.size() - slash_pos - 1).to_int()); + value_subtype_string = value_subtype_string.substr(0, slash_pos); + } + + value_subtype_hint_string = value.substr(hint_value_subtype_separator + 1, value.size() - hint_value_subtype_separator - 1); + value_subtype = Variant::Type(value_subtype_string.to_int()); + + Variant new_value = object->get_new_item_value(); + VariantInternal::initialize(&new_value, value_subtype); + object->set_new_item_value(new_value); + } + } } void EditorPropertyDictionary::update_property() { Variant updated_val = get_edited_property_value(); + String dict_type_name = "Dictionary"; + if (key_subtype != Variant::NIL || value_subtype != Variant::NIL) { + String key_subtype_name = "Variant"; + if (key_subtype == Variant::OBJECT && (key_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || key_subtype_hint == PROPERTY_HINT_NODE_TYPE)) { + key_subtype_name = key_subtype_hint_string; + } else if (key_subtype != Variant::NIL) { + key_subtype_name = Variant::get_type_name(key_subtype); + } + String value_subtype_name = "Variant"; + if (value_subtype == Variant::OBJECT && (value_subtype_hint == PROPERTY_HINT_RESOURCE_TYPE || value_subtype_hint == PROPERTY_HINT_NODE_TYPE)) { + value_subtype_name = value_subtype_hint_string; + } else if (value_subtype != Variant::NIL) { + value_subtype_name = Variant::get_type_name(value_subtype); + } + dict_type_name += vformat("[%s, %s]", key_subtype_name, value_subtype_name); + } + if (updated_val.get_type() != Variant::DICTIONARY) { - edit->set_text(TTR("Dictionary (Nil)")); // This provides symmetry with the array property. + edit->set_text(vformat(TTR("(Nil) %s"), dict_type_name)); // This provides symmetry with the array property. edit->set_pressed(false); if (container) { set_bottom_editor(nullptr); @@ -993,7 +1081,7 @@ void EditorPropertyDictionary::update_property() { Dictionary dict = updated_val; object->set_dict(updated_val); - edit->set_text(vformat(TTR("Dictionary (size %d)"), dict.size())); + edit->set_text(vformat(TTR("%s (size %d)"), dict_type_name, dict.size())); bool unfolded = get_edited_object()->editor_is_section_unfolded(get_edited_property()); if (edit->is_pressed() != unfolded) { @@ -1074,7 +1162,9 @@ void EditorPropertyDictionary::update_property() { editor->setup("Object"); new_prop = editor; } else { - new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE); + bool use_key = slot.index == EditorPropertyDictionaryObject::NEW_KEY_INDEX; + new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", use_key ? key_subtype_hint : value_subtype_hint, + use_key ? key_subtype_hint_string : value_subtype_hint_string, PROPERTY_USAGE_NONE); } new_prop->set_selectable(false); new_prop->set_use_folding(is_using_folding()); @@ -1108,6 +1198,13 @@ void EditorPropertyDictionary::update_property() { } } +void EditorPropertyDictionary::_remove_pressed(int p_slot_index) { + Dictionary dict = object->get_dict().duplicate(); + dict.erase(dict.get_key_at_index(p_slot_index)); + + emit_changed(get_edited_property(), dict); +} + void EditorPropertyDictionary::_object_id_selected(const StringName &p_property, ObjectID p_id) { emit_signal(SNAME("object_id_selected"), p_property, p_id); } @@ -1140,7 +1237,7 @@ void EditorPropertyDictionary::_notification(int p_what) { void EditorPropertyDictionary::_edit_pressed() { Variant prop_val = get_edited_property_value(); if (prop_val.get_type() == Variant::NIL && edit->is_pressed()) { - VariantInternal::initialize(&prop_val, Variant::DICTIONARY); + initialize_dictionary(prop_val); emit_changed(get_edited_property(), prop_val); } @@ -1187,6 +1284,14 @@ EditorPropertyDictionary::EditorPropertyDictionary() { change_type->connect(SceneStringName(id_pressed), callable_mp(this, &EditorPropertyDictionary::_change_type_menu)); changing_type_index = -1; has_borders = true; + + key_subtype = Variant::NIL; + key_subtype_hint = PROPERTY_HINT_NONE; + key_subtype_hint_string = ""; + + value_subtype = Variant::NIL; + value_subtype_hint = PROPERTY_HINT_NONE; + value_subtype_hint_string = ""; } ///////////////////// LOCALIZABLE STRING /////////////////////////// diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h index 024c04956f..84c3f975be 100644 --- a/editor/editor_properties_array_dict.h +++ b/editor/editor_properties_array_dict.h @@ -219,7 +219,6 @@ class EditorPropertyDictionary : public EditorProperty { EditorSpinSlider *size_sliderv = nullptr; Button *button_add_item = nullptr; EditorPaginator *paginator = nullptr; - PropertyHint property_hint; LocalVector<Slot> slots; void _create_new_property_slot(int p_idx); @@ -231,12 +230,21 @@ class EditorPropertyDictionary : public EditorProperty { void _add_key_value(); void _object_id_selected(const StringName &p_property, ObjectID p_id); + void _remove_pressed(int p_slot_index); + + Variant::Type key_subtype; + PropertyHint key_subtype_hint; + String key_subtype_hint_string; + Variant::Type value_subtype; + PropertyHint value_subtype_hint; + String value_subtype_hint_string; + void initialize_dictionary(Variant &p_dictionary); protected: void _notification(int p_what); public: - void setup(PropertyHint p_hint); + void setup(PropertyHint p_hint, const String &p_hint_string = ""); virtual void update_property() override; virtual bool is_colored(ColorationMode p_mode) override; EditorPropertyDictionary(); diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index 8935b9ad8a..f20dd992bb 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -175,6 +175,13 @@ void EditorResourcePicker::_file_quick_selected() { _file_selected(quick_open->get_selected()); } +void EditorResourcePicker::_resource_saved(Object *p_resource) { + if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) { + emit_signal(SNAME("resource_changed"), edited_resource); + _update_resource(); + } +} + void EditorResourcePicker::_update_menu() { _update_menu_items(); @@ -408,6 +415,10 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { if (edited_resource.is_null()) { return; } + Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved); + if (!EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) { + EditorNode::get_singleton()->connect("resource_saved", resource_saved); + } EditorNode::get_singleton()->save_resource_as(edited_resource); } break; @@ -833,6 +844,13 @@ void EditorResourcePicker::_notification(int p_what) { assign_button->queue_redraw(); } } break; + + case NOTIFICATION_EXIT_TREE: { + Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved); + if (EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) { + EditorNode::get_singleton()->disconnect("resource_saved", resource_saved); + } + } break; } } diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h index 28229e6b37..05e392da2c 100644 --- a/editor/editor_resource_picker.h +++ b/editor/editor_resource_picker.h @@ -91,6 +91,8 @@ class EditorResourcePicker : public HBoxContainer { void _file_quick_selected(); void _file_selected(const String &p_path); + void _resource_saved(Object *p_resource); + void _update_menu(); void _update_menu_items(); void _edit_menu_cbk(int p_which); diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index 71865f7e8c..956fdc5cfa 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -221,7 +221,9 @@ void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref< r_small_texture->set_image(small_image); } - break; + if (generated.is_valid()) { + break; + } } if (!p_item.resource.is_valid()) { diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index b5c11b574e..b1a91fa3dd 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -593,6 +593,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "docks/filesystem/thumbnail_size", 64, "32,128,16") _initial_set("docks/filesystem/always_show_folders", true); _initial_set("docks/filesystem/textfile_extensions", "txt,md,cfg,ini,log,json,yml,yaml,toml,xml"); + _initial_set("docks/filesystem/other_file_extensions", "ico,icns"); // Property editor EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "docks/property_editor/auto_refresh_interval", 0.2, "0.01,1,0.001"); // Update 5 times per second by default. @@ -824,7 +825,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/screen", -5, screen_hints) #endif // Should match the ANDROID_WINDOW_* constants in 'platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt' - String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2"; + String android_window_hints = "Auto (based on screen size):0,Same as Editor:1,Side-by-side with Editor:2,Launch in PiP mode:3"; EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "run/window_placement/android_window", 0, android_window_hints) int default_play_window_pip_mode = 0; @@ -871,6 +872,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/auto_switch_to_remote_scene_tree", false, "") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_history_size", 3600, "60,10000,1") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_frame_max_functions", 64, "16,512,1") + EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "debugger/profiler_target_fps", 60, "1,1000,1") EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "debugger/remote_scene_tree_refresh_interval", 1.0, "0.1,10,0.01,or_greater") EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_RANGE, "debugger/remote_inspect_refresh_interval", 0.2, "0.02,10,0.01,or_greater") EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "debugger/profile_native_calls", false, "") diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp index c0bf216634..2e96ae82fc 100644 --- a/editor/editor_undo_redo_manager.cpp +++ b/editor/editor_undo_redo_manager.cpp @@ -177,7 +177,7 @@ void EditorUndoRedoManager::_add_do_method(const Variant **p_args, int p_argcoun return; } - if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) { + if (!p_args[1]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 1; r_error.expected = Variant::STRING_NAME; @@ -206,7 +206,7 @@ void EditorUndoRedoManager::_add_undo_method(const Variant **p_args, int p_argco return; } - if (p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING) { + if (!p_args[1]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 1; r_error.expected = Variant::STRING_NAME; diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index f56dd61e3b..6bb21d7fd4 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -140,7 +140,7 @@ Vector<String> EditorExportPlugin::get_ios_project_static_libs() const { } Variant EditorExportPlugin::get_option(const StringName &p_name) const { - ERR_FAIL_NULL_V(export_preset, Variant()); + ERR_FAIL_COND_V(export_preset.is_null(), Variant()); return export_preset->get(p_name); } diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index 2e4f2da9a8..5360b6fb60 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -571,7 +571,7 @@ bool ExportTemplateManager::_install_file_selected(const String &p_file, bool p_ unzClose(pkg); _update_template_status(); - EditorSettings::get_singleton()->set_meta("export_template_download_directory", p_file.get_base_dir()); + EditorSettings::get_singleton()->set("_export_template_download_directory", p_file.get_base_dir()); return true; } @@ -1102,7 +1102,7 @@ ExportTemplateManager::ExportTemplateManager() { install_file_dialog->set_title(TTR("Select Template File")); install_file_dialog->set_access(FileDialog::ACCESS_FILESYSTEM); install_file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); - install_file_dialog->set_current_dir(EditorSettings::get_singleton()->get_meta("export_template_download_directory", "")); + install_file_dialog->set_current_dir(EDITOR_DEF("_export_template_download_directory", "")); install_file_dialog->add_filter("*.tpz", TTR("Godot Export Templates")); install_file_dialog->connect("file_selected", callable_mp(this, &ExportTemplateManager::_install_file_selected).bind(false)); add_child(install_file_dialog); diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index 03e9fba12d..3ad8ab0b19 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -903,7 +903,7 @@ bool ProjectExportDialog::_fill_tree(EditorFileSystemDirectory *p_dir, TreeItem if (p_export_filter == EditorExportPreset::EXPORT_SELECTED_SCENES && type != "PackedScene") { continue; } - if (type == "TextFile") { + if (type == "TextFile" || type == "OtherFile") { continue; } diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index bfb7123925..f7e81b329c 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -49,6 +49,7 @@ #include "editor/gui/editor_scene_tabs.h" #include "editor/import/3d/scene_import_settings.h" #include "editor/import_dock.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/editor_resource_tooltip_plugins.h" #include "editor/scene_create_dialog.h" #include "editor/scene_tree_dock.h" @@ -270,7 +271,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory List<FileInfo> file_list; for (int i = 0; i < p_dir->get_file_count(); i++) { String file_type = p_dir->get_file_type(i); - if (file_type != "TextFile" && _is_file_type_disabled_by_feature_profile(file_type)) { + if (file_type != "TextFile" && file_type != "OtherFile" && _is_file_type_disabled_by_feature_profile(file_type)) { // If type is disabled, file won't be displayed. continue; } @@ -2556,6 +2557,9 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected String dir = ProjectSettings::get_singleton()->globalize_path(fpath); ScriptEditor::get_singleton()->open_text_file_create_dialog(dir); } break; + default: + EditorNode::get_editor_data().filesystem_options_pressed(EditorData::CONTEXT_SLOT_FILESYSTEM, p_option, p_selected); + break; } } @@ -3178,6 +3182,8 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect new_menu->add_icon_item(get_editor_theme_icon(SNAME("Script")), TTR("Script..."), FILE_NEW_SCRIPT); new_menu->add_icon_item(get_editor_theme_icon(SNAME("Object")), TTR("Resource..."), FILE_NEW_RESOURCE); new_menu->add_icon_item(get_editor_theme_icon(SNAME("TextFile")), TTR("TextFile..."), FILE_NEW_TEXTFILE); + + EditorNode::get_editor_data().add_options_from_plugins(new_menu, EditorData::CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, p_paths); p_popup->add_separator(); } @@ -3317,6 +3323,8 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, const Vect current_path = fpath; } + + EditorNode::get_editor_data().add_options_from_plugins(p_popup, EditorData::CONTEXT_SLOT_FILESYSTEM, p_paths); } void FileSystemDock::_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) { @@ -3410,6 +3418,11 @@ void FileSystemDock::_file_list_empty_clicked(const Vector2 &p_pos, MouseButton current_path = current_path_line_edit->get_text(); + // Favorites isn't a directory so don't show menu. + if (current_path == "Favorites") { + return; + } + file_list_popup->clear(); file_list_popup->reset_size(); @@ -3546,6 +3559,10 @@ void FileSystemDock::_tree_gui_input(Ref<InputEvent> p_event) { } else if (ED_IS_SHORTCUT("editor/open_search", p_event)) { focus_on_filter(); } else { + int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_FILESYSTEM, p_event); + if (match_option) { + _tree_rmb_option(match_option); + } return; } diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index 7aa19509e1..b6aa3c2215 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -115,6 +115,8 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_file file_name = ProjectSettings::get_singleton()->localize_path(file_name); } } + selected_options = p_selected_options; + String f = files[0]; if (mode == FILE_MODE_OPEN_FILES) { emit_signal(SNAME("files_selected"), files); @@ -146,7 +148,6 @@ void EditorFileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_file } file->set_text(f); dir->set_text(f.get_base_dir()); - selected_options = p_selected_options; filter->select(p_filter); } diff --git a/editor/gui/editor_object_selector.cpp b/editor/gui/editor_object_selector.cpp index bfd7e18de1..b73cd3b65c 100644 --- a/editor/gui/editor_object_selector.cpp +++ b/editor/gui/editor_object_selector.cpp @@ -103,7 +103,6 @@ void EditorObjectSelector::_show_popup() { sub_objects_menu->set_position(gp); sub_objects_menu->set_size(Size2(size.width, 1)); - sub_objects_menu->take_mouse_focus(); sub_objects_menu->popup(); } diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 5372d33b4c..d49d597084 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -185,10 +185,6 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; - if (is_read_only()) { - return; - } - if (grabbing_grabber) { if (mb.is_valid()) { if (mb->get_button_index() == MouseButton::WHEEL_UP) { @@ -241,7 +237,7 @@ void EditorSpinSlider::_grabber_gui_input(const Ref<InputEvent> &p_event) { void EditorSpinSlider::_value_input_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; - if (k.is_valid() && k->is_pressed() && !is_read_only()) { + if (k.is_valid() && k->is_pressed() && !read_only) { Key code = k->get_keycode(); switch (code) { @@ -315,7 +311,7 @@ void EditorSpinSlider::_draw_spin_slider() { bool rtl = is_layout_rtl(); Vector2 size = get_size(); - Ref<StyleBox> sb = get_theme_stylebox(is_read_only() ? SNAME("read_only") : CoreStringName(normal), SNAME("LineEdit")); + Ref<StyleBox> sb = get_theme_stylebox(read_only ? SNAME("read_only") : CoreStringName(normal), SNAME("LineEdit")); if (!flat) { draw_style_box(sb, Rect2(Vector2(), size)); } @@ -327,14 +323,14 @@ void EditorSpinSlider::_draw_spin_slider() { int label_width = font->get_string_size(label, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size).width; int number_width = size.width - sb->get_minimum_size().width - label_width - sep; - Ref<Texture2D> updown = get_theme_icon(is_read_only() ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox")); + Ref<Texture2D> updown = get_theme_icon(read_only ? SNAME("updown_disabled") : SNAME("updown"), SNAME("SpinBox")); String numstr = get_text_value(); int vofs = (size.height - font->get_height(font_size)) / 2 + font->get_ascent(font_size); - Color fc = get_theme_color(is_read_only() ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit")); - Color lc = get_theme_color(is_read_only() ? SNAME("read_only_label_color") : SNAME("label_color")); + Color fc = get_theme_color(read_only ? SNAME("font_uneditable_color") : SceneStringName(font_color), SNAME("LineEdit")); + Color lc = get_theme_color(read_only ? SNAME("read_only_label_color") : SNAME("label_color")); if (flat && !label.is_empty()) { Ref<StyleBox> label_bg = get_theme_stylebox(SNAME("label_bg"), SNAME("EditorSpinSlider")); @@ -384,7 +380,7 @@ void EditorSpinSlider::_draw_spin_slider() { if (!hide_slider) { if (get_step() == 1) { - Ref<Texture2D> updown2 = is_read_only() ? theme_cache.updown_disabled_icon : theme_cache.updown_icon; + Ref<Texture2D> updown2 = read_only ? theme_cache.updown_disabled_icon : theme_cache.updown_icon; int updown_vofs = (size.height - updown2->get_height()) / 2; if (rtl) { updown_offset = sb->get_margin(SIDE_LEFT); @@ -604,7 +600,7 @@ void EditorSpinSlider::_value_focus_exited() { return; } - if (is_read_only()) { + if (read_only) { // Spin slider has become read only while it was being edited. return; } @@ -665,6 +661,10 @@ bool EditorSpinSlider::is_grabbing() const { } void EditorSpinSlider::_focus_entered() { + if (read_only) { + return; + } + _ensure_input_popup(); value_input->set_text(get_text_value()); value_input_popup->set_size(get_size()); diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 52ba98b4d5..a7a14de527 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -174,15 +174,40 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i EditorDockManager::get_singleton()->focus_dock(NodeDock::get_singleton()); NodeDock::get_singleton()->show_groups(); } else if (p_id == BUTTON_UNIQUE) { - undo_redo->create_action(TTR("Disable Scene Unique Name")); - undo_redo->add_do_method(n, "set_unique_name_in_owner", false); - undo_redo->add_undo_method(n, "set_unique_name_in_owner", true); - undo_redo->add_do_method(this, "_update_tree"); - undo_redo->add_undo_method(this, "_update_tree"); - undo_redo->commit_action(); + bool ask_before_revoking_unique_name = EDITOR_GET("docks/scene_tree/ask_before_revoking_unique_name"); + revoke_node = n; + if (ask_before_revoking_unique_name) { + String msg = vformat(TTR("Revoke unique name for node \"%s\"?"), n->get_name()); + ask_before_revoke_checkbox->set_pressed(false); + revoke_dialog_label->set_text(msg); + revoke_dialog->reset_size(); + revoke_dialog->popup_centered(); + } else { + _revoke_unique_name(); + } } } +void SceneTreeEditor::_update_ask_before_revoking_unique_name() { + if (ask_before_revoke_checkbox->is_pressed()) { + EditorSettings::get_singleton()->set("docks/scene_tree/ask_before_revoking_unique_name", false); + ask_before_revoke_checkbox->set_pressed(false); + } + + _revoke_unique_name(); +} + +void SceneTreeEditor::_revoke_unique_name() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + undo_redo->create_action(TTR("Disable Scene Unique Name")); + undo_redo->add_do_method(revoke_node, "set_unique_name_in_owner", false); + undo_redo->add_undo_method(revoke_node, "set_unique_name_in_owner", true); + undo_redo->add_do_method(this, "_update_tree"); + undo_redo->add_undo_method(this, "_update_tree"); + undo_redo->commit_action(); +} + void SceneTreeEditor::_toggle_visible(Node *p_node) { if (p_node->has_method("is_visible") && p_node->has_method("set_visible")) { bool v = bool(p_node->call("is_visible")); @@ -491,10 +516,14 @@ void SceneTreeEditor::_update_node_tooltip(Node *p_node, TreeItem *p_item) { String tooltip = p_node->get_name(); if (p_node == get_scene_node() && p_node->get_scene_inherited_state().is_valid()) { - p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) { + p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + } tooltip += String("\n" + TTR("Inherits:") + " " + p_node->get_scene_inherited_state()->get_path()); } else if (p_node != get_scene_node() && !p_node->get_scene_file_path().is_empty() && can_open_instance) { - p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + if (p_item->get_button_by_id(0, BUTTON_SUBSCENE) == -1) { + p_item->add_button(0, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_SUBSCENE, false, TTR("Open in Editor")); + } tooltip += String("\n" + TTR("Instance:") + " " + p_node->get_scene_file_path()); } @@ -1620,6 +1649,18 @@ SceneTreeEditor::SceneTreeEditor(bool p_label, bool p_can_rename, bool p_can_ope update_node_tooltip_delay->set_one_shot(true); add_child(update_node_tooltip_delay); + revoke_dialog = memnew(ConfirmationDialog); + revoke_dialog->set_ok_button_text(TTR("Revoke")); + add_child(revoke_dialog); + revoke_dialog->connect(SceneStringName(confirmed), callable_mp(this, &SceneTreeEditor::_update_ask_before_revoking_unique_name)); + VBoxContainer *vb = memnew(VBoxContainer); + revoke_dialog->add_child(vb); + revoke_dialog_label = memnew(Label); + vb->add_child(revoke_dialog_label); + ask_before_revoke_checkbox = memnew(CheckBox(TTR("Don't Ask Again"))); + ask_before_revoke_checkbox->set_tooltip_text(TTR("This dialog can also be enabled/disabled in the Editor Settings: Docks > Scene Tree > Ask Before Revoking Unique Name.")); + vb->add_child(ask_before_revoke_checkbox); + script_types = memnew(List<StringName>); ClassDB::get_inheriters_from_class("Script", script_types); } diff --git a/editor/gui/scene_tree_editor.h b/editor/gui/scene_tree_editor.h index b4d9644f16..9633993b8b 100644 --- a/editor/gui/scene_tree_editor.h +++ b/editor/gui/scene_tree_editor.h @@ -31,6 +31,7 @@ #ifndef SCENE_TREE_EDITOR_H #define SCENE_TREE_EDITOR_H +#include "scene/gui/check_box.h" #include "scene/gui/check_button.h" #include "scene/gui/dialogs.h" #include "scene/gui/tree.h" @@ -68,6 +69,11 @@ class SceneTreeEditor : public Control { AcceptDialog *error = nullptr; AcceptDialog *warning = nullptr; + ConfirmationDialog *revoke_dialog = nullptr; + Label *revoke_dialog_label = nullptr; + CheckBox *ask_before_revoke_checkbox = nullptr; + Node *revoke_node = nullptr; + bool auto_expand_selected = true; bool connect_to_script_mode = false; bool connecting_signal = false; @@ -144,6 +150,9 @@ class SceneTreeEditor : public Control { Vector<StringName> valid_types; + void _update_ask_before_revoking_unique_name(); + void _revoke_unique_name(); + public: // Public for use with callable_mp. void _update_tree(bool p_scroll_to_selected = false); diff --git a/editor/icons/SnapKeys.svg b/editor/icons/SnapKeys.svg new file mode 100644 index 0000000000..813781b801 --- /dev/null +++ b/editor/icons/SnapKeys.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 7,13 v 2 h 2 v -2 z m 6,0 v 2 h 2 v -2 z"/><path fill="#fff" fill-opacity=".686" d="m 7,13 h 2 v -2 a 2,2 0 0 1 4,0 v 2 h 2 v -2 a 4,4 0 0 0 -8,0 z"/><path fill="#fff" d="M 5,1 1,5 5,9 9,5 Z"/></svg>
\ No newline at end of file diff --git a/editor/icons/SnapTimeline.svg b/editor/icons/SnapTimeline.svg new file mode 100644 index 0000000000..b558545f5a --- /dev/null +++ b/editor/icons/SnapTimeline.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="m 7,13 v 2 h 2 v -2 z m 6,0 v 2 h 2 v -2 z"/><path fill="#fff" fill-opacity=".686" d="m 7,13 h 2 v -2 a 2,2 0 0 1 4,0 v 2 h 2 v -2 a 4,4 0 0 0 -8,0 z"/><path fill="#fff" d="m 1,2 3,3 v 9 H 6 V 5 L 9,2 Z"/></svg>
\ No newline at end of file diff --git a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp index a8886b3818..64bec0532b 100644 --- a/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp @@ -48,7 +48,7 @@ void PostImportPluginSkeletonRestFixer::get_internal_import_options(InternalImpo // TODO: PostImportPlugin need to be implemented such as validate_option(PropertyInfo &property, const Dictionary &p_options). // get_internal_option_visibility() is not sufficient because it can only retrieve options implemented in the core and can only read option values. // r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/filter", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::STRING_NAME, PROPERTY_HINT_ENUM, "Hips,Spine,Chest")), Array())); - r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/fix_silhouette/filter", PROPERTY_HINT_ARRAY_TYPE, "StringName"), Array())); + r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::ARRAY, "retarget/rest_fixer/fix_silhouette/filter", PROPERTY_HINT_ARRAY_TYPE, vformat("%s:", Variant::STRING_NAME)), Array())); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/threshold"), 15)); r_options->push_back(ResourceImporter::ImportOption(PropertyInfo(Variant::FLOAT, "retarget/rest_fixer/fix_silhouette/base_height_adjustment", PROPERTY_HINT_RANGE, "-1,1,0.01"), 0.0)); } diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index fa07511dd0..5c28213ca7 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -446,7 +446,7 @@ static String _fixstr(const String &p_what, const String &p_str) { } static void _pre_gen_shape_list(Ref<ImporterMesh> &mesh, Vector<Ref<Shape3D>> &r_shape_list, bool p_convex) { - ERR_FAIL_NULL_MSG(mesh, "Cannot generate shape list with null mesh value."); + ERR_FAIL_COND_MSG(mesh.is_null(), "Cannot generate shape list with null mesh value."); if (!p_convex) { Ref<ConcavePolygonShape3D> shape = mesh->create_trimesh_shape(); r_shape_list.push_back(shape); diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 7ca3cb6c3a..011d0135b4 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -37,6 +37,7 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/gui/editor_file_dialog.h" +#include "editor/plugins/skeleton_3d_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/animation/animation_player.h" @@ -419,7 +420,9 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite animation_player->connect(SceneStringName(animation_finished), callable_mp(this, &SceneImportSettingsDialog::_animation_finished)); } else if (Object::cast_to<Skeleton3D>(p_node)) { category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE; - skeletons.push_back(Object::cast_to<Skeleton3D>(p_node)); + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node); + skeleton->connect(SceneStringName(tree_entered), callable_mp(this, &SceneImportSettingsDialog::_skeleton_tree_entered).bind(skeleton)); + skeletons.push_back(skeleton); } else { category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE; } @@ -480,6 +483,31 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite contents_aabb.merge_with(aabb); } } + + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_node); + if (skeleton) { + Ref<ArrayMesh> bones_mesh = Skeleton3DGizmoPlugin::get_bones_mesh(skeleton, -1, true); + + bones_mesh_preview->set_mesh(bones_mesh); + + Transform3D accum_xform; + Node3D *base = skeleton; + while (base) { + accum_xform = base->get_transform() * accum_xform; + base = Object::cast_to<Node3D>(base->get_parent()); + } + + bones_mesh_preview->set_transform(accum_xform * skeleton->get_transform()); + + AABB aabb = accum_xform.xform(bones_mesh->get_aabb()); + + if (first_aabb) { + contents_aabb = aabb; + first_aabb = false; + } else { + contents_aabb.merge_with(aabb); + } + } } void SceneImportSettingsDialog::_update_scene() { @@ -795,6 +823,7 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons selecting = true; scene_import_settings_data->hide_options = false; + bones_mesh_preview->hide(); if (p_type == "Node") { node_selected->hide(); // Always hide just in case. mesh_preview->hide(); @@ -834,6 +863,7 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION_NODE; } else if (Object::cast_to<Skeleton3D>(nd.node)) { scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_SKELETON_3D_NODE; + bones_mesh_preview->show(); } else { scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_NODE; scene_import_settings_data->hide_options = editing_animation; @@ -853,6 +883,8 @@ void SceneImportSettingsDialog::_select(Tree *p_from, const String &p_type, cons scene_import_settings_data->settings = &ad.settings; scene_import_settings_data->category = ResourceImporterScene::INTERNAL_IMPORT_CATEGORY_ANIMATION; + + _animation_update_skeleton_visibility(); } else if (p_type == "Mesh") { node_selected->hide(); if (Object::cast_to<Node3D>(scene)) { @@ -1055,6 +1087,11 @@ void SceneImportSettingsDialog::_animation_slider_value_changed(double p_value) animation_player->seek(p_value * animation_map[selected_id].animation->get_length(), true); } +void SceneImportSettingsDialog::_skeleton_tree_entered(Skeleton3D *p_skeleton) { + bones_mesh_preview->set_skeleton_path(p_skeleton->get_path()); + bones_mesh_preview->set_skin(p_skeleton->register_skin(p_skeleton->create_skin_from_rest_transforms())); +} + void SceneImportSettingsDialog::_animation_finished(const StringName &p_name) { Animation::LoopMode loop_mode = animation_loop_mode; @@ -1080,6 +1117,14 @@ void SceneImportSettingsDialog::_animation_finished(const StringName &p_name) { } } +void SceneImportSettingsDialog::_animation_update_skeleton_visibility() { + if (animation_toggle_skeleton_visibility->is_pressed()) { + bones_mesh_preview->show(); + } else { + bones_mesh_preview->hide(); + } +} + void SceneImportSettingsDialog::_material_tree_selected() { if (selecting) { return; @@ -1282,6 +1327,8 @@ void SceneImportSettingsDialog::_notification(int p_what) { light_1_switch->set_icon(theme_cache.light_1_icon); light_2_switch->set_icon(theme_cache.light_2_icon); light_rotate_switch->set_icon(theme_cache.rotate_icon); + + animation_toggle_skeleton_visibility->set_icon(get_editor_theme_icon(SNAME("Skeleton3D"))); } break; case NOTIFICATION_PROCESS: { @@ -1661,6 +1708,7 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { animation_hbox->add_child(animation_stop_button); animation_stop_button->set_flat(true); animation_stop_button->set_focus_mode(Control::FOCUS_NONE); + animation_stop_button->set_tooltip_text(TTR("Selected Animation Stop")); animation_stop_button->connect(SceneStringName(pressed), callable_mp(this, &SceneImportSettingsDialog::_stop_current_animation)); animation_slider = memnew(HSlider); @@ -1673,6 +1721,15 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { animation_slider->set_focus_mode(Control::FOCUS_NONE); animation_slider->connect(SceneStringName(value_changed), callable_mp(this, &SceneImportSettingsDialog::_animation_slider_value_changed)); + animation_toggle_skeleton_visibility = memnew(Button); + animation_hbox->add_child(animation_toggle_skeleton_visibility); + animation_toggle_skeleton_visibility->set_toggle_mode(true); + animation_toggle_skeleton_visibility->set_flat(true); + animation_toggle_skeleton_visibility->set_focus_mode(Control::FOCUS_NONE); + animation_toggle_skeleton_visibility->set_tooltip_text(TTR("Toggle Animation Skeleton Visibility")); + + animation_toggle_skeleton_visibility->connect(SceneStringName(pressed), callable_mp(this, &SceneImportSettingsDialog::_animation_update_skeleton_visibility)); + base_viewport->set_use_own_world_3d(true); HBoxContainer *viewport_hbox = memnew(HBoxContainer); @@ -1800,6 +1857,13 @@ SceneImportSettingsDialog::SceneImportSettingsDialog() { collider_mat->set_albedo(Color(0.5, 0.5, 1.0)); } + { + bones_mesh_preview = memnew(MeshInstance3D); + bones_mesh_preview->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); + bones_mesh_preview->set_skeleton_path(NodePath()); + base_viewport->add_child(bones_mesh_preview); + } + inspector = memnew(EditorInspector); inspector->set_custom_minimum_size(Size2(300 * EDSCALE, 0)); inspector->connect(SNAME("property_edited"), callable_mp(this, &SceneImportSettingsDialog::_inspector_property_edited)); diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h index bbd0d2c22d..1088acf772 100644 --- a/editor/import/3d/scene_import_settings.h +++ b/editor/import/3d/scene_import_settings.h @@ -109,10 +109,12 @@ class SceneImportSettingsDialog : public ConfirmationDialog { HSlider *animation_slider = nullptr; Button *animation_play_button = nullptr; Button *animation_stop_button = nullptr; + Button *animation_toggle_skeleton_visibility = nullptr; Animation::LoopMode animation_loop_mode = Animation::LOOP_NONE; bool animation_pingpong = false; bool previous_import_as_skeleton = false; bool previous_rest_as_reset = false; + MeshInstance3D *bones_mesh_preview = nullptr; Ref<StandardMaterial3D> collider_mat; @@ -187,9 +189,11 @@ class SceneImportSettingsDialog : public ConfirmationDialog { void _reset_animation(const String &p_animation_name = ""); void _animation_slider_value_changed(double p_value); void _animation_finished(const StringName &p_name); + void _animation_update_skeleton_visibility(); void _material_tree_selected(); void _mesh_tree_selected(); void _scene_tree_selected(); + void _skeleton_tree_entered(Skeleton3D *p_skeleton); void _cleanup(); void _on_light_1_switch_pressed(); void _on_light_2_switch_pressed(); diff --git a/editor/import/dynamic_font_import_settings.cpp b/editor/import/dynamic_font_import_settings.cpp index 590e3a9ede..c526ca0b0c 100644 --- a/editor/import/dynamic_font_import_settings.cpp +++ b/editor/import/dynamic_font_import_settings.cpp @@ -509,7 +509,7 @@ void DynamicFontImportSettingsDialog::_variation_add() { Ref<DynamicFontImportSettingsData> import_variation_data; import_variation_data.instantiate(); import_variation_data->owner = this; - ERR_FAIL_NULL(import_variation_data); + ERR_FAIL_COND(import_variation_data.is_null()); for (List<ResourceImporter::ImportOption>::Element *E = options_variations.front(); E; E = E->next()) { import_variation_data->defaults[E->get().option.name] = E->get().default_value; @@ -529,7 +529,7 @@ void DynamicFontImportSettingsDialog::_variation_selected() { TreeItem *vars_item = vars_list->get_selected(); if (vars_item) { Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0); - ERR_FAIL_NULL(import_variation_data); + ERR_FAIL_COND(import_variation_data.is_null()); inspector_vars->edit(import_variation_data.ptr()); import_variation_data->notify_property_list_changed(); @@ -588,14 +588,14 @@ void DynamicFontImportSettingsDialog::_variations_validate() { } for (TreeItem *vars_item_a = vars_list_root->get_first_child(); vars_item_a; vars_item_a = vars_item_a->get_next()) { Ref<DynamicFontImportSettingsData> import_variation_data_a = vars_item_a->get_metadata(0); - ERR_FAIL_NULL(import_variation_data_a); + ERR_FAIL_COND(import_variation_data_a.is_null()); for (TreeItem *vars_item_b = vars_list_root->get_first_child(); vars_item_b; vars_item_b = vars_item_b->get_next()) { if (vars_item_b != vars_item_a) { bool match = true; for (const KeyValue<StringName, Variant> &E : import_variation_data_a->settings) { Ref<DynamicFontImportSettingsData> import_variation_data_b = vars_item_b->get_metadata(0); - ERR_FAIL_NULL(import_variation_data_b); + ERR_FAIL_COND(import_variation_data_b.is_null()); match = match && (import_variation_data_b->settings[E.key] == E.value); } if (match) { @@ -956,7 +956,7 @@ void DynamicFontImportSettingsDialog::_re_import() { Array configurations; for (TreeItem *vars_item = vars_list_root->get_first_child(); vars_item; vars_item = vars_item->get_next()) { Ref<DynamicFontImportSettingsData> import_variation_data = vars_item->get_metadata(0); - ERR_FAIL_NULL(import_variation_data); + ERR_FAIL_COND(import_variation_data.is_null()); Dictionary preload_config; preload_config["name"] = vars_item->get_text(0); @@ -1107,7 +1107,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) { inspector_general->edit(nullptr); text_settings_data.instantiate(); - ERR_FAIL_NULL(text_settings_data); + ERR_FAIL_COND(text_settings_data.is_null()); text_settings_data->owner = this; @@ -1137,7 +1137,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) { Ref<ConfigFile> config; config.instantiate(); - ERR_FAIL_NULL(config); + ERR_FAIL_COND(config.is_null()); Error err = config->load(p_path + ".import"); print_verbose("Loading import settings:"); @@ -1169,7 +1169,7 @@ void DynamicFontImportSettingsDialog::open_settings(const String &p_path) { Ref<DynamicFontImportSettingsData> import_variation_data_custom; import_variation_data_custom.instantiate(); - ERR_FAIL_NULL(import_variation_data_custom); + ERR_FAIL_COND(import_variation_data_custom.is_null()); import_variation_data_custom->owner = this; for (List<ResourceImporter::ImportOption>::Element *F = options_variations.front(); F; F = F->next()) { diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp index 212a4160bf..552815a6af 100644 --- a/editor/import/resource_importer_layered_texture.cpp +++ b/editor/import/resource_importer_layered_texture.cpp @@ -510,7 +510,7 @@ void ResourceImporterLayeredTexture::_check_compress_ctex(const String &p_source } bool can_compress_hdr = r_texture_import->hdr_compression > 0; - ERR_FAIL_NULL(r_texture_import->image); + ERR_FAIL_COND(r_texture_import->image.is_null()); bool is_hdr = (r_texture_import->image->get_format() >= Image::FORMAT_RF && r_texture_import->image->get_format() <= Image::FORMAT_RGBE9995); ERR_FAIL_NULL(r_texture_import->slices); // Can compress hdr, but hdr with alpha is not compressible. diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp index ce403a66de..77b3629b07 100644 --- a/editor/import/resource_importer_wav.cpp +++ b/editor/import/resource_importer_wav.cpp @@ -90,7 +90,8 @@ void ResourceImporterWAV::get_import_options(const String &p_path, List<ImportOp r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_mode", PROPERTY_HINT_ENUM, "Detect From WAV,Disabled,Forward,Ping-Pong,Backward", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_begin"), 0)); r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "edit/loop_end"), -1)); - r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "PCM (Uncompressed),IMA ADPCM,Quite OK Audio"), 0)); + // Quite OK Audio is lightweight enough and supports virtually every significant AudioStreamWAV feature. + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "PCM (Uncompressed),IMA ADPCM,Quite OK Audio"), 2)); } Error ResourceImporterWAV::import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata) { diff --git a/editor/import_dock.cpp b/editor/import_dock.cpp index a8f8e9ef11..16f4aeda95 100644 --- a/editor/import_dock.cpp +++ b/editor/import_dock.cpp @@ -171,7 +171,7 @@ void ImportDock::_add_keep_import_option(const String &p_importer_name) { void ImportDock::_update_options(const String &p_path, const Ref<ConfigFile> &p_config) { // Set the importer class to fetch the correct class in the XML class reference. // This allows tooltips to display when hovering properties. - if (params->importer != nullptr) { + if (params->importer.is_valid()) { // Null check to avoid crashing if the "Keep File (exported as is)" mode is selected. import_opts->set_object_class(params->importer->get_class_name()); } diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 1056c8abd0..5cb558abbe 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -224,6 +224,69 @@ void AnimationPlayerEditor::_autoplay_pressed() { } } +void AnimationPlayerEditor::_go_to_nearest_keyframe(bool p_backward) { + if (_get_current().is_empty()) { + return; + } + + Ref<Animation> anim = player->get_animation(player->get_assigned_animation()); + + double current_time = player->get_current_animation_position(); + // Offset the time to avoid finding the same keyframe with Animation::track_find_key(). + double time_offset = MAX(CMP_EPSILON * 2, current_time * CMP_EPSILON * 2); + double current_time_offset = current_time + (p_backward ? -time_offset : time_offset); + + float nearest_key_time = p_backward ? 0 : anim->get_length(); + int track_count = anim->get_track_count(); + bool bezier_active = track_editor->is_bezier_editor_active(); + + Node *root = get_tree()->get_edited_scene_root(); + EditorSelection *selection = EditorNode::get_singleton()->get_editor_selection(); + + Vector<int> selected_tracks; + for (int i = 0; i < track_count; ++i) { + if (selection->is_selected(root->get_node_or_null(anim->track_get_path(i)))) { + selected_tracks.push_back(i); + } + } + + // Find the nearest keyframe in selection if the scene has selected nodes + // or the nearest keyframe in the entire animation otherwise. + if (selected_tracks.size() > 0) { + for (int track : selected_tracks) { + if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) { + continue; + } + int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward); + if (key == -1) { + continue; + } + double key_time = anim->track_get_key_time(track, key); + if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) { + nearest_key_time = key_time; + } + } + } else { + for (int track = 0; track < track_count; ++track) { + if (bezier_active && anim->track_get_type(track) != Animation::TYPE_BEZIER) { + continue; + } + int key = anim->track_find_key(track, current_time_offset, Animation::FIND_MODE_NEAREST, false, !p_backward); + if (key == -1) { + continue; + } + double key_time = anim->track_get_key_time(track, key); + if ((p_backward && key_time > nearest_key_time) || (!p_backward && key_time < nearest_key_time)) { + nearest_key_time = key_time; + } + } + } + + player->seek_internal(nearest_key_time, true, true, true); + frame->set_value(nearest_key_time); + track_editor->set_anim_pos(nearest_key_time); +} + void AnimationPlayerEditor::_play_pressed() { String current = _get_current(); @@ -339,7 +402,17 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { track_editor->set_animation(anim, animation_is_readonly); Node *root = player->get_node_or_null(player->get_root_node()); - if (root) { + + // Player shouldn't access parent if it's the scene root. + if (!root || (player == get_tree()->get_edited_scene_root() && player->get_root_node() == SceneStringName(path_pp))) { + NodePath cached_root_path = player->get_path_to(get_cached_root_node()); + if (player->get_node_or_null(cached_root_path) != nullptr) { + player->set_root_node(cached_root_path); + } else { + player->set_root_node(SceneStringName(path_pp)); // No other choice, preventing crash. + } + } else { + cached_root_node_id = root->get_instance_id(); // Caching as `track_editor` can lose track of player's root node. track_editor->set_root(root); } } @@ -474,17 +547,12 @@ void AnimationPlayerEditor::_select_anim_by_name(const String &p_anim) { } float AnimationPlayerEditor::_get_editor_step() const { - // Returns the effective snapping value depending on snapping modifiers, or 0 if snapping is disabled. - if (track_editor->is_snap_enabled()) { - const String current = player->get_assigned_animation(); - const Ref<Animation> anim = player->get_animation(current); - ERR_FAIL_COND_V(!anim.is_valid(), 0.0); + const String current = player->get_assigned_animation(); + const Ref<Animation> anim = player->get_animation(current); + ERR_FAIL_COND_V(anim.is_null(), 0.0); - // Use more precise snapping when holding Shift - return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step(); - } - - return 0.0f; + // Use more precise snapping when holding Shift + return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step(); } void AnimationPlayerEditor::_animation_name_edited() { @@ -1290,7 +1358,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_timeline_o anim = player->get_animation(current); double pos = CLAMP((double)anim->get_length() * (p_value / frame->get_max()), 0, (double)anim->get_length()); - if (track_editor->is_snap_enabled()) { + if (track_editor->is_snap_timeline_enabled()) { pos = Math::snapped(pos, _get_editor_step()); } pos = CLAMP(pos, 0, (double)anim->get_length() - CMP_EPSILON2); // Hack: Avoid fposmod with LOOP_LINEAR. @@ -1408,7 +1476,7 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timel } updating = true; - frame->set_value(Math::snapped(p_pos, _get_editor_step())); + frame->set_value(track_editor->is_snap_timeline_enabled() ? Math::snapped(p_pos, _get_editor_step()) : p_pos); updating = false; _seek_value_changed(p_pos, p_timeline_only); } @@ -1511,30 +1579,28 @@ void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventKey> k = p_ev; - if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) { - switch (k->get_keycode()) { - case Key::A: { - if (!k->is_shift_pressed()) { - _play_bw_from_pressed(); - } else { - _play_bw_pressed(); - } - accept_event(); - } break; - case Key::S: { - _stop_pressed(); - accept_event(); - } break; - case Key::D: { - if (!k->is_shift_pressed()) { - _play_from_pressed(); - } else { - _play_pressed(); - } - accept_event(); - } break; - default: - break; + if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo()) { + if (ED_IS_SHORTCUT("animation_editor/stop_animation", p_ev)) { + _stop_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/play_animation", p_ev)) { + _play_from_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/play_animation_backwards", p_ev)) { + _play_bw_from_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_start", p_ev)) { + _play_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/play_animation_from_end", p_ev)) { + _play_bw_pressed(); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/go_to_next_keyframe", p_ev)) { + _go_to_nearest_keyframe(false); + accept_event(); + } else if (ED_IS_SHORTCUT("animation_editor/go_to_previous_keyframe", p_ev)) { + _go_to_nearest_keyframe(true); + accept_event(); } } } @@ -1830,6 +1896,10 @@ AnimationMixer *AnimationPlayerEditor::fetch_mixer_for_library() const { return original_node; } +Node *AnimationPlayerEditor::get_cached_root_node() const { + return Object::cast_to<Node>(ObjectDB::get_instance(cached_root_node_id)); +} + bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) { bool is_valid = true; if (!p_anim.is_valid()) { @@ -1909,27 +1979,27 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug play_bw_from = memnew(Button); play_bw_from->set_theme_type_variation("FlatButton"); - play_bw_from->set_tooltip_text(TTR("Play selected animation backwards from current pos. (A)")); + play_bw_from->set_tooltip_text(TTR("Play Animation Backwards")); hb->add_child(play_bw_from); play_bw = memnew(Button); play_bw->set_theme_type_variation("FlatButton"); - play_bw->set_tooltip_text(TTR("Play selected animation backwards from end. (Shift+A)")); + play_bw->set_tooltip_text(TTR("Play Animation Backwards from End")); hb->add_child(play_bw); stop = memnew(Button); stop->set_theme_type_variation("FlatButton"); + stop->set_tooltip_text(TTR("Pause/Stop Animation")); hb->add_child(stop); - stop->set_tooltip_text(TTR("Pause/stop animation playback. (S)")); play = memnew(Button); play->set_theme_type_variation("FlatButton"); - play->set_tooltip_text(TTR("Play selected animation from start. (Shift+D)")); + play->set_tooltip_text(TTR("Play Animation from Start")); hb->add_child(play); play_from = memnew(Button); play_from->set_theme_type_variation("FlatButton"); - play_from->set_tooltip_text(TTR("Play selected animation from current pos. (D)")); + play_from->set_tooltip_text(TTR("Play Animation")); hb->add_child(play_from); frame = memnew(SpinBox); @@ -2145,6 +2215,14 @@ void fragment() { } )"); RS::get_singleton()->material_set_shader(onion.capture.material->get_rid(), onion.capture.shader->get_rid()); + + ED_SHORTCUT("animation_editor/stop_animation", TTR("Pause/Stop Animation"), Key::S); + ED_SHORTCUT("animation_editor/play_animation", TTR("Play Animation"), Key::D); + ED_SHORTCUT("animation_editor/play_animation_backwards", TTR("Play Animation Backwards"), Key::A); + ED_SHORTCUT("animation_editor/play_animation_from_start", TTR("Play Animation from Start"), KeyModifierMask::SHIFT + Key::D); + ED_SHORTCUT("animation_editor/play_animation_from_end", TTR("Play Animation Backwards from End"), KeyModifierMask::SHIFT + Key::A); + ED_SHORTCUT("animation_editor/go_to_next_keyframe", TTR("Go to Next Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::D); + ED_SHORTCUT("animation_editor/go_to_previous_keyframe", TTR("Go to Previous Keyframe"), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::A); } AnimationPlayerEditor::~AnimationPlayerEditor() { diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 8804e4e3b4..e4ca6c17c3 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -52,6 +52,7 @@ class AnimationPlayerEditor : public VBoxContainer { AnimationPlayerEditorPlugin *plugin = nullptr; AnimationMixer *original_node = nullptr; // For pinned mark in SceneTree. AnimationPlayer *player = nullptr; // For AnimationPlayerEditor, could be dummy. + ObjectID cached_root_node_id; bool is_dummy = false; enum { @@ -178,6 +179,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _select_anim_by_name(const String &p_anim); float _get_editor_step() const; + void _go_to_nearest_keyframe(bool p_backward); void _play_pressed(); void _play_from_pressed(); void _play_bw_pressed(); @@ -252,6 +254,7 @@ public: AnimationMixer *get_editing_node() const; AnimationPlayer *get_player() const; AnimationMixer *fetch_mixer_for_library() const; + Node *get_cached_root_node() const; static AnimationPlayerEditor *get_singleton() { return singleton; } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 6e41e98360..4b7bf1674f 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4120,7 +4120,8 @@ void CanvasItemEditor::_notification(int p_what) { } } break; - case NOTIFICATION_APPLICATION_FOCUS_OUT: { + case NOTIFICATION_APPLICATION_FOCUS_OUT: + case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { if (drag_type != DRAG_NONE) { _reset_drag(); viewport->queue_redraw(); diff --git a/editor/plugins/editor_context_menu_plugin.cpp b/editor/plugins/editor_context_menu_plugin.cpp new file mode 100644 index 0000000000..86d3c0a896 --- /dev/null +++ b/editor/plugins/editor_context_menu_plugin.cpp @@ -0,0 +1,65 @@ +/**************************************************************************/ +/* editor_context_menu_plugin.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "editor_context_menu_plugin.h" + +#include "core/input/shortcut.h" +#include "editor/editor_node.h" +#include "scene/resources/texture.h" + +void EditorContextMenuPlugin::add_options(const Vector<String> &p_paths) { + GDVIRTUAL_CALL(_popup_menu, p_paths); +} + +void EditorContextMenuPlugin::add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable) { + context_menu_shortcuts.insert(p_shortcut, p_callable); +} + +void EditorContextMenuPlugin::add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut) { + ERR_FAIL_COND_MSG(context_menu_items.has(p_name), "Context menu item already registered."); + ERR_FAIL_COND_MSG(context_menu_items.size() == MAX_ITEMS, "Maximum number of context menu items reached."); + ContextMenuItem item; + item.item_name = p_name; + item.callable = p_callable; + item.icon = p_texture; + item.shortcut = p_shortcut; + item.idx = EditorData::CONTEXT_MENU_ITEM_ID_BASE + start_idx + context_menu_shortcuts.size() + context_menu_items.size(); + context_menu_items.insert(p_name, item); +} + +void EditorContextMenuPlugin::clear_context_menu_items() { + context_menu_items.clear(); +} + +void EditorContextMenuPlugin::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_menu_shortcut", "shortcut", "callback"), &EditorContextMenuPlugin::add_menu_shortcut); + ClassDB::bind_method(D_METHOD("add_context_menu_item", "name", "callback", "icon", "shortcut"), &EditorContextMenuPlugin::add_context_menu_item, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Shortcut>())); + GDVIRTUAL_BIND(_popup_menu, "paths"); +} diff --git a/editor/plugins/editor_context_menu_plugin.h b/editor/plugins/editor_context_menu_plugin.h new file mode 100644 index 0000000000..db05e6c6c7 --- /dev/null +++ b/editor/plugins/editor_context_menu_plugin.h @@ -0,0 +1,70 @@ +/**************************************************************************/ +/* editor_context_menu_plugin.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 EDITOR_CONTEXT_MENU_PLUGIN_H +#define EDITOR_CONTEXT_MENU_PLUGIN_H + +#include "core/object/gdvirtual.gen.inc" +#include "core/object/ref_counted.h" + +class Texture2D; +class Shortcut; + +class EditorContextMenuPlugin : public RefCounted { + GDCLASS(EditorContextMenuPlugin, RefCounted); + +public: + int start_idx; + + inline static constexpr int MAX_ITEMS = 100; + + struct ContextMenuItem { + int idx = 0; + String item_name; + Callable callable; + Ref<Texture2D> icon; + Ref<Shortcut> shortcut; + }; + HashMap<String, ContextMenuItem> context_menu_items; + HashMap<Ref<Shortcut>, Callable> context_menu_shortcuts; + +protected: + static void _bind_methods(); + + GDVIRTUAL1(_popup_menu, Vector<String>); + +public: + virtual void add_options(const Vector<String> &p_paths); + void add_menu_shortcut(const Ref<Shortcut> &p_shortcut, const Callable &p_callable); + void add_context_menu_item(const String &p_name, const Callable &p_callable, const Ref<Texture2D> &p_texture, const Ref<Shortcut> &p_shortcut); + void clear_context_menu_items(); +}; + +#endif // EDITOR_CONTEXT_MENU_PLUGIN_H diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp index 8ce667568f..0ee67e08ba 100644 --- a/editor/plugins/editor_plugin.cpp +++ b/editor/plugins/editor_plugin.cpp @@ -47,6 +47,7 @@ #include "editor/import/editor_import_plugin.h" #include "editor/inspector_dock.h" #include "editor/plugins/canvas_item_editor_plugin.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/editor_resource_conversion_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" @@ -490,6 +491,16 @@ void EditorPlugin::remove_scene_post_import_plugin(const Ref<EditorScenePostImpo ResourceImporterScene::remove_post_importer_plugin(p_plugin); } +void EditorPlugin::add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { + ERR_FAIL_COND(p_plugin.is_null()); + EditorNode::get_editor_data().add_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin); +} + +void EditorPlugin::remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin) { + ERR_FAIL_COND(p_plugin.is_null()); + EditorNode::get_editor_data().remove_context_menu_plugin(EditorData::ContextMenuSlot(p_slot), p_plugin); +} + int find(const PackedStringArray &a, const String &v) { const String *r = a.ptr(); for (int j = 0; j < a.size(); ++j) { @@ -629,6 +640,8 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_resource_conversion_plugin", "plugin"), &EditorPlugin::remove_resource_conversion_plugin); ClassDB::bind_method(D_METHOD("set_input_event_forwarding_always_enabled"), &EditorPlugin::set_input_event_forwarding_always_enabled); ClassDB::bind_method(D_METHOD("set_force_draw_over_forwarding_enabled"), &EditorPlugin::set_force_draw_over_forwarding_enabled); + ClassDB::bind_method(D_METHOD("add_context_menu_plugin", "slot", "plugin"), &EditorPlugin::add_context_menu_plugin); + ClassDB::bind_method(D_METHOD("remove_context_menu_plugin", "slot", "plugin"), &EditorPlugin::remove_context_menu_plugin); ClassDB::bind_method(D_METHOD("get_editor_interface"), &EditorPlugin::get_editor_interface); ClassDB::bind_method(D_METHOD("get_script_create_dialog"), &EditorPlugin::get_script_create_dialog); @@ -694,6 +707,11 @@ void EditorPlugin::_bind_methods() { BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_PASS); BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_STOP); BIND_ENUM_CONSTANT(AFTER_GUI_INPUT_CUSTOM); + + BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCENE_TREE); + BIND_ENUM_CONSTANT(CONTEXT_SLOT_FILESYSTEM); + BIND_ENUM_CONSTANT(CONTEXT_SLOT_SCRIPT_EDITOR); + BIND_ENUM_CONSTANT(CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE); } EditorUndoRedoManager *EditorPlugin::get_undo_redo() { diff --git a/editor/plugins/editor_plugin.h b/editor/plugins/editor_plugin.h index 2e0771f906..35f65f03cd 100644 --- a/editor/plugins/editor_plugin.h +++ b/editor/plugins/editor_plugin.h @@ -53,6 +53,7 @@ class EditorToolAddons; class EditorTranslationParserPlugin; class EditorUndoRedoManager; class ScriptCreateDialog; +class EditorContextMenuPlugin; class EditorPlugin : public Node { GDCLASS(EditorPlugin, Node); @@ -102,6 +103,13 @@ public: AFTER_GUI_INPUT_CUSTOM, }; + enum ContextMenuSlot { + CONTEXT_SLOT_SCENE_TREE, + CONTEXT_SLOT_FILESYSTEM, + CONTEXT_SLOT_SCRIPT_EDITOR, + CONTEXT_SUBMENU_SLOT_FILESYSTEM_CREATE, + }; + protected: void _notification(int p_what); @@ -249,6 +257,9 @@ public: void add_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin); void remove_resource_conversion_plugin(const Ref<EditorResourceConversionPlugin> &p_plugin); + void add_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); + void remove_context_menu_plugin(ContextMenuSlot p_slot, const Ref<EditorContextMenuPlugin> &p_plugin); + void enable_plugin(); void disable_plugin(); @@ -259,6 +270,7 @@ public: VARIANT_ENUM_CAST(EditorPlugin::CustomControlContainer); VARIANT_ENUM_CAST(EditorPlugin::DockSlot); VARIANT_ENUM_CAST(EditorPlugin::AfterGUIInput); +VARIANT_ENUM_CAST(EditorPlugin::ContextMenuSlot); typedef EditorPlugin *(*EditorPluginCreateFunc)(); diff --git a/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp index ae24b4250e..c277ec8cd3 100644 --- a/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/joint_3d_gizmo_plugin.cpp @@ -293,9 +293,15 @@ Joint3DGizmoPlugin::Joint3DGizmoPlugin() { void Joint3DGizmoPlugin::incremental_update_gizmos() { if (!current_gizmos.is_empty()) { - update_idx++; - update_idx = update_idx % current_gizmos.size(); - redraw(current_gizmos.get(update_idx)); + HashSet<EditorNode3DGizmo *>::Iterator E = current_gizmos.find(last_drawn); + if (E) { + ++E; + } + if (!E) { + E = current_gizmos.begin(); + } + redraw(*E); + last_drawn = *E; } } diff --git a/editor/plugins/gizmos/joint_3d_gizmo_plugin.h b/editor/plugins/gizmos/joint_3d_gizmo_plugin.h index 79fe40d1b2..25b08d71c6 100644 --- a/editor/plugins/gizmos/joint_3d_gizmo_plugin.h +++ b/editor/plugins/gizmos/joint_3d_gizmo_plugin.h @@ -37,7 +37,7 @@ class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin); Timer *update_timer = nullptr; - uint64_t update_idx = 0; + EditorNode3DGizmo *last_drawn = nullptr; void incremental_update_gizmos(); diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 67d5e44ce5..8aff3c9aec 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -431,12 +431,13 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< colors.resize(p_handles.size()); Color *w = colors.ptrw(); for (int i = 0; i < p_handles.size(); i++) { + int id = p_ids.is_empty() ? i : p_ids[i]; + Color col(1, 1, 1, 1); - if (is_handle_highlighted(i, p_secondary)) { + if (is_handle_highlighted(id, p_secondary)) { col = Color(0, 0, 1, 0.9); } - int id = p_ids.is_empty() ? i : p_ids[i]; if (!is_current_hover_gizmo || current_hover_handle != id || p_secondary != current_hover_handle_secondary) { col.a = 0.8; } @@ -982,7 +983,7 @@ void EditorNode3DGizmoPlugin::create_handle_material(const String &p_name, bool handle_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); handle_material->set_flag(StandardMaterial3D::FLAG_USE_POINT_SIZE, true); - Ref<Texture2D> handle_t = p_icon != nullptr ? p_icon : EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle"), EditorStringName(EditorIcons)); + Ref<Texture2D> handle_t = p_icon.is_valid() ? p_icon : EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Editor3DHandle"), EditorStringName(EditorIcons)); handle_material->set_point_size(handle_t->get_width()); handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle_t); handle_material->set_albedo(Color(1, 1, 1)); @@ -1060,7 +1061,7 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) { ref->set_node_3d(p_spatial); ref->set_hidden(current_state == HIDDEN); - current_gizmos.push_back(ref.ptr()); + current_gizmos.insert(ref.ptr()); return ref; } diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index c4b275032a..1916bc2058 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -157,7 +157,7 @@ public: protected: int current_state; - List<EditorNode3DGizmo *> current_gizmos; + HashSet<EditorNode3DGizmo *> current_gizmos; HashMap<String, Vector<Ref<StandardMaterial3D>>> materials; static void _bind_methods(); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 8b0f4a64a7..44673e7224 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -1975,7 +1975,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } - if (_edit.mode != TRANSFORM_NONE) { + if (!_edit.instant && _edit.mode != TRANSFORM_NONE) { Node3D *selected = spatial_editor->get_single_selected_node(); Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; @@ -2399,15 +2399,30 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", p_event) && _edit.mode != TRANSFORM_NONE) { cancel_transform(); } - if (!is_freelook_active()) { - if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event)) { - begin_transform(TRANSFORM_TRANSLATE, true); + if (!is_freelook_active() && !k->is_echo()) { + if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event) && _edit.mode != TRANSFORM_TRANSLATE) { + if (_edit.mode == TRANSFORM_NONE) { + begin_transform(TRANSFORM_TRANSLATE, true); + } else if (_edit.instant) { + commit_transform(); + begin_transform(TRANSFORM_TRANSLATE, true); + } } - if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event)) { - begin_transform(TRANSFORM_ROTATE, true); + if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event) && _edit.mode != TRANSFORM_ROTATE) { + if (_edit.mode == TRANSFORM_NONE) { + begin_transform(TRANSFORM_ROTATE, true); + } else if (_edit.instant) { + commit_transform(); + begin_transform(TRANSFORM_ROTATE, true); + } } - if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event)) { - begin_transform(TRANSFORM_SCALE, true); + if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event) && _edit.mode != TRANSFORM_SCALE) { + if (_edit.mode == TRANSFORM_NONE) { + begin_transform(TRANSFORM_SCALE, true); + } else if (_edit.instant) { + commit_transform(); + begin_transform(TRANSFORM_SCALE, true); + } } } @@ -3077,8 +3092,11 @@ void Node3DEditorViewport::_notification(int p_what) { update_preview_node = false; } break; + case NOTIFICATION_APPLICATION_FOCUS_OUT: case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { set_freelook_active(false); + cursor.region_select = false; + surface->queue_redraw(); } break; case NOTIFICATION_ENTER_TREE: { @@ -4506,8 +4524,8 @@ bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path Node *instantiated_scene = nullptr; - if (mesh != nullptr || scene != nullptr) { - if (mesh != nullptr) { + if (mesh.is_valid() || scene.is_valid()) { + if (mesh.is_valid()) { MeshInstance3D *mesh_instance = memnew(MeshInstance3D); mesh_instance->set_mesh(mesh); @@ -4538,7 +4556,7 @@ bool Node3DEditorViewport::_create_instance(Node *p_parent, const String &p_path } } - if (scene != nullptr) { + if (scene.is_valid()) { instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(p_path)); } @@ -5462,7 +5480,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p preview_camera = memnew(CheckBox); preview_camera->set_text(TTR("Preview")); - preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KeyModifierMask::CMD_OR_CTRL | Key::P)); + // Using Control even on macOS to avoid conflict with Quick Open shortcut. + preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KeyModifierMask::CTRL | Key::P)); vbox->add_child(preview_camera); preview_camera->set_h_size_flags(0); preview_camera->hide(); @@ -7846,6 +7865,9 @@ void Node3DEditor::_sun_environ_settings_pressed() { sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0)); sun_environ_popup->reset_size(); sun_environ_popup->popup(); + // Grabbing the focus is required for Shift modifier checking to be functional + // (when the Add sun/environment buttons are pressed). + sun_environ_popup->grab_focus(); } void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) { @@ -9291,7 +9313,7 @@ struct _GizmoPluginNameComparator { }; void Node3DEditor::add_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) { - ERR_FAIL_NULL(p_plugin.ptr()); + ERR_FAIL_COND(p_plugin.is_null()); gizmo_plugins_by_priority.push_back(p_plugin); gizmo_plugins_by_priority.sort_custom<_GizmoPluginPriorityComparator>(); diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp index 5b23cf44d0..0455b266b4 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -42,7 +42,6 @@ void Path2DEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { curve_edit->set_icon(get_editor_theme_icon(SNAME("CurveEdit"))); curve_edit_curve->set_icon(get_editor_theme_icon(SNAME("CurveCurve"))); @@ -50,6 +49,8 @@ void Path2DEditor::_notification(int p_what) { curve_del->set_icon(get_editor_theme_icon(SNAME("CurveDelete"))); curve_close->set_icon(get_editor_theme_icon(SNAME("CurveClose"))); curve_clear_points->set_icon(get_editor_theme_icon(SNAME("Clear"))); + + create_curve_button->set_icon(get_editor_theme_icon(SNAME("Curve2D"))); } break; } } @@ -197,12 +198,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { const Vector2 new_point = xform.affine_inverse().xform(gpoint2); curve->add_point(new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1); - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Split Curve")); - undo_redo->add_do_method(curve.ptr(), "add_point", new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1); - undo_redo->add_undo_method(curve.ptr(), "remove_point", insertion_point + 1); - - action = ACTION_MOVING_NEW_POINT; + action = ACTION_MOVING_NEW_POINT_FROM_SPLIT; action_point = insertion_point + 1; moving_from = curve->get_point_position(action_point); moving_screen_from = gpoint2; @@ -245,6 +241,16 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { undo_redo->commit_action(false); } break; + case ACTION_MOVING_NEW_POINT_FROM_SPLIT: { + undo_redo->create_action(TTR("Split Curve")); + undo_redo->add_do_method(curve.ptr(), "add_point", Vector2(), Vector2(), Vector2(), action_point); + undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint); + undo_redo->add_undo_method(curve.ptr(), "remove_point", action_point); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(false); + } break; + case ACTION_MOVING_IN: { if (original_mouse_pos != gpoint) { undo_redo->create_action(TTR("Move In-Control in Curve")); @@ -295,7 +301,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { Vector2 gpoint = mm->get_position(); Ref<Curve2D> curve = node->get_curve(); - if (curve == nullptr) { + if (curve.is_null()) { return true; } if (curve->get_point_count() < 2) { @@ -350,7 +356,8 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { break; case ACTION_MOVING_POINT: - case ACTION_MOVING_NEW_POINT: { + case ACTION_MOVING_NEW_POINT: + case ACTION_MOVING_NEW_POINT_FROM_SPLIT: { curve->set_point_position(action_point, cpoint); } break; @@ -444,6 +451,16 @@ void Path2DEditor::_node_visibility_changed() { } canvas_item_editor->update_viewport(); + _update_toolbar(); +} + +void Path2DEditor::_update_toolbar() { + if (!node) { + return; + } + bool has_curve = node->get_curve().is_valid(); + toolbar->set_visible(has_curve); + create_curve_button->set_visible(!has_curve); } void Path2DEditor::edit(Node *p_path2d) { @@ -457,6 +474,7 @@ void Path2DEditor::edit(Node *p_path2d) { if (p_path2d) { node = Object::cast_to<Path2D>(p_path2d); + _update_toolbar(); if (!node->is_connected(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed))) { node->connect(SceneStringName(visibility_changed), callable_mp(this, &Path2DEditor::_node_visibility_changed)); @@ -471,7 +489,7 @@ void Path2DEditor::edit(Node *p_path2d) { } void Path2DEditor::_bind_methods() { - //ClassDB::bind_method(D_METHOD("_menu_option"),&Path2DEditor::_menu_option); + ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path2DEditor::_update_toolbar); ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path2DEditor::_clear_curve_points); ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path2DEditor::_restore_curve_points); } @@ -573,6 +591,10 @@ void Path2DEditor::_cancel_current_action() { curve->remove_point(curve->get_point_count() - 1); } break; + case ACTION_MOVING_NEW_POINT_FROM_SPLIT: { + curve->remove_point(action_point); + } break; + case ACTION_MOVING_IN: { curve->set_point_in(action_point, moving_from); curve->set_point_out(action_point, mirror_handle_length ? -moving_from : (-moving_from.normalized() * orig_out_length)); @@ -591,6 +613,21 @@ void Path2DEditor::_cancel_current_action() { action = ACTION_NONE; } +void Path2DEditor::_create_curve() { + ERR_FAIL_NULL(node); + + Ref<Curve2D> new_curve; + new_curve.instantiate(); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Create Curve in Path2D")); + undo_redo->add_do_property(node, "curve", new_curve); + undo_redo->add_undo_property(node, "curve", Ref<Curve2D>()); + undo_redo->add_do_method(this, "_update_toolbar"); + undo_redo->add_undo_method(this, "_update_toolbar"); + undo_redo->commit_action(); +} + void Path2DEditor::_confirm_clear_points() { if (!node || node->get_curve().is_null()) { return; @@ -638,6 +675,8 @@ void Path2DEditor::_restore_curve_points(Path2D *p_path2d, const PackedVector2Ar } Path2DEditor::Path2DEditor() { + toolbar = memnew(HBoxContainer); + curve_edit = memnew(Button); curve_edit->set_theme_type_variation("FlatButton"); curve_edit->set_toggle_mode(true); @@ -645,7 +684,7 @@ Path2DEditor::Path2DEditor() { curve_edit->set_focus_mode(Control::FOCUS_NONE); curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point")); curve_edit->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT)); - add_child(curve_edit); + toolbar->add_child(curve_edit); curve_edit_curve = memnew(Button); curve_edit_curve->set_theme_type_variation("FlatButton"); @@ -653,7 +692,7 @@ Path2DEditor::Path2DEditor() { curve_edit_curve->set_focus_mode(Control::FOCUS_NONE); curve_edit_curve->set_tooltip_text(TTR("Select Control Points (Shift+Drag)")); curve_edit_curve->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT_CURVE)); - add_child(curve_edit_curve); + toolbar->add_child(curve_edit_curve); curve_create = memnew(Button); curve_create->set_theme_type_variation("FlatButton"); @@ -661,7 +700,7 @@ Path2DEditor::Path2DEditor() { curve_create->set_focus_mode(Control::FOCUS_NONE); curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Right Click: Delete Point")); curve_create->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CREATE)); - add_child(curve_create); + toolbar->add_child(curve_create); curve_del = memnew(Button); curve_del->set_theme_type_variation("FlatButton"); @@ -669,42 +708,48 @@ Path2DEditor::Path2DEditor() { curve_del->set_focus_mode(Control::FOCUS_NONE); curve_del->set_tooltip_text(TTR("Delete Point")); curve_del->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_DELETE)); - add_child(curve_del); + toolbar->add_child(curve_del); curve_close = memnew(Button); curve_close->set_theme_type_variation("FlatButton"); curve_close->set_focus_mode(Control::FOCUS_NONE); curve_close->set_tooltip_text(TTR("Close Curve")); curve_close->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE)); - add_child(curve_close); + toolbar->add_child(curve_close); curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation("FlatButton"); curve_clear_points->set_focus_mode(Control::FOCUS_NONE); curve_clear_points->set_tooltip_text(TTR("Clear Points")); curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_confirm_clear_points)); - add_child(curve_clear_points); + toolbar->add_child(curve_clear_points); clear_points_dialog = memnew(ConfirmationDialog); clear_points_dialog->set_title(TTR("Please Confirm...")); clear_points_dialog->set_text(TTR("Remove all curve points?")); clear_points_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLEAR_POINTS)); - add_child(clear_points_dialog); - - PopupMenu *menu; + toolbar->add_child(clear_points_dialog); handle_menu = memnew(MenuButton); handle_menu->set_flat(false); handle_menu->set_theme_type_variation("FlatMenuButton"); handle_menu->set_text(TTR("Options")); - add_child(handle_menu); + toolbar->add_child(handle_menu); - menu = handle_menu->get_popup(); + PopupMenu *menu = handle_menu->get_popup(); menu->add_check_item(TTR("Mirror Handle Angles")); menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle); menu->add_check_item(TTR("Mirror Handle Lengths")); menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length); menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path2DEditor::_handle_option_pressed)); + + add_child(toolbar); + + create_curve_button = memnew(Button); + create_curve_button->set_text(TTR("Create Curve")); + create_curve_button->hide(); + add_child(create_curve_button); + create_curve_button->connect(SceneStringName(pressed), callable_mp(this, &Path2DEditor::_create_curve)); } void Path2DEditorPlugin::edit(Object *p_object) { diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h index a2857fddb7..f2f03f12f1 100644 --- a/editor/plugins/path_2d_editor_plugin.h +++ b/editor/plugins/path_2d_editor_plugin.h @@ -58,6 +58,7 @@ class Path2DEditor : public HBoxContainer { }; Mode mode = MODE_EDIT; + HBoxContainer *toolbar = nullptr; Button *curve_clear_points = nullptr; Button *curve_close = nullptr; Button *curve_create = nullptr; @@ -66,6 +67,7 @@ class Path2DEditor : public HBoxContainer { Button *curve_edit_curve = nullptr; MenuButton *handle_menu = nullptr; + Button *create_curve_button = nullptr; ConfirmationDialog *clear_points_dialog = nullptr; bool mirror_handle_angle = true; @@ -81,6 +83,7 @@ class Path2DEditor : public HBoxContainer { ACTION_NONE, ACTION_MOVING_POINT, ACTION_MOVING_NEW_POINT, + ACTION_MOVING_NEW_POINT_FROM_SPLIT, ACTION_MOVING_IN, ACTION_MOVING_OUT, }; @@ -99,7 +102,9 @@ class Path2DEditor : public HBoxContainer { void _cancel_current_action(); void _node_visibility_changed(); + void _update_toolbar(); + void _create_curve(); void _confirm_clear_points(); void _clear_curve_points(Path2D *p_path2d); void _restore_curve_points(Path2D *p_path2d, const PackedVector2Array &p_points); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 240206e124..7ba464f4e5 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -652,6 +652,7 @@ void Path3DEditorPlugin::edit(Object *p_object) { if (path->get_curve().is_valid()) { path->get_curve()->emit_signal(CoreStringName(changed)); } + _update_toolbar(); } } else { Path3D *pre = path; @@ -732,6 +733,21 @@ void Path3DEditorPlugin::_handle_option_pressed(int p_option) { } } +void Path3DEditorPlugin::_create_curve() { + ERR_FAIL_NULL(path); + + Ref<Curve3D> new_curve; + new_curve.instantiate(); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Create Curve in Path3D")); + undo_redo->add_do_property(path, "curve", new_curve); + undo_redo->add_undo_property(path, "curve", Ref<Curve3D>()); + undo_redo->add_do_method(this, "_update_toolbar"); + undo_redo->add_undo_method(this, "_update_toolbar"); + undo_redo->commit_action(); +} + void Path3DEditorPlugin::_confirm_clear_points() { if (!path || path->get_curve().is_null() || path->get_curve()->get_point_count() == 0) { return; @@ -783,6 +799,17 @@ void Path3DEditorPlugin::_update_theme() { curve_del->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveDelete"), EditorStringName(EditorIcons))); curve_close->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveClose"), EditorStringName(EditorIcons))); curve_clear_points->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Clear"), EditorStringName(EditorIcons))); + + create_curve_button->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Curve3D"), EditorStringName(EditorIcons))); +} + +void Path3DEditorPlugin::_update_toolbar() { + if (!path) { + return; + } + bool has_curve = path->get_curve().is_valid(); + toolbar->set_visible(has_curve); + create_curve_button->set_visible(!has_curve); } void Path3DEditorPlugin::_notification(int p_what) { @@ -808,6 +835,7 @@ void Path3DEditorPlugin::_notification(int p_what) { } void Path3DEditorPlugin::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_toolbar"), &Path3DEditorPlugin::_update_toolbar); ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path3DEditorPlugin::_clear_curve_points); ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path3DEditorPlugin::_restore_curve_points); } @@ -830,65 +858,74 @@ Path3DEditorPlugin::Path3DEditorPlugin() { topmenu_bar->hide(); Node3DEditor::get_singleton()->add_control_to_menu_panel(topmenu_bar); + toolbar = memnew(HBoxContainer); + topmenu_bar->add_child(toolbar); + curve_edit = memnew(Button); curve_edit->set_theme_type_variation("FlatButton"); curve_edit->set_toggle_mode(true); curve_edit->set_focus_mode(Control::FOCUS_NONE); curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Click: Select multiple Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point")); - topmenu_bar->add_child(curve_edit); + toolbar->add_child(curve_edit); curve_edit_curve = memnew(Button); curve_edit_curve->set_theme_type_variation("FlatButton"); curve_edit_curve->set_toggle_mode(true); curve_edit_curve->set_focus_mode(Control::FOCUS_NONE); curve_edit_curve->set_tooltip_text(TTR("Select Control Points") + "\n" + TTR("Shift+Click: Drag out Control Points")); - topmenu_bar->add_child(curve_edit_curve); + toolbar->add_child(curve_edit_curve); curve_edit_tilt = memnew(Button); curve_edit_tilt->set_theme_type_variation("FlatButton"); curve_edit_tilt->set_toggle_mode(true); curve_edit_tilt->set_focus_mode(Control::FOCUS_NONE); curve_edit_tilt->set_tooltip_text(TTR("Select Tilt Handles")); - topmenu_bar->add_child(curve_edit_tilt); + toolbar->add_child(curve_edit_tilt); curve_create = memnew(Button); curve_create->set_theme_type_variation("FlatButton"); curve_create->set_toggle_mode(true); curve_create->set_focus_mode(Control::FOCUS_NONE); curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Split Segment (in curve)")); - topmenu_bar->add_child(curve_create); + toolbar->add_child(curve_create); curve_del = memnew(Button); curve_del->set_theme_type_variation("FlatButton"); curve_del->set_toggle_mode(true); curve_del->set_focus_mode(Control::FOCUS_NONE); curve_del->set_tooltip_text(TTR("Delete Point")); - topmenu_bar->add_child(curve_del); + toolbar->add_child(curve_del); curve_close = memnew(Button); curve_close->set_theme_type_variation("FlatButton"); curve_close->set_focus_mode(Control::FOCUS_NONE); curve_close->set_tooltip_text(TTR("Close Curve")); - topmenu_bar->add_child(curve_close); + toolbar->add_child(curve_close); curve_clear_points = memnew(Button); curve_clear_points->set_theme_type_variation("FlatButton"); curve_clear_points->set_focus_mode(Control::FOCUS_NONE); curve_clear_points->set_tooltip_text(TTR("Clear Points")); curve_clear_points->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_confirm_clear_points)); - topmenu_bar->add_child(curve_clear_points); + toolbar->add_child(curve_clear_points); clear_points_dialog = memnew(ConfirmationDialog); clear_points_dialog->set_title(TTR("Please Confirm...")); clear_points_dialog->set_text(TTR("Remove all curve points?")); clear_points_dialog->connect(SceneStringName(confirmed), callable_mp(this, &Path3DEditorPlugin::_clear_points)); - topmenu_bar->add_child(clear_points_dialog); + toolbar->add_child(clear_points_dialog); handle_menu = memnew(MenuButton); handle_menu->set_flat(false); handle_menu->set_theme_type_variation("FlatMenuButton"); handle_menu->set_text(TTR("Options")); - topmenu_bar->add_child(handle_menu); + toolbar->add_child(handle_menu); + + create_curve_button = memnew(Button); + create_curve_button->set_text(TTR("Create Curve")); + create_curve_button->hide(); + topmenu_bar->add_child(create_curve_button); + create_curve_button->connect(SceneStringName(pressed), callable_mp(this, &Path3DEditorPlugin::_create_curve)); PopupMenu *menu; menu = handle_menu->get_popup(); diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index ee73df1617..09a7a46803 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -113,6 +113,8 @@ class Path3DEditorPlugin : public EditorPlugin { Ref<Path3DGizmoPlugin> path_3d_gizmo_plugin; HBoxContainer *topmenu_bar = nullptr; + + HBoxContainer *toolbar = nullptr; Button *curve_create = nullptr; Button *curve_edit = nullptr; Button *curve_edit_curve = nullptr; @@ -122,6 +124,7 @@ class Path3DEditorPlugin : public EditorPlugin { Button *curve_clear_points = nullptr; MenuButton *handle_menu = nullptr; + Button *create_curve_button = nullptr; ConfirmationDialog *clear_points_dialog = nullptr; float disk_size = 0.8; @@ -138,14 +141,16 @@ class Path3DEditorPlugin : public EditorPlugin { Path3D *path = nullptr; void _update_theme(); + void _update_toolbar(); void _mode_changed(int p_mode); void _close_curve(); void _handle_option_pressed(int p_option); bool handle_clicked = false; - bool mirror_handle_angle; - bool mirror_handle_length; + bool mirror_handle_angle = true; + bool mirror_handle_length = true; + void _create_curve(); void _confirm_clear_points(); void _clear_points(); void _clear_curve_points(); diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index 496323aa2a..b8a309bc60 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -1482,7 +1482,7 @@ Polygon2DEditor::Polygon2DEditor() { grid_settings = memnew(AcceptDialog); grid_settings->set_title(TTR("Configure Grid:")); - add_child(grid_settings); + uv_edit->add_child(grid_settings); VBoxContainer *grid_settings_vb = memnew(VBoxContainer); grid_settings->add_child(grid_settings_vb); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index d7de5a7223..93c8ae5438 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -57,6 +57,7 @@ #include "editor/gui/editor_toaster.h" #include "editor/inspector_dock.h" #include "editor/node_dock.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/shader_editor_plugin.h" #include "editor/plugins/text_shader_editor.h" #include "editor/themes/editor_scale.h" @@ -510,7 +511,7 @@ void ScriptEditor::_set_execution(Ref<RefCounted> p_script, int p_line) { continue; } - if ((scr != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) { + if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) { se->set_executing_line(p_line); } } @@ -526,7 +527,7 @@ void ScriptEditor::_clear_execution(Ref<RefCounted> p_script) { continue; } - if ((scr != nullptr && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) { + if ((scr.is_valid() && se->get_edited_resource() == p_script) || se->get_edited_resource()->get_path() == scr->get_path()) { se->clear_executing_line(); } } @@ -711,7 +712,7 @@ void ScriptEditor::_go_to_tab(int p_idx) { } Ref<Script> scr = Object::cast_to<ScriptEditorBase>(c)->get_edited_resource(); - if (scr != nullptr) { + if (scr.is_valid()) { notify_script_changed(scr); } @@ -1017,7 +1018,7 @@ void ScriptEditor::_resave_scripts(const String &p_str) { } Ref<TextFile> text_file = scr; - if (text_file != nullptr) { + if (text_file.is_valid()) { se->apply_code(); _save_text_file(text_file, text_file->get_path()); break; @@ -1228,7 +1229,7 @@ Ref<Script> ScriptEditor::_get_current_script() { if (current) { Ref<Script> scr = current->get_edited_resource(); - return scr != nullptr ? scr : nullptr; + return scr.is_valid() ? scr : nullptr; } else { return nullptr; } @@ -1398,6 +1399,16 @@ void ScriptEditor::_menu_option(int p_option) { } } + // Context menu options. + if (p_option >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) { + Ref<Resource> resource; + if (current) { + resource = current->get_edited_resource(); + } + EditorNode::get_editor_data().script_editor_options_pressed(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_option, resource); + return; + } + if (current) { switch (p_option) { case FILE_SAVE: { @@ -1420,7 +1431,7 @@ void ScriptEditor::_menu_option(int p_option) { Ref<TextFile> text_file = resource; Ref<Script> scr = resource; - if (text_file != nullptr) { + if (text_file.is_valid()) { file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); file_dialog_option = FILE_SAVE_AS; @@ -1449,7 +1460,7 @@ void ScriptEditor::_menu_option(int p_option) { case FILE_TOOL_RELOAD_SOFT: { Ref<Script> scr = current->get_edited_resource(); - if (scr == nullptr || scr.is_null()) { + if (scr.is_null()) { EditorNode::get_singleton()->show_warning(TTR("Can't obtain the script for reloading.")); break; } @@ -1463,7 +1474,7 @@ void ScriptEditor::_menu_option(int p_option) { case FILE_RUN: { Ref<Script> scr = current->get_edited_resource(); - if (scr == nullptr || scr.is_null()) { + if (scr.is_null()) { EditorToaster::get_singleton()->popup_str(TTR("Cannot run the edited file because it's not a script."), EditorToaster::SEVERITY_WARNING); break; } @@ -1796,7 +1807,7 @@ void ScriptEditor::_close_builtin_scripts_from_scene(const String &p_scene) { if (se) { Ref<Script> scr = se->get_edited_resource(); - if (scr == nullptr || !scr.is_valid()) { + if (scr.is_null()) { continue; } @@ -2500,7 +2511,7 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col, continue; } - if ((scr != nullptr && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) { + if ((scr.is_valid() && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) { if (should_open) { se->enable_editor(this); @@ -2550,7 +2561,7 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col, PackedStringArray languages = highlighter->_get_supported_languages(); // If script try language, else use extension. - if (scr != nullptr) { + if (scr.is_valid()) { if (languages.has(scr->get_language()->get_name())) { se->set_syntax_highlighter(highlighter); highlighter_set = true; @@ -2660,7 +2671,7 @@ void ScriptEditor::save_current_script() { Ref<TextFile> text_file = resource; Ref<Script> scr = resource; - if (text_file != nullptr) { + if (text_file.is_valid()) { current->apply_code(); _save_text_file(text_file, text_file->get_path()); return; @@ -2711,7 +2722,7 @@ void ScriptEditor::save_all_scripts() { Ref<TextFile> text_file = edited_res; Ref<Script> scr = edited_res; - if (text_file != nullptr) { + if (text_file.is_valid()) { _save_text_file(text_file, text_file->get_path()); continue; } @@ -2788,7 +2799,7 @@ void ScriptEditor::_reload_scripts(bool p_refresh_only) { } Ref<JSON> json = edited_res; - if (json != nullptr) { + if (json.is_valid()) { Ref<JSON> rel_json = ResourceLoader::load(json->get_path(), json->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE); ERR_CONTINUE(!rel_json.is_valid()); json->parse(rel_json->get_parsed_text(), true); @@ -3304,6 +3315,11 @@ void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) { _menu_option(WINDOW_MOVE_DOWN); accept_event(); } + // Context menu shortcuts. + int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, p_event); + if (match_option) { + _menu_option(match_option); + } } void ScriptEditor::_script_list_clicked(int p_item, Vector2 p_local_mouse_pos, MouseButton p_mouse_button_index) { @@ -3338,7 +3354,7 @@ void ScriptEditor::_make_script_list_context_menu() { context_menu->add_separator(); if (se) { Ref<Script> scr = se->get_edited_resource(); - if (scr != nullptr) { + if (scr.is_valid()) { if (!scr.is_null() && scr->is_tool()) { context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/reload_script_soft"), FILE_TOOL_RELOAD_SOFT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/run_file"), FILE_RUN); @@ -3362,6 +3378,17 @@ void ScriptEditor::_make_script_list_context_menu() { context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_MOVE_DOWN), tab_container->get_current_tab() >= tab_container->get_tab_count() - 1); context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_SORT), tab_container->get_tab_count() <= 1); + // Context menu plugin. + Vector<String> selected_paths; + if (se) { + Ref<Resource> scr = se->get_edited_resource(); + if (scr.is_valid()) { + String path = scr->get_path(); + selected_paths.push_back(path); + } + } + EditorNode::get_editor_data().add_options_from_plugins(context_menu, EditorData::CONTEXT_SLOT_SCRIPT_EDITOR, selected_paths); + context_menu->set_position(get_screen_position() + get_local_mouse_position()); context_menu->reset_size(); context_menu->popup(); @@ -3685,7 +3712,7 @@ void ScriptEditor::_update_history_pos(int p_new_pos) { seb->ensure_focus(); Ref<Script> scr = seb->get_edited_resource(); - if (scr != nullptr) { + if (scr.is_valid()) { notify_script_changed(scr); } } @@ -3724,7 +3751,7 @@ Vector<Ref<Script>> ScriptEditor::get_open_scripts() const { } Ref<Script> scr = se->get_edited_resource(); - if (scr != nullptr) { + if (scr.is_valid()) { out_scripts.push_back(scr); } } @@ -4282,7 +4309,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); if (!make_floating->is_disabled()) { // Override default ScreenSelect tooltip if multi-window support is available. - make_floating->set_tooltip_text(TTR("Make the script editor floating.")); + make_floating->set_tooltip_text(TTR("Make the script editor floating.\nRight-click to open the screen selector.")); } menu_hb->add_child(make_floating); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 34557b26b4..7e8fba8b9e 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1843,7 +1843,8 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data const String &line = te->get_line(drop_at_line); const bool is_empty_line = line_will_be_empty || line.is_empty() || te->get_first_non_whitespace_column(drop_at_line) == line.length(); - if (d.has("type") && String(d["type"]) == "resource") { + const String type = d.get("type", ""); + if (type == "resource") { Ref<Resource> resource = d["resource"]; if (resource.is_null()) { return; @@ -1868,11 +1869,11 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } } - if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) { - Array files = d["files"]; - for (int i = 0; i < files.size(); i++) { - const String &path = String(files[i]); + if (type == "files" || type == "files_and_dirs") { + const PackedStringArray files = d["files"]; + PackedStringArray parts; + for (const String &path : files) { if (drop_modifier_pressed && ResourceLoader::exists(path)) { Ref<Resource> resource = ResourceLoader::load(path); if (resource.is_null()) { @@ -1880,18 +1881,15 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data resource.instantiate(); resource->set_path_cache(path); } - text_to_drop += _get_dropped_resource_line(resource, is_empty_line); + parts.append(_get_dropped_resource_line(resource, is_empty_line)); } else { - text_to_drop += _quote_drop_data(path); - } - - if (i < files.size() - 1) { - text_to_drop += is_empty_line ? "\n" : ", "; + parts.append(_quote_drop_data(path)); } } + text_to_drop = String(is_empty_line ? "\n" : ", ").join(parts); } - if (d.has("type") && String(d["type"]) == "nodes") { + if (type == "nodes") { Node *scene_root = get_tree()->get_edited_scene_root(); if (!scene_root) { EditorNode::get_singleton()->show_warning(TTR("Can't drop nodes without an open scene.")); @@ -1982,7 +1980,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } } - if (d.has("type") && String(d["type"]) == "obj_property") { + if (type == "obj_property") { bool add_literal = EDITOR_GET("text_editor/completion/add_node_path_literals"); text_to_drop = add_literal ? "^" : ""; // It is unclear whether properties may contain single or double quotes. @@ -2543,8 +2541,9 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_breakpoint", "macos", KeyModifierMask::META | KeyModifierMask::SHIFT | Key::B); ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::F9); - ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KeyModifierMask::CMD_OR_CTRL | Key::PERIOD); - ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KeyModifierMask::CMD_OR_CTRL | Key::COMMA); + // Using Control for these shortcuts even on macOS because Command+Comma is taken for opening Editor Settings. + ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KeyModifierMask::CTRL | Key::PERIOD); + ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KeyModifierMask::CTRL | Key::COMMA); ScriptEditor::register_create_script_editor_function(create_editor); } diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index ea049756b7..597ef49004 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -802,7 +802,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() { make_floating->connect("request_open_in_screen", callable_mp(window_wrapper, &WindowWrapper::enable_window_on_screen).bind(true)); if (!make_floating->is_disabled()) { // Override default ScreenSelect tooltip if multi-window support is available. - make_floating->set_tooltip_text(TTR("Make the shader editor floating.")); + make_floating->set_tooltip_text(TTR("Make the shader editor floating.\nRight-click to open the screen selector.")); } menu_hb->add_child(make_floating); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index dc4d4db3f8..9fc88b3a6a 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -1348,16 +1348,18 @@ int Skeleton3DEditor::get_selected_bone() const { return selected_bone; } +Skeleton3DGizmoPlugin::SelectionMaterials Skeleton3DGizmoPlugin::selection_materials; + Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { - unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); - unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); - unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); - unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); - unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); - - selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial)); - selected_sh = Ref<Shader>(memnew(Shader)); + selection_materials.unselected_mat.instantiate(); + selection_materials.unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + selection_materials.unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + selection_materials.unselected_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + + selection_materials.selected_mat.instantiate(); + Ref<Shader> selected_sh = Ref<Shader>(memnew(Shader)); selected_sh->set_code(R"( // Skeleton 3D gizmo bones shader. @@ -1376,7 +1378,7 @@ void fragment() { ALPHA = COLOR.a; } )"); - selected_mat->set_shader(selected_sh); + selection_materials.selected_mat->set_shader(selected_sh); // Register properties in editor settings. EDITOR_DEF_RST("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); @@ -1386,6 +1388,11 @@ void fragment() { EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron")); } +Skeleton3DGizmoPlugin::~Skeleton3DGizmoPlugin() { + selection_materials.unselected_mat.unref(); + selection_materials.selected_mat.unref(); +} + bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; } @@ -1526,6 +1533,11 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { selected = se->get_selected_bone(); } + Ref<ArrayMesh> m = get_bones_mesh(skeleton, selected, p_gizmo->is_selected()); + p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); +} + +Ref<ArrayMesh> Skeleton3DGizmoPlugin::get_bones_mesh(Skeleton3D *p_skeleton, int p_selected, bool p_is_selected) { Color bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/skeleton"); Color selected_bone_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/selected_bone"); real_t bone_axis_length = EDITOR_GET("editors/3d_gizmos/gizmo_settings/bone_axis_length"); @@ -1539,11 +1551,11 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); surface_tool->begin(Mesh::PRIMITIVE_LINES); - if (p_gizmo->is_selected()) { - surface_tool->set_material(selected_mat); + if (p_is_selected) { + surface_tool->set_material(selection_materials.selected_mat); } else { - unselected_mat->set_albedo(bone_color); - surface_tool->set_material(unselected_mat); + selection_materials.unselected_mat->set_albedo(bone_color); + surface_tool->set_material(selection_materials.unselected_mat); } LocalVector<int> bones; @@ -1557,16 +1569,16 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { weights[0] = 1; int current_bone_index = 0; - Vector<int> bones_to_process = skeleton->get_parentless_bones(); + Vector<int> bones_to_process = p_skeleton->get_parentless_bones(); while (bones_to_process.size() > current_bone_index) { int current_bone_idx = bones_to_process[current_bone_index]; current_bone_index++; - Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color; + Color current_bone_color = (current_bone_idx == p_selected) ? selected_bone_color : bone_color; Vector<int> child_bones_vector; - child_bones_vector = skeleton->get_bone_children(current_bone_idx); + child_bones_vector = p_skeleton->get_bone_children(current_bone_idx); int child_bones_size = child_bones_vector.size(); for (int i = 0; i < child_bones_size; i++) { @@ -1577,8 +1589,8 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { int child_bone_idx = child_bones_vector[i]; - Vector3 v0 = skeleton->get_bone_global_rest(current_bone_idx).origin; - Vector3 v1 = skeleton->get_bone_global_rest(child_bone_idx).origin; + Vector3 v0 = p_skeleton->get_bone_global_rest(current_bone_idx).origin; + Vector3 v1 = p_skeleton->get_bone_global_rest(child_bone_idx).origin; Vector3 d = (v1 - v0).normalized(); real_t dist = v0.distance_to(v1); @@ -1586,7 +1598,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { int closest = -1; real_t closest_d = 0.0; for (int j = 0; j < 3; j++) { - real_t dp = Math::abs(skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d)); + real_t dp = Math::abs(p_skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d)); if (j == 0 || dp > closest_d) { closest = j; } @@ -1613,7 +1625,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { for (int j = 0; j < 3; j++) { Vector3 axis; if (first == Vector3()) { - axis = d.cross(d.cross(skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized(); + axis = d.cross(d.cross(p_skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized(); first = axis; } else { axis = d.cross(first).normalized(); @@ -1668,7 +1680,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { surface_tool->add_vertex(v0); surface_tool->set_bones(bones); surface_tool->set_weights(weights); - surface_tool->add_vertex(v0 + (skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); + surface_tool->add_vertex(v0 + (p_skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); if (j == closest) { continue; @@ -1685,7 +1697,7 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { surface_tool->add_vertex(v1); surface_tool->set_bones(bones); surface_tool->set_weights(weights); - surface_tool->add_vertex(v1 + (skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); + surface_tool->add_vertex(v1 + (p_skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); if (j == closest) { continue; @@ -1698,6 +1710,5 @@ void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } - Ref<ArrayMesh> m = surface_tool->commit(); - p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); + return surface_tool->commit(); } diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 0bb58aac23..d4dee1f16f 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -260,11 +260,15 @@ public: class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); - Ref<StandardMaterial3D> unselected_mat; - Ref<ShaderMaterial> selected_mat; - Ref<Shader> selected_sh; + struct SelectionMaterials { + Ref<StandardMaterial3D> unselected_mat; + Ref<ShaderMaterial> selected_mat; + }; + static SelectionMaterials selection_materials; public: + static Ref<ArrayMesh> get_bones_mesh(Skeleton3D *p_skeleton, int p_selected, bool p_is_selected); + bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; int get_priority() const override; @@ -277,6 +281,7 @@ public: void redraw(EditorNode3DGizmo *p_gizmo) override; Skeleton3DGizmoPlugin(); + ~Skeleton3DGizmoPlugin(); }; #endif // SKELETON_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index ff5aca6cb0..37d5b787eb 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -659,6 +659,7 @@ void SpriteFramesEditor::_notification(int p_what) { zoom_reset->set_icon(get_editor_theme_icon(SNAME("ZoomReset"))); zoom_in->set_icon(get_editor_theme_icon(SNAME("ZoomMore"))); add_anim->set_icon(get_editor_theme_icon(SNAME("New"))); + duplicate_anim->set_icon(get_editor_theme_icon(SNAME("Duplicate"))); delete_anim->set_icon(get_editor_theme_icon(SNAME("Remove"))); anim_search_box->set_right_icon(get_editor_theme_icon(SNAME("Search"))); split_sheet_zoom_out->set_icon(get_editor_theme_icon(SNAME("ZoomLess"))); @@ -1179,6 +1180,41 @@ void SpriteFramesEditor::_animation_add() { animations->grab_focus(); } +void SpriteFramesEditor::_animation_duplicate() { + if (updating) { + return; + } + + if (!frames->has_animation(edited_anim)) { + return; + } + + int counter = 1; + String new_name = edited_anim; + PackedStringArray name_component = new_name.rsplit("_", true, 1); + String base_name = name_component[0]; + if (name_component.size() > 1 && name_component[1].is_valid_int() && name_component[1].to_int() >= 0) { + counter = name_component[1].to_int(); + } + new_name = base_name + "_" + itos(counter); + while (frames->has_animation(new_name)) { + counter++; + new_name = base_name + "_" + itos(counter); + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Duplicate Animation"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene()); + undo_redo->add_do_method(frames.ptr(), "duplicate_animation", edited_anim, new_name); + undo_redo->add_undo_method(frames.ptr(), "remove_animation", new_name); + undo_redo->add_do_method(this, "_select_animation", new_name); + undo_redo->add_undo_method(this, "_select_animation", edited_anim); + undo_redo->add_do_method(this, "_update_library"); + undo_redo->add_undo_method(this, "_update_library"); + undo_redo->commit_action(); + + animations->grab_focus(); +} + void SpriteFramesEditor::_animation_remove() { if (updating) { return; @@ -1541,6 +1577,7 @@ void SpriteFramesEditor::edit(Ref<SpriteFrames> p_frames) { _zoom_reset(); add_anim->set_disabled(read_only); + duplicate_anim->set_disabled(read_only); delete_anim->set_disabled(read_only); anim_speed->set_editable(!read_only); anim_loop->set_disabled(read_only); @@ -1865,6 +1902,11 @@ SpriteFramesEditor::SpriteFramesEditor() { hbc_animlist->add_child(add_anim); add_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_add)); + duplicate_anim = memnew(Button); + duplicate_anim->set_flat(true); + hbc_animlist->add_child(duplicate_anim); + duplicate_anim->connect(SceneStringName(pressed), callable_mp(this, &SpriteFramesEditor::_animation_duplicate)); + delete_anim = memnew(Button); delete_anim->set_theme_type_variation("FlatButton"); hbc_animlist->add_child(delete_anim); @@ -1918,6 +1960,8 @@ SpriteFramesEditor::SpriteFramesEditor() { add_anim->set_shortcut_context(animations); add_anim->set_shortcut(ED_SHORTCUT("sprite_frames/new_animation", TTR("Add Animation"), KeyModifierMask::CMD_OR_CTRL | Key::N)); + duplicate_anim->set_shortcut_context(animations); + duplicate_anim->set_shortcut(ED_SHORTCUT("sprite_frames/duplicate_animation", TTR("Duplicate Animation"), KeyModifierMask::CMD_OR_CTRL | Key::D)); delete_anim->set_shortcut_context(animations); delete_anim->set_shortcut(ED_SHORTCUT("sprite_frames/delete_animation", TTR("Delete Animation"), Key::KEY_DELETE)); diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index d6345a3ac8..a85a6b2453 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -121,6 +121,7 @@ class SpriteFramesEditor : public HSplitContainer { Vector<int> selection; Button *add_anim = nullptr; + Button *duplicate_anim = nullptr; Button *delete_anim = nullptr; SpinBox *anim_speed = nullptr; Button *anim_loop = nullptr; @@ -210,6 +211,7 @@ class SpriteFramesEditor : public HSplitContainer { void _animation_selected(); void _animation_name_edited(); void _animation_add(); + void _animation_duplicate(); void _animation_remove(); void _animation_remove_confirmed(); void _animation_search_text_changed(const String &p_text); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index c1bcd43b2e..1262fe1ce8 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -104,12 +104,12 @@ void TextEditor::set_edited_resource(const Ref<Resource> &p_res) { edited_res = p_res; Ref<TextFile> text_file = edited_res; - if (text_file != nullptr) { + if (text_file.is_valid()) { code_editor->get_text_editor()->set_text(text_file->get_text()); } Ref<JSON> json_file = edited_res; - if (json_file != nullptr) { + if (json_file.is_valid()) { code_editor->get_text_editor()->set_text(json_file->get_parsed_text()); } @@ -169,12 +169,12 @@ void TextEditor::reload_text() { int v = te->get_v_scroll(); Ref<TextFile> text_file = edited_res; - if (text_file != nullptr) { + if (text_file.is_valid()) { te->set_text(text_file->get_text()); } Ref<JSON> json_file = edited_res; - if (json_file != nullptr) { + if (json_file.is_valid()) { te->set_text(json_file->get_parsed_text()); } @@ -194,7 +194,7 @@ void TextEditor::_validate_script() { emit_signal(SNAME("edited_script_changed")); Ref<JSON> json_file = edited_res; - if (json_file != nullptr) { + if (json_file.is_valid()) { CodeEdit *te = code_editor->get_text_editor(); te->set_line_background_color(code_editor->get_error_pos().x, Color(0, 0, 0, 0)); @@ -245,12 +245,12 @@ void TextEditor::_bookmark_item_pressed(int p_idx) { void TextEditor::apply_code() { Ref<TextFile> text_file = edited_res; - if (text_file != nullptr) { + if (text_file.is_valid()) { text_file->set_text(code_editor->get_text_editor()->get_text()); } Ref<JSON> json_file = edited_res; - if (json_file != nullptr) { + if (json_file.is_valid()) { json_file->parse(code_editor->get_text_editor()->get_text(), true); } code_editor->get_text_editor()->get_syntax_highlighter()->update_cache(); diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index a3c1405553..d93466b5ba 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -169,7 +169,7 @@ void EditorInspectorPluginTexture::parse_begin(Object *p_object) { Ref<Image> image(Object::cast_to<Image>(p_object)); texture = ImageTexture::create_from_image(image); - ERR_FAIL_NULL_MSG(texture, "Failed to create the texture from an invalid image."); + ERR_FAIL_COND_MSG(texture.is_null(), "Failed to create the texture from an invalid image."); } add_custom_control(memnew(TexturePreview(texture, true))); diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 8dbf58e228..79915012a8 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -1482,30 +1482,36 @@ void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Tra debug_occlusion_color.push_back(color); RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); - Ref<OccluderPolygon2D> occluder = tile_data->get_occluder(occlusion_layer); - if (occluder.is_valid() && occluder->get_polygon().size() >= 3) { - p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color); + for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) { + Ref<OccluderPolygon2D> occluder = tile_data->get_occluder_polygon(occlusion_layer, i); + if (occluder.is_valid() && occluder->get_polygon().size() >= 3) { + p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color); + } } RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); } Variant TileDataOcclusionShapeEditor::_get_painted_value() { - Ref<OccluderPolygon2D> occluder_polygon; - if (polygon_editor->get_polygon_count() >= 1) { + Array polygons; + for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { + Ref<OccluderPolygon2D> occluder_polygon; occluder_polygon.instantiate(); - occluder_polygon->set_polygon(polygon_editor->get_polygon(0)); + occluder_polygon->set_polygon(polygon_editor->get_polygon(i)); + polygons.push_back(occluder_polygon); } - return occluder_polygon; + return polygons; } void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_NULL(tile_data); - Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer); polygon_editor->clear_polygons(); - if (occluder_polygon.is_valid()) { - polygon_editor->add_polygon(occluder_polygon->get_polygon()); + for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) { + Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder_polygon(occlusion_layer, i); + if (occluder_polygon.is_valid()) { + polygon_editor->add_polygon(occluder_polygon->get_polygon()); + } } polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile); } @@ -1513,8 +1519,13 @@ void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, const Variant &p_value) { TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_NULL(tile_data); - Ref<OccluderPolygon2D> occluder_polygon = p_value; - tile_data->set_occluder(occlusion_layer, occluder_polygon); + + Array polygons = p_value; + tile_data->set_occluder_polygons_count(occlusion_layer, polygons.size()); + for (int i = 0; i < polygons.size(); i++) { + Ref<OccluderPolygon2D> occluder_polygon = polygons[i]; + tile_data->set_occluder_polygon(occlusion_layer, i, occluder_polygon); + } polygon_editor->set_background_tile(p_tile_set_atlas_source, p_coords, p_alternative_tile); } @@ -1522,7 +1533,11 @@ void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atl Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_NULL_V(tile_data, Variant()); - return tile_data->get_occluder(occlusion_layer); + Array polygons; + for (int i = 0; i < tile_data->get_occluder_polygons_count(occlusion_layer); i++) { + polygons.push_back(tile_data->get_occluder_polygon(occlusion_layer, i)); + } + return polygons; } void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, const Variant &p_new_value) { @@ -1548,6 +1563,7 @@ void TileDataOcclusionShapeEditor::_notification(int p_what) { TileDataOcclusionShapeEditor::TileDataOcclusionShapeEditor() { polygon_editor = memnew(GenericTilePolygonEditor); + polygon_editor->set_multiple_polygon_mode(true); add_child(polygon_editor); } diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 224c4e434f..b1417b2878 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -2768,15 +2768,7 @@ void EditorPropertyTilePolygon::_add_focusable_children(Node *p_node) { void EditorPropertyTilePolygon::_polygons_changed() { if (String(count_property).is_empty()) { - if (base_type == "OccluderPolygon2D") { - // Single OccluderPolygon2D. - Ref<OccluderPolygon2D> occluder; - if (generic_tile_polygon_editor->get_polygon_count() >= 1) { - occluder.instantiate(); - occluder->set_polygon(generic_tile_polygon_editor->get_polygon(0)); - } - emit_changed(get_edited_property(), occluder); - } else if (base_type == "NavigationPolygon") { + if (base_type == "NavigationPolygon") { Ref<NavigationPolygon> navigation_polygon; if (generic_tile_polygon_editor->get_polygon_count() >= 1) { navigation_polygon.instantiate(); @@ -2798,19 +2790,24 @@ void EditorPropertyTilePolygon::_polygons_changed() { emit_changed(get_edited_property(), navigation_polygon); } } else { - if (base_type.is_empty()) { - // Multiple array of vertices. - Vector<String> changed_properties; - Array values; - int count = generic_tile_polygon_editor->get_polygon_count(); - changed_properties.push_back(count_property); - values.push_back(count); - for (int i = 0; i < count; i++) { - changed_properties.push_back(vformat(element_pattern, i)); + // Multiple array of vertices or OccluderPolygon2D. + Vector<String> changed_properties; + Array values; + int count = generic_tile_polygon_editor->get_polygon_count(); + changed_properties.push_back(count_property); + values.push_back(count); + for (int i = 0; i < count; i++) { + changed_properties.push_back(vformat(element_pattern, i)); + if (base_type.is_empty()) { values.push_back(generic_tile_polygon_editor->get_polygon(i)); + } else if (base_type == "OccluderPolygon2D") { + Ref<OccluderPolygon2D> occluder; + occluder.instantiate(); + occluder->set_polygon(generic_tile_polygon_editor->get_polygon(i)); + values.push_back(occluder); } - emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false); } + emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false); } } @@ -2834,15 +2831,8 @@ void EditorPropertyTilePolygon::update_property() { generic_tile_polygon_editor->clear_polygons(); if (String(count_property).is_empty()) { - if (base_type == "OccluderPolygon2D") { - // Single OccluderPolygon2D. - Ref<OccluderPolygon2D> occluder = get_edited_property_value(); - generic_tile_polygon_editor->clear_polygons(); - if (occluder.is_valid()) { - generic_tile_polygon_editor->add_polygon(occluder->get_polygon()); - } - } else if (base_type == "NavigationPolygon") { - // Single OccluderPolygon2D. + if (base_type == "NavigationPolygon") { + // Single NavigationPolygon. Ref<NavigationPolygon> navigation_polygon = get_edited_property_value(); generic_tile_polygon_editor->clear_polygons(); if (navigation_polygon.is_valid()) { @@ -2859,6 +2849,15 @@ void EditorPropertyTilePolygon::update_property() { for (int i = 0; i < count; i++) { generic_tile_polygon_editor->add_polygon(get_edited_object()->get(vformat(element_pattern, i))); } + } else if (base_type == "OccluderPolygon2D") { + // Multiple OccluderPolygon2D. + generic_tile_polygon_editor->clear_polygons(); + for (int i = 0; i < count; i++) { + Ref<OccluderPolygon2D> occluder = get_edited_object()->get(vformat(element_pattern, i)); + if (occluder.is_valid()) { + generic_tile_polygon_editor->add_polygon(occluder->get_polygon()); + } + } } } } @@ -2899,16 +2898,30 @@ bool EditorInspectorPluginTileData::can_handle(Object *p_object) { bool EditorInspectorPluginTileData::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField<PropertyUsageFlags> p_usage, const bool p_wide) { Vector<String> components = String(p_path).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + if (components.size() >= 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { // Occlusion layers. int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); ERR_FAIL_COND_V(layer_index < 0, false); - if (components[1] == "polygon") { + if (components[1] == "polygons_count") { EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon); - ep->setup_single_mode(p_path, "OccluderPolygon2D"); - add_property_editor(p_path, ep); + ep->setup_multiple_mode(vformat("occlusion_layer_%d/polygons", layer_index), vformat("occlusion_layer_%d/polygons_count", layer_index), vformat("occlusion_layer_%d/polygon_%%d/polygon", layer_index), "OccluderPolygon2D"); + Vector<String> properties; + properties.push_back(p_path); + int count = p_object->get(vformat("occlusion_layer_%d/polygons_count", layer_index)); + for (int i = 0; i < count; i++) { + properties.push_back(vformat("occlusion_layer_%d/polygon_%d/polygon", layer_index, i)); + } + add_property_editor_for_multiple_properties("Polygons", properties, ep); return true; } + // We keep the original editor for now, but here is the code that could be used if we need a custom editor for each polygon: + /*else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); + if (components[2] == "polygon") { + return true; + } + }*/ } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { // Physics layers. int layer_index = components[0].trim_prefix("physics_layer_").to_int(); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 3059d10c4c..bf8dab92f8 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -2201,7 +2201,7 @@ void VisualShaderEditor::_update_options_menu() { if (input.is_valid()) { input->set_shader_mode(visual_shader->get_mode()); input->set_shader_type(visual_shader->get_shader_type()); - if (!add_options[i].ops.is_empty() && add_options[i].ops[0].get_type() == Variant::STRING) { + if (!add_options[i].ops.is_empty() && add_options[i].ops[0].is_string()) { input->set_input_name((String)add_options[i].ops[0]); } } @@ -2995,8 +2995,8 @@ void VisualShaderEditor::_frame_title_popup_show(const Point2 &p_position, int p } frame_title_change_edit->set_text(node->get_title()); frame_title_change_popup->set_meta("id", p_node_id); - frame_title_change_popup->popup(); frame_title_change_popup->set_position(p_position); + frame_title_change_popup->popup(); // Select current text. frame_title_change_edit->grab_focus(); @@ -3281,7 +3281,7 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, const Vector<Vari VisualShaderNodeInput *input = Object::cast_to<VisualShaderNodeInput>(p_node); if (input) { - ERR_FAIL_COND(p_ops[0].get_type() != Variant::STRING); + ERR_FAIL_COND(!p_ops[0].is_string()); input->set_input_name((String)p_ops[0]); return; } diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp index d47270841d..8f609850b8 100644 --- a/editor/property_selector.cpp +++ b/editor/property_selector.cpp @@ -162,6 +162,9 @@ void PropertySelector::_update_search() { if (!found && !search_box->get_text().is_empty() && E.name.containsn(search_text)) { item->select(0); found = true; + } else if (!found && search_box->get_text().is_empty() && E.name == selected) { + item->select(0); + found = true; } item->set_selectable(0, true); @@ -173,6 +176,12 @@ void PropertySelector::_update_search() { if (category && category->get_first_child() == nullptr) { memdelete(category); //old category was unused } + + if (found) { + // As we call this while adding items, defer until list is completely populated. + callable_mp(search_options, &Tree::scroll_to_item).call_deferred(search_options->get_selected(), true); + } + } else { List<MethodInfo> methods; @@ -305,12 +314,20 @@ void PropertySelector::_update_search() { if (!found && !search_box->get_text().is_empty() && name.containsn(search_text)) { item->select(0); found = true; + } else if (!found && search_box->get_text().is_empty() && name == selected) { + item->select(0); + found = true; } } if (category && category->get_first_child() == nullptr) { memdelete(category); //old category was unused } + + if (found) { + // As we call this while adding items, defer until list is completely populated. + callable_mp(search_options, &Tree::scroll_to_item).call_deferred(search_options->get_selected(), true); + } } get_ok_button()->set_disabled(root->get_first_child() == nullptr); diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 8e135e7eae..00377a0dd2 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -79,6 +79,7 @@ #include "editor/plugins/cpu_particles_2d_editor_plugin.h" #include "editor/plugins/cpu_particles_3d_editor_plugin.h" #include "editor/plugins/curve_editor_plugin.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/editor_debugger_plugin.h" #include "editor/plugins/editor_resource_tooltip_plugins.h" #include "editor/plugins/font_config_plugin.h" @@ -179,6 +180,7 @@ void register_editor_types() { GDREGISTER_CLASS(EditorResourcePicker); GDREGISTER_CLASS(EditorScriptPicker); GDREGISTER_ABSTRACT_CLASS(EditorUndoRedoManager); + GDREGISTER_CLASS(EditorContextMenuPlugin); GDREGISTER_ABSTRACT_CLASS(FileSystemDock); GDREGISTER_VIRTUAL_CLASS(EditorFileSystemImportFormatSupportQuery); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 3110ecb926..2a39b11815 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -50,6 +50,7 @@ #include "editor/node_dock.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/canvas_item_editor_plugin.h" +#include "editor/plugins/editor_context_menu_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/reparent_dialog.h" @@ -213,6 +214,10 @@ void SceneTreeDock::shortcut_input(const Ref<InputEvent> &p_event) { } else if (ED_IS_SHORTCUT("scene_tree/delete", p_event)) { _tool_selected(TOOL_ERASE); } else { + int match_option = EditorNode::get_editor_data().match_context_menu_shortcut(EditorData::CONTEXT_SLOT_SCENE_TREE, p_event); + if (match_option) { + _tool_selected(match_option); + } return; } @@ -1236,6 +1241,10 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { break; } + if (!_validate_no_foreign()) { + break; + } + List<Node *> selection = editor_selection->get_selected_node_list(); List<Node *>::Element *e = selection.front(); if (e) { @@ -1482,6 +1491,13 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } break; default: { + // Editor context plugin. + if (p_tool >= EditorData::CONTEXT_MENU_ITEM_ID_BASE) { + List<Node *> selection = editor_selection->get_selected_node_list(); + EditorNode::get_editor_data().scene_tree_options_pressed(EditorData::CONTEXT_SLOT_SCENE_TREE, p_tool, selection); + break; + } + _filter_option_selected(p_tool); if (p_tool >= EDIT_SUBRESOURCE_BASE) { @@ -2235,8 +2251,7 @@ void SceneTreeDock::_node_reparent(NodePath p_path, bool p_keep_global_xform) { } void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, Vector<Node *> p_nodes, bool p_keep_global_xform) { - Node *new_parent = p_new_parent; - ERR_FAIL_NULL(new_parent); + ERR_FAIL_NULL(p_new_parent); if (p_nodes.size() == 0) { return; // Nothing to reparent. @@ -2276,7 +2291,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V // Prevent selecting the hovered node and keep the reparented node(s) selected instead. hovered_but_reparenting = true; - Node *validate = new_parent; + Node *validate = p_new_parent; while (validate) { ERR_FAIL_COND_MSG(p_nodes.has(validate), "Selection changed at some point. Can't reparent."); validate = validate->get_parent(); @@ -2298,7 +2313,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V // No undo implemented for this yet. Node *node = p_nodes[ni]; - fill_path_renames(node, new_parent, &path_renames); + fill_path_renames(node, p_new_parent, &path_renames); former_names.push_back(node->get_name()); List<Node *> owned; @@ -2308,7 +2323,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V owners.push_back(E); } - bool same_parent = new_parent == node->get_parent(); + bool same_parent = p_new_parent == node->get_parent(); if (same_parent && node->get_index(false) < p_position_in_parent + ni) { inc--; // If the child will generate a gap when moved, adjust. } @@ -2319,17 +2334,17 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V need_edit = select_node_hovered_at_end_of_drag; } else { undo_redo->add_do_method(node->get_parent(), "remove_child", node); - undo_redo->add_do_method(new_parent, "add_child", node, true); + undo_redo->add_do_method(p_new_parent, "add_child", node, true); } int new_position_in_parent = p_position_in_parent == -1 ? -1 : p_position_in_parent + inc; if (new_position_in_parent >= 0 || same_parent) { - undo_redo->add_do_method(new_parent, "move_child", node, new_position_in_parent); + undo_redo->add_do_method(p_new_parent, "move_child", node, new_position_in_parent); } EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); String old_name = former_names[ni]; - String new_name = new_parent->validate_child_name(node); + String new_name = p_new_parent->validate_child_name(node); // Name was modified, fix the path renames. if (old_name.casecmp_to(new_name) != 0) { @@ -2354,8 +2369,12 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V } } - undo_redo->add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(new_parent), new_name, new_position_in_parent); - undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index(false)); + // FIXME: Live editing for "Reparent to New Node" option is broken. + // We must get the path to `p_new_parent` *after* it was added to the scene. + if (p_new_parent->is_inside_tree()) { + undo_redo->add_do_method(ed, "live_debug_reparent_node", edited_scene->get_path_to(node), edited_scene->get_path_to(p_new_parent), new_name, new_position_in_parent); + undo_redo->add_undo_method(ed, "live_debug_reparent_node", NodePath(String(edited_scene->get_path_to(p_new_parent)).path_join(new_name)), edited_scene->get_path_to(node->get_parent()), node->get_name(), node->get_index(false)); + } if (p_keep_global_xform) { if (Object::cast_to<Node2D>(node)) { @@ -2375,7 +2394,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V undo_redo->add_do_method(AnimationPlayerEditor::get_singleton()->get_track_editor(), "set_root", node); } - undo_redo->add_undo_method(new_parent, "remove_child", node); + undo_redo->add_undo_method(p_new_parent, "remove_child", node); undo_redo->add_undo_method(node, "set_name", former_names[ni]); inc++; @@ -2393,7 +2412,7 @@ void SceneTreeDock::_do_reparent(Node *p_new_parent, int p_position_in_parent, V } int child_pos = node->get_index(false); - bool reparented_to_container = Object::cast_to<Container>(new_parent) && Object::cast_to<Control>(node); + bool reparented_to_container = Object::cast_to<Container>(p_new_parent) && Object::cast_to<Control>(node); undo_redo->add_undo_method(node->get_parent(), "add_child", node, true); undo_redo->add_undo_method(node->get_parent(), "move_child", node, child_pos); @@ -2773,10 +2792,10 @@ void SceneTreeDock::_selection_changed() { _update_script_button(); } -void SceneTreeDock::_do_create(Node *p_parent) { +Node *SceneTreeDock::_do_create(Node *p_parent) { Variant c = create_dialog->instantiate_selected(); Node *child = Object::cast_to<Node>(c); - ERR_FAIL_NULL(child); + ERR_FAIL_NULL_V(child, nullptr); String new_name = p_parent->validate_child_name(child); if (GLOBAL_GET("editor/naming/node_name_casing").operator int() != NAME_CASING_PASCAL_CASE) { @@ -2790,8 +2809,6 @@ void SceneTreeDock::_do_create(Node *p_parent) { if (edited_scene) { undo_redo->add_do_method(p_parent, "add_child", child, true); undo_redo->add_do_method(child, "set_owner", edited_scene); - undo_redo->add_do_method(editor_selection, "clear"); - undo_redo->add_do_method(editor_selection, "add_node", child); undo_redo->add_do_reference(child); undo_redo->add_undo_method(p_parent, "remove_child", child); @@ -2806,28 +2823,34 @@ void SceneTreeDock::_do_create(Node *p_parent) { undo_redo->add_undo_method(EditorNode::get_singleton(), "set_edited_scene", (Object *)nullptr); } + undo_redo->add_do_method(this, "_post_do_create", child); undo_redo->commit_action(); - _push_item(c); + + return child; +} + +void SceneTreeDock::_post_do_create(Node *p_child) { editor_selection->clear(); - editor_selection->add_node(child); - if (Object::cast_to<Control>(c)) { - //make editor more comfortable, so some controls don't appear super shrunk - Control *ct = Object::cast_to<Control>(c); + editor_selection->add_node(p_child); + _push_item(p_child); - Size2 ms = ct->get_minimum_size(); + // Make editor more comfortable, so some controls don't appear super shrunk. + Control *control = Object::cast_to<Control>(p_child); + if (control) { + Size2 ms = control->get_minimum_size(); if (ms.width < 4) { ms.width = 40; } if (ms.height < 4) { ms.height = 40; } - if (ct->is_layout_rtl()) { - ct->set_position(ct->get_position() - Vector2(ms.x, 0)); + if (control->is_layout_rtl()) { + control->set_position(control->get_position() - Vector2(ms.x, 0)); } - ct->set_size(ms); + control->set_size(ms); } - emit_signal(SNAME("node_created"), c); + emit_signal(SNAME("node_created"), p_child); } void SceneTreeDock::_create() { @@ -2910,22 +2933,24 @@ void SceneTreeDock::_create() { } Node *parent = nullptr; + int original_position = -1; if (only_one_top_node) { parent = top_node->get_parent(); + original_position = top_node->get_index(false); } else { parent = top_node->get_parent()->get_parent(); } - _do_create(parent); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action_for_history(TTR("Reparent to New Node"), editor_data->get_current_edited_scene_history_id()); + + Node *last_created = _do_create(parent); Vector<Node *> nodes; for (Node *E : selection) { nodes.push_back(E); } - // This works because editor_selection was cleared and populated with last created node in _do_create() - Node *last_created = editor_selection->get_selected_node_list().front()->get(); - if (center_parent) { // Find parent type and only average positions of relevant nodes. Node3D *parent_node_3d = Object::cast_to<Node3D>(last_created); @@ -2964,6 +2989,11 @@ void SceneTreeDock::_create() { } _do_reparent(last_created, -1, nodes, true); + + if (only_one_top_node) { + undo_redo->add_do_method(parent, "move_child", last_created, original_position); + } + undo_redo->commit_action(); } scene_tree->get_scene_tree()->grab_focus(); @@ -3682,7 +3712,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { if (selection.size() == 1) { bool is_external = (!selection.front()->get()->get_scene_file_path().is_empty()); if (is_external) { - bool is_inherited = selection.front()->get()->get_scene_inherited_state() != nullptr; + bool is_inherited = selection.front()->get()->get_scene_inherited_state().is_valid(); bool is_top_level = selection.front()->get()->get_owner() == nullptr; if (is_inherited && is_top_level) { menu->add_separator(); @@ -3729,6 +3759,15 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { menu->add_separator(); menu->add_icon_shortcut(get_editor_theme_icon(SNAME("Remove")), ED_GET_SHORTCUT("scene_tree/delete"), TOOL_ERASE); } + + Vector<String> p_paths; + Node *root = EditorNode::get_singleton()->get_edited_scene(); + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + String node_path = root->get_path().rel_path_to(E->get()->get_path()); + p_paths.push_back(node_path); + } + EditorNode::get_editor_data().add_options_from_plugins(menu, EditorData::CONTEXT_SLOT_SCENE_TREE, p_paths); + menu->reset_size(); menu->set_position(p_menu_pos); menu->popup(); @@ -4369,6 +4408,7 @@ void SceneTreeDock::_edit_subresource(int p_idx, const PopupMenu *p_from_menu) { } void SceneTreeDock::_bind_methods() { + ClassDB::bind_method(D_METHOD("_post_do_create"), &SceneTreeDock::_post_do_create); ClassDB::bind_method(D_METHOD("_set_owners"), &SceneTreeDock::_set_owners); ClassDB::bind_method(D_METHOD("_reparent_nodes_to_root"), &SceneTreeDock::_reparent_nodes_to_root); ClassDB::bind_method(D_METHOD("_reparent_nodes_to_paths_with_transform_and_name"), &SceneTreeDock::_reparent_nodes_to_paths_with_transform_and_name); @@ -4634,6 +4674,7 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec EDITOR_DEF("interface/editors/show_scene_tree_root_selection", true); EDITOR_DEF("interface/editors/derive_script_globals_by_name", true); EDITOR_DEF("docks/scene_tree/ask_before_deleting_related_animation_tracks", true); + EDITOR_DEF("docks/scene_tree/ask_before_revoking_unique_name", true); EDITOR_DEF("_use_favorites_root_selection", false); Resource::_update_configuration_warning = _update_configuration_warning; diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index f01e6598cf..1807ec5896 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -179,7 +179,8 @@ class SceneTreeDock : public VBoxContainer { bool first_enter = true; void _create(); - void _do_create(Node *p_parent); + Node *_do_create(Node *p_parent); + void _post_do_create(Node *p_child); Node *scene_root = nullptr; Node *edited_scene = nullptr; Node *pending_click_select = nullptr; diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp index f8673ddd44..0cb4952b04 100644 --- a/editor/script_create_dialog.cpp +++ b/editor/script_create_dialog.cpp @@ -111,15 +111,7 @@ static Vector<String> _get_hierarchy(const String &p_class_name) { void ScriptCreateDialog::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - Ref<Texture2D> language_icon = get_editor_theme_icon(ScriptServer::get_language(i)->get_type()); - if (language_icon.is_valid()) { - language_menu->set_item_icon(i, language_icon); - } - } - + case NOTIFICATION_ENTER_TREE: { String last_language = EditorSettings::get_singleton()->get_project_metadata("script_setup", "last_selected_language", ""); if (!last_language.is_empty()) { for (int i = 0; i < language_menu->get_item_count(); i++) { @@ -131,9 +123,16 @@ void ScriptCreateDialog::_notification(int p_what) { } else { language_menu->select(default_language); } - if (EditorSettings::get_singleton()->has_meta("script_setup_use_script_templates")) { - is_using_templates = bool(EditorSettings::get_singleton()->get_meta("script_setup_use_script_templates")); - use_templates->set_pressed(is_using_templates); + is_using_templates = EDITOR_DEF("_script_setup_use_script_templates", false); + use_templates->set_pressed(is_using_templates); + } break; + + case NOTIFICATION_THEME_CHANGED: { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + Ref<Texture2D> language_icon = get_editor_theme_icon(ScriptServer::get_language(i)->get_type()); + if (language_icon.is_valid()) { + language_menu->set_item_icon(i, language_icon); + } } path_button->set_icon(get_editor_theme_icon(SNAME("Folder"))); @@ -297,12 +296,9 @@ void ScriptCreateDialog::_template_changed(int p_template) { EditorSettings::get_singleton()->set_project_metadata("script_setup", "templates_dictionary", dic_templates_project); } else { // Save template info to editor dictionary (not a project template). - Dictionary dic_templates; - if (EditorSettings::get_singleton()->has_meta("script_setup_templates_dictionary")) { - dic_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup_templates_dictionary"); - } + Dictionary dic_templates = EDITOR_GET("_script_setup_templates_dictionary"); dic_templates[parent_name->get_text()] = sinfo.get_hash(); - EditorSettings::get_singleton()->set_meta("script_setup_templates_dictionary", dic_templates); + EditorSettings::get_singleton()->set("_script_setup_templates_dictionary", dic_templates); // Remove template from project dictionary as we last used an editor level template. Dictionary dic_templates_project = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary()); if (dic_templates_project.has(parent_name->get_text())) { @@ -415,7 +411,7 @@ void ScriptCreateDialog::_built_in_pressed() { void ScriptCreateDialog::_use_template_pressed() { is_using_templates = use_templates->is_pressed(); - EditorSettings::get_singleton()->set_meta("script_setup_use_script_templates", is_using_templates); + EditorSettings::get_singleton()->set("_script_setup_use_script_templates", is_using_templates); validation_panel->update(); } @@ -509,10 +505,7 @@ void ScriptCreateDialog::_update_template_menu() { if (is_language_using_templates) { // Get the latest templates used for each type of node from project settings then global settings. Dictionary last_local_templates = EditorSettings::get_singleton()->get_project_metadata("script_setup", "templates_dictionary", Dictionary()); - Dictionary last_global_templates; - if (EditorSettings::get_singleton()->has_meta("script_setup_templates_dictionary")) { - last_global_templates = (Dictionary)EditorSettings::get_singleton()->get_meta("script_setup_templates_dictionary"); - } + Dictionary last_global_templates = EDITOR_GET("_script_setup_templates_dictionary"); String inherits_base_type = parent_name->get_text(); // If it inherits from a script, get its parent class first. @@ -825,6 +818,8 @@ void ScriptCreateDialog::_bind_methods() { } ScriptCreateDialog::ScriptCreateDialog() { + EDITOR_DEF("_script_setup_templates_dictionary", Dictionary()); + /* Main Controls */ GridContainer *gc = memnew(GridContainer); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index f5a790353a..3041857d83 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -183,7 +183,7 @@ Ref<EditorTheme> EditorThemeManager::_create_base_theme(const Ref<EditorTheme> & // If settings are comparable to the old theme, then just copy existing icons over. // Otherwise, regenerate them. - bool keep_old_icons = (p_old_theme != nullptr && theme->get_generated_icons_hash() == p_old_theme->get_generated_icons_hash()); + bool keep_old_icons = (p_old_theme.is_valid() && theme->get_generated_icons_hash() == p_old_theme->get_generated_icons_hash()); if (keep_old_icons) { print_verbose("EditorTheme: Can keep old icons, copying."); editor_copy_icons(theme, p_old_theme); @@ -865,7 +865,6 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the // CheckBox. { Ref<StyleBoxFlat> checkbox_style = p_config.panel_container_style->duplicate(); - checkbox_style->set_content_margin_individual((p_config.increased_margin + 2) * EDSCALE, p_config.base_margin * EDSCALE, (p_config.increased_margin + 2) * EDSCALE, p_config.base_margin * EDSCALE); p_theme->set_stylebox(CoreStringName(normal), "CheckBox", checkbox_style); p_theme->set_stylebox(SceneStringName(pressed), "CheckBox", checkbox_style); @@ -1165,9 +1164,6 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the // LineEdit & TextEdit. { Ref<StyleBoxFlat> text_editor_style = p_config.button_style->duplicate(); - // The original button_style style has an extra 1 pixel offset that makes LineEdits not align with Buttons, - // so this compensates for that. - text_editor_style->set_content_margin(SIDE_TOP, text_editor_style->get_content_margin(SIDE_TOP) - 1 * EDSCALE); // Don't round the bottom corners to make the line look sharper. text_editor_style->set_corner_radius(CORNER_BOTTOM_LEFT, 0); @@ -1507,7 +1503,9 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the p_theme->set_constant("buttons_vertical_separation", "SpinBox", 0); p_theme->set_constant("field_and_buttons_separation", "SpinBox", 2); p_theme->set_constant("buttons_width", "SpinBox", 16); +#ifndef DISABLE_DEPRECATED p_theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1); +#endif } // ProgressBar. diff --git a/main/main.cpp b/main/main.cpp index 101fc642b6..18ffedef18 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1037,7 +1037,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph if (arg == "--audio-driver" || arg == "--display-driver" || arg == "--rendering-method" || - arg == "--rendering-driver") { + arg == "--rendering-driver" || + arg == "--xr-mode") { if (N) { forwardable_cli_arguments[CLI_SCOPE_TOOL].push_back(arg); forwardable_cli_arguments[CLI_SCOPE_TOOL].push_back(N->get()); @@ -2327,15 +2328,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph } } - // note this is the desired rendering driver, it doesn't mean we will get it. - // TODO - make sure this is updated in the case of fallbacks, so that the user interface - // shows the correct driver string. - OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); - OS::get_singleton()->set_current_rendering_method(rendering_method); - // always convert to lower case for consistency in the code rendering_driver = rendering_driver.to_lower(); + OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); + OS::get_singleton()->set_current_rendering_method(rendering_method); + if (use_custom_res) { if (!force_res) { window_size.width = GLOBAL_GET("display/window/size/viewport_width"); @@ -2906,6 +2904,8 @@ Error Main::setup2(bool p_show_boot_logo) { Error err; display_server = DisplayServer::create(display_driver_idx, rendering_driver, window_mode, window_vsync_mode, window_flags, window_position, window_size, init_screen, context, err); if (err != OK || display_server == nullptr) { + String last_name = DisplayServer::get_create_function_name(display_driver_idx); + // We can't use this display server, try other ones as fallback. // Skip headless (always last registered) because that's not what users // would expect if they didn't request it explicitly. @@ -2913,6 +2913,9 @@ Error Main::setup2(bool p_show_boot_logo) { if (i == display_driver_idx) { continue; // Don't try the same twice. } + String name = DisplayServer::get_create_function_name(i); + WARN_PRINT(vformat("Display driver %s failed, falling back to %s.", last_name, name)); + display_server = DisplayServer::create(i, rendering_driver, window_mode, window_vsync_mode, window_flags, window_position, window_size, init_screen, context, err); if (err == OK && display_server != nullptr) { break; @@ -3546,13 +3549,16 @@ int Main::start() { gdscript_docs_path = E->next()->get(); #endif } else if (E->get() == "--export-release") { + ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting."); editor = true; //needs editor _export_preset = E->next()->get(); } else if (E->get() == "--export-debug") { + ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting."); editor = true; //needs editor _export_preset = E->next()->get(); export_debug = true; } else if (E->get() == "--export-pack") { + ERR_FAIL_COND_V_MSG(!editor && !found_project, EXIT_FAILURE, "Please provide a valid project path when exporting, aborting."); editor = true; _export_preset = E->next()->get(); export_pack_only = true; @@ -3564,6 +3570,8 @@ int Main::start() { if (parsed_pair) { E = E->next(); } + } else if (E->get().begins_with("--export-")) { + ERR_FAIL_V_MSG(EXIT_FAILURE, "Missing export preset name, aborting."); } #ifdef TOOLS_ENABLED // Handle case where no path is given to --doctool. @@ -4407,7 +4415,7 @@ bool Main::iteration() { } #ifdef TOOLS_ENABLED - if (wait_for_import && EditorFileSystem::get_singleton()->doing_first_scan()) { + if (wait_for_import && EditorFileSystem::get_singleton() && EditorFileSystem::get_singleton()->doing_first_scan()) { exit = false; } #endif diff --git a/misc/dist/macos_template.app/Contents/Info.plist b/misc/dist/macos_template.app/Contents/Info.plist index 78bb559c0e..708498dd9c 100644 --- a/misc/dist/macos_template.app/Contents/Info.plist +++ b/misc/dist/macos_template.app/Contents/Info.plist @@ -49,12 +49,17 @@ $usage_descriptions <string>NSApplication</string> <key>LSApplicationCategoryType</key> <string>public.app-category.$app_category</string> - <key>LSMinimumSystemVersion</key> - <string>$min_version</string> + <key>LSArchitecturePriority</key> + <array> + <string>arm64</string> + <string>x86_64</string> + </array> <key>LSMinimumSystemVersionByArchitecture</key> <dict> + <key>arm64</key> + <string>$min_version_arm64</string> <key>x86_64</key> - <string>$min_version</string> + <string>$min_version_x86_64</string> </dict> <key>NSHighResolutionCapable</key> $highres diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected index 851ef69261..ad77e30e11 100644 --- a/misc/extension_api_validation/4.3-stable.expected +++ b/misc/extension_api_validation/4.3-stable.expected @@ -65,3 +65,18 @@ Validate extension JSON: Error: Field 'classes/AudioStreamPlayer2D/properties/pl Validate extension JSON: Error: Field 'classes/AudioStreamPlayer3D/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing". These setters have been renamed to expose them. GDExtension language bindings couldn't have exposed these properties before. + + +GH-94322 +-------- +Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_node_selector/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_property_selector/arguments': size changed value in new API, from 3 to 4. + +Added optional argument to popup_property_selector and popup_node_selector to specify the current value. + + +GH-94434 +-------- +Validate extension JSON: Error: Field 'classes/OS/methods/execute_with_pipe/arguments': size changed value in new API, from 2 to 3. + +Optional argument added. Compatibility method registered. diff --git a/modules/betsy/bc6h.glsl b/modules/betsy/bc6h.glsl index 0d10d378fd..37e7591aea 100644 --- a/modules/betsy/bc6h.glsl +++ b/modules/betsy/bc6h.glsl @@ -1,7 +1,7 @@ #[versions] signed = "#define SIGNED"; -unsigned = ""; +unsigned = "#define QUALITY"; // The "Quality" preset causes artifacting on signed data, so for now it's exclusive to unsigned. #[compute] #version 450 @@ -10,10 +10,6 @@ unsigned = ""; #include "UavCrossPlatform_piece_all.glsl" #VERSION_DEFINES -#define QUALITY - -//SIGNED macro is WIP -//#define SIGNED float3 f32tof16(float3 value) { return float3(packHalf2x16(float2(value.x, 0.0)), @@ -48,11 +44,59 @@ params; const float HALF_MAX = 65504.0f; const uint PATTERN_NUM = 32u; +#ifdef SIGNED +const float HALF_MIN = -65504.0f; +#else +const float HALF_MIN = 0.0f; +#endif + +#ifdef SIGNED +// https://github.com/godotengine/godot/pull/96377#issuecomment-2323488254 +// https://github.com/godotengine/godot/pull/96377#issuecomment-2323450950 +bool isNegative(float a) { + return a < 0.0f; +} + +float CalcSignlessMSLE(float a, float b) { + float err = log2((b + 1.0f) / (a + 1.0f)); + err = err * err; + return err; +} + +float CrossCalcMSLE(float a, float b) { + float result = 0.0f; + result += CalcSignlessMSLE(0.0f, abs(a)); + result += CalcSignlessMSLE(0.0f, abs(b)); + return result; +} + +float CalcMSLE(float3 a, float3 b) { + float result = 0.0f; + if (isNegative(a.x) != isNegative(b.x)) { + result += CrossCalcMSLE(a.x, b.x); + } else { + result += CalcSignlessMSLE(abs(a.x), abs(b.x)); + } + if (isNegative(a.y) != isNegative(b.y)) { + result += CrossCalcMSLE(a.y, b.y); + } else { + result += CalcSignlessMSLE(abs(a.y), abs(b.y)); + } + if (isNegative(a.z) != isNegative(b.z)) { + result += CrossCalcMSLE(a.z, b.z); + } else { + result += CalcSignlessMSLE(abs(a.z), abs(b.z)); + } + + return result; +} +#else float CalcMSLE(float3 a, float3 b) { float3 err = log2((b + 1.0f) / (a + 1.0f)); err = err * err; return err.x + err.y + err.z; } +#endif uint PatternFixupID(uint i) { uint ret = 15u; @@ -176,11 +220,6 @@ float3 Unquantize10(float3 x) { float3 FinishUnquantize(float3 endpoint0Unq, float3 endpoint1Unq, float weight) { float3 comp = (endpoint0Unq * (64.0f - weight) + endpoint1Unq * weight + 32.0f) * (31.0f / 2048.0f); - /*float3 signVal; - signVal.x = comp.x >= 0.0f ? 0.0f : 0x8000; - signVal.y = comp.y >= 0.0f ? 0.0f : 0x8000; - signVal.z = comp.z >= 0.0f ? 0.0f : 0x8000;*/ - //return f16tof32( uint3( signVal + abs( comp ) ) ); return f16tof32(uint3(comp)); } #endif @@ -207,6 +246,7 @@ uint ComputeIndex4(float texelPos, float endPoint0Pos, float endPoint1Pos) { return uint(clamp(r * 14.93333f + 0.03333f + 0.5f, 0.0f, 15.0f)); } +// This adds a bitflag to quantized values that signifies whether they are negative. void SignExtend(inout float3 v1, uint mask, uint signFlag) { int3 v = int3(v1); v.x = (v.x & int(mask)) | (v.x < 0 ? int(signFlag) : 0); @@ -215,6 +255,7 @@ void SignExtend(inout float3 v1, uint mask, uint signFlag) { v1 = v; } +// Encodes a block with mode 11 (2x 10-bit endpoints). void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) { // compute endpoints (min/max RGB bbox) float3 blockMin = texels[0]; @@ -250,6 +291,12 @@ void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) { float endPoint0Pos = f32tof16(dot(blockMin, blockDir)); float endPoint1Pos = f32tof16(dot(blockMax, blockDir)); +#ifdef SIGNED + int maxVal10 = 0x1FF; + endpoint0 = clamp(endpoint0, -maxVal10, maxVal10); + endpoint1 = clamp(endpoint1, -maxVal10, maxVal10); +#endif + // check if endpoint swap is required float fixupTexelPos = f32tof16(dot(texels[0], blockDir)); uint fixupIndex = ComputeIndex4(fixupTexelPos, endPoint0Pos, endPoint1Pos); @@ -276,6 +323,11 @@ void EncodeP1(inout uint4 block, inout float blockMSLE, float3 texels[16]) { msle += CalcMSLE(texels[i], texelUnc); } +#ifdef SIGNED + SignExtend(endpoint0, 0x1FF, 0x200); + SignExtend(endpoint1, 0x1FF, 0x200); +#endif + // encode block for mode 11 blockMSLE = msle; block.x = 0x03; @@ -316,11 +368,12 @@ float DistToLineSq(float3 PointOnLine, float3 LineDirection, float3 Point) { return dot(x, x); } +// Gets the deviation from the source data of a particular pattern (smaller is better). float EvaluateP2Pattern(uint pattern, float3 texels[16]) { float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); - float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f); + float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN); float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); - float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f); + float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN); for (uint i = 0; i < 16; ++i) { uint paletteID = Pattern(pattern, i); @@ -350,11 +403,12 @@ float EvaluateP2Pattern(uint pattern, float3 texels[16]) { return sqDistanceFromLine; } +// Encodes a block with either mode 2 (7-bit base, 3x 6-bit delta), or mode 6 (9-bit base, 3x 5-bit delta). Both use pattern encoding. void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, float3 texels[16]) { float3 p0BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); - float3 p0BlockMax = float3(0.0f, 0.0f, 0.0f); + float3 p0BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN); float3 p1BlockMin = float3(HALF_MAX, HALF_MAX, HALF_MAX); - float3 p1BlockMax = float3(0.0f, 0.0f, 0.0f); + float3 p1BlockMax = float3(HALF_MIN, HALF_MIN, HALF_MIN); for (uint i = 0u; i < 16u; ++i) { uint paletteID = Pattern(pattern, i); @@ -430,6 +484,13 @@ void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, flo endpoint952 = clamp(endpoint952, -maxVal95, maxVal95); endpoint953 = clamp(endpoint953, -maxVal95, maxVal95); +#ifdef SIGNED + int maxVal7 = 0x3F; + int maxVal9 = 0xFF; + endpoint760 = clamp(endpoint760, -maxVal7, maxVal7); + endpoint950 = clamp(endpoint950, -maxVal9, maxVal9); +#endif + float3 endpoint760Unq = Unquantize7(endpoint760); float3 endpoint761Unq = Unquantize7(endpoint760 + endpoint761); float3 endpoint762Unq = Unquantize7(endpoint760 + endpoint762); @@ -465,6 +526,11 @@ void EncodeP2Pattern(inout uint4 block, inout float blockMSLE, uint pattern, flo SignExtend(endpoint952, 0xF, 0x10); SignExtend(endpoint953, 0xF, 0x10); +#ifdef SIGNED + SignExtend(endpoint760, 0x3F, 0x40); + SignExtend(endpoint950, 0xFF, 0x100); +#endif + // encode block float p2MSLE = min(msle76, msle95); if (p2MSLE < blockMSLE) { @@ -637,7 +703,7 @@ void main() { float bestScore = EvaluateP2Pattern(0, texels); uint bestPattern = 0; - for (uint i = 1u; i < 32u; ++i) { + for (uint i = 1u; i < PATTERN_NUM; ++i) { float score = EvaluateP2Pattern(i, texels); if (score < bestScore) { diff --git a/modules/betsy/image_compress_betsy.cpp b/modules/betsy/image_compress_betsy.cpp index 7f723826d1..bc72203b2f 100644 --- a/modules/betsy/image_compress_betsy.cpp +++ b/modules/betsy/image_compress_betsy.cpp @@ -36,6 +36,9 @@ #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_context_driver_vulkan.h" #endif +#if defined(METAL_ENABLED) +#include "drivers/metal/rendering_context_driver_metal.h" +#endif #include "bc6h.glsl.gen.h" @@ -66,10 +69,16 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) { if (rd == nullptr) { #if defined(RD_ENABLED) -#if defined(VULKAN_ENABLED) - rcd = memnew(RenderingContextDriverVulkan); +#if defined(METAL_ENABLED) + rcd = memnew(RenderingContextDriverMetal); rd = memnew(RenderingDevice); #endif +#if defined(VULKAN_ENABLED) + if (rcd == nullptr) { + rcd = memnew(RenderingContextDriverVulkan); + rd = memnew(RenderingDevice); + } +#endif #endif if (rcd != nullptr && rd != nullptr) { err = rcd->initialize(); @@ -116,6 +125,7 @@ Error _compress_betsy(BetsyFormat p_format, Image *r_img) { } if (err != OK) { + compute_shader->print_errors("Betsy compress shader"); memdelete(rd); if (rcd != nullptr) { memdelete(rcd); diff --git a/modules/fbx/fbx_document.cpp b/modules/fbx/fbx_document.cpp index 4e1a00cad6..8d4d0234da 100644 --- a/modules/fbx/fbx_document.cpp +++ b/modules/fbx/fbx_document.cpp @@ -875,7 +875,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) { const int material = int(fbx_material->typed_id); ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT); Ref<Material> mat3d = p_state->materials[material]; - ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT); Ref<BaseMaterial3D> base_material = mat3d; if (has_vertex_color && base_material.is_valid()) { @@ -891,7 +891,7 @@ Error FBXDocument::_parse_meshes(Ref<FBXState> p_state) { } mat = mat3d; } - ERR_FAIL_NULL_V(mat, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT); mat_name = mat->get_name(); } import_mesh->add_surface(primitive, array, morphs, @@ -1056,7 +1056,7 @@ GLTFImageIndex FBXDocument::_parse_image_save_image(Ref<FBXState> p_state, const } Error FBXDocument::_parse_images(Ref<FBXState> p_state, const String &p_base_path) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); const ufbx_scene *fbx_scene = p_state->scene.get(); for (int texture_i = 0; texture_i < static_cast<int>(fbx_scene->texture_files.count); texture_i++) { @@ -2118,7 +2118,6 @@ Error FBXDocument::_parse(Ref<FBXState> p_state, String p_path, Ref<FileAccess> Node *FBXDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) { Ref<FBXState> state = p_state; ERR_FAIL_COND_V(state.is_null(), nullptr); - ERR_FAIL_NULL_V(state, nullptr); ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr); p_state->set_bake_fps(p_bake_fps); GLTFNodeIndex fbx_root = state->root_nodes.write[0]; @@ -2246,7 +2245,7 @@ Error FBXDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint3 Error err; Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err); ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN); - ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN); + ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN); String base_path = p_base_path; if (base_path.is_empty()) { base_path = p_path.get_base_dir(); diff --git a/modules/gdscript/editor/gdscript_docgen.cpp b/modules/gdscript/editor/gdscript_docgen.cpp index 35b69fab8c..32ef429b0d 100644 --- a/modules/gdscript/editor/gdscript_docgen.cpp +++ b/modules/gdscript/editor/gdscript_docgen.cpp @@ -84,6 +84,15 @@ void GDScriptDocGen::_doctype_from_gdtype(const GDType &p_gdtype, String &r_type return; } } + if (p_gdtype.builtin_type == Variant::DICTIONARY && p_gdtype.has_container_element_types()) { + String key, value; + _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(0), key, r_enum); + _doctype_from_gdtype(p_gdtype.get_container_element_type_or_variant(1), value, r_enum); + if (key != "Variant" || value != "Variant") { + r_type = "Dictionary[" + key + ", " + value + "]"; + return; + } + } r_type = Variant::get_type_name(p_gdtype.builtin_type); return; case GDType::NATIVE: @@ -155,34 +164,82 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re return "<Object>"; case Variant::DICTIONARY: { const Dictionary dict = p_variant; + String result; - if (dict.is_empty()) { - return "{}"; - } + if (dict.is_typed()) { + result += "Dictionary["; + + Ref<Script> key_script = dict.get_typed_key_script(); + if (key_script.is_valid()) { + if (key_script->get_global_name() != StringName()) { + result += key_script->get_global_name(); + } else if (!key_script->get_path().get_file().is_empty()) { + result += key_script->get_path().get_file(); + } else { + result += dict.get_typed_key_class_name(); + } + } else if (dict.get_typed_key_class_name() != StringName()) { + result += dict.get_typed_key_class_name(); + } else if (dict.is_typed_key()) { + result += Variant::get_type_name((Variant::Type)dict.get_typed_key_builtin()); + } else { + result += "Variant"; + } + + result += ", "; - if (p_recursion_level > MAX_RECURSION_LEVEL) { - return "{...}"; + Ref<Script> value_script = dict.get_typed_value_script(); + if (value_script.is_valid()) { + if (value_script->get_global_name() != StringName()) { + result += value_script->get_global_name(); + } else if (!value_script->get_path().get_file().is_empty()) { + result += value_script->get_path().get_file(); + } else { + result += dict.get_typed_value_class_name(); + } + } else if (dict.get_typed_value_class_name() != StringName()) { + result += dict.get_typed_value_class_name(); + } else if (dict.is_typed_value()) { + result += Variant::get_type_name((Variant::Type)dict.get_typed_value_builtin()); + } else { + result += "Variant"; + } + + result += "]("; } - List<Variant> keys; - dict.get_key_list(&keys); - keys.sort(); + if (dict.is_empty()) { + result += "{}"; + } else if (p_recursion_level > MAX_RECURSION_LEVEL) { + result += "{...}"; + } else { + result += "{"; + + List<Variant> keys; + dict.get_key_list(&keys); + keys.sort(); - String data; - for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { - if (E->prev()) { - data += ", "; + for (List<Variant>::Element *E = keys.front(); E; E = E->next()) { + if (E->prev()) { + result += ", "; + } + result += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1); } - data += _docvalue_from_variant(E->get(), p_recursion_level + 1) + ": " + _docvalue_from_variant(dict[E->get()], p_recursion_level + 1); + + result += "}"; + } + + if (dict.is_typed()) { + result += ")"; } - return "{" + data + "}"; + return result; } break; case Variant::ARRAY: { const Array array = p_variant; String result; - if (array.get_typed_builtin() != Variant::NIL) { + if (array.is_typed()) { result += "Array["; Ref<Script> script = array.get_typed_script(); @@ -209,16 +266,18 @@ String GDScriptDocGen::_docvalue_from_variant(const Variant &p_variant, int p_re result += "[...]"; } else { result += "["; + for (int i = 0; i < array.size(); i++) { if (i > 0) { result += ", "; } result += _docvalue_from_variant(array[i], p_recursion_level + 1); } + result += "]"; } - if (array.get_typed_builtin() != Variant::NIL) { + if (array.is_typed()) { result += ")"; } diff --git a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp index d74d316704..b31ae878ce 100644 --- a/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp +++ b/modules/gdscript/editor/gdscript_translation_parser_plugin.cpp @@ -71,7 +71,7 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) { ERR_FAIL_NULL_V(p_expression, false); - return p_expression->is_constant && (p_expression->reduced_value.get_type() == Variant::STRING || p_expression->reduced_value.get_type() == Variant::STRING_NAME); + return p_expression->is_constant && p_expression->reduced_value.is_string(); } void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) { diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index e3f2a61090..276a12f5de 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -138,7 +138,7 @@ void GDScript::_super_implicit_constructor(GDScript *p_script, GDScriptInstance } } ERR_FAIL_NULL(p_script->implicit_initializer); - if (likely(valid)) { + if (likely(p_script->valid)) { p_script->implicit_initializer->call(p_instance, nullptr, 0, r_error); } else { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index 7e29a9c0fe..7f0d5005cb 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -724,6 +724,18 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type result.set_container_element_type(0, container_type); } } + if (result.builtin_type == Variant::DICTIONARY) { + GDScriptParser::DataType key_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(0))); + if (key_type.kind != GDScriptParser::DataType::VARIANT) { + key_type.is_constant = false; + result.set_container_element_type(0, key_type); + } + GDScriptParser::DataType value_type = type_from_metatype(resolve_datatype(p_type->get_container_type_or_null(1))); + if (value_type.kind != GDScriptParser::DataType::VARIANT) { + value_type.is_constant = false; + result.set_container_element_type(1, value_type); + } + } } else if (class_exists(first)) { // Native engine classes. result.kind = GDScriptParser::DataType::NATIVE; @@ -884,11 +896,16 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type if (!p_type->container_types.is_empty()) { if (result.builtin_type == Variant::ARRAY) { if (p_type->container_types.size() != 1) { - push_error("Arrays require exactly one collection element type.", p_type); + push_error(R"(Typed arrays require exactly one collection element type.)", p_type); + return bad_type; + } + } else if (result.builtin_type == Variant::DICTIONARY) { + if (p_type->container_types.size() != 2) { + push_error(R"(Typed dictionaries require exactly two collection element types.)", p_type); return bad_type; } } else { - push_error("Only arrays can specify collection element types.", p_type); + push_error(R"(Only arrays and dictionaries can specify collection element types.)", p_type); return bad_type; } } @@ -1926,6 +1943,11 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi if (has_specified_type && specified_type.has_container_element_type(0)) { update_array_literal_element_type(array, specified_type.get_container_element_type(0)); } + } else if (p_assignable->initializer->type == GDScriptParser::Node::DICTIONARY) { + GDScriptParser::DictionaryNode *dictionary = static_cast<GDScriptParser::DictionaryNode *>(p_assignable->initializer); + if (has_specified_type && specified_type.has_container_element_types()) { + update_dictionary_literal_element_type(dictionary, specified_type.get_container_element_type_or_variant(0), specified_type.get_container_element_type_or_variant(1)); + } } if (is_constant && !p_assignable->initializer->is_constant) { @@ -1987,7 +2009,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi } else { push_error(vformat(R"(Cannot assign a value of type %s to %s "%s" with specified type %s.)", initializer_type.to_string(), p_kind, p_assignable->identifier->name, specified_type.to_string()), p_assignable->initializer); } - } else if (specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) { + } else if ((specified_type.has_container_element_type(0) && !initializer_type.has_container_element_type(0)) || (specified_type.has_container_element_type(1) && !initializer_type.has_container_element_type(1))) { mark_node_unsafe(p_assignable->initializer); #ifdef DEBUG_ENABLED } else if (specified_type.builtin_type == Variant::INT && initializer_type.builtin_type == Variant::FLOAT) { @@ -2229,8 +2251,12 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) { } else if (!is_type_compatible(specified_type, variable_type)) { p_for->use_conversion_assign = true; } - if (p_for->list && p_for->list->type == GDScriptParser::Node::ARRAY) { - update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + if (p_for->list) { + if (p_for->list->type == GDScriptParser::Node::ARRAY) { + update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_for->list), specified_type); + } else if (p_for->list->type == GDScriptParser::Node::DICTIONARY) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_for->list), specified_type, GDScriptParser::DataType::get_variant_type()); + } } } p_for->variable->set_datatype(specified_type); @@ -2432,6 +2458,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) { } else { if (p_return->return_value->type == GDScriptParser::Node::ARRAY && has_expected_type && expected_type.has_container_element_type(0)) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_return->return_value), expected_type.get_container_element_type(0)); + } else if (p_return->return_value->type == GDScriptParser::Node::DICTIONARY && has_expected_type && expected_type.has_container_element_types()) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_return->return_value), + expected_type.get_container_element_type_or_variant(0), expected_type.get_container_element_type_or_variant(1)); } if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) { update_const_expression_builtin_type(p_return->return_value, expected_type, "return"); @@ -2678,6 +2707,54 @@ void GDScriptAnalyzer::update_array_literal_element_type(GDScriptParser::ArrayNo p_array->set_datatype(array_type); } +// When a dictionary literal is stored (or passed as function argument) to a typed context, we then assume the dictionary is typed. +// This function determines which type is that (if any). +void GDScriptAnalyzer::update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type) { + GDScriptParser::DataType expected_key_type = p_key_element_type; + GDScriptParser::DataType expected_value_type = p_value_element_type; + expected_key_type.container_element_types.clear(); // Nested types (like `Dictionary[String, Array[int]]`) are not currently supported. + expected_value_type.container_element_types.clear(); + + for (int i = 0; i < p_dictionary->elements.size(); i++) { + GDScriptParser::ExpressionNode *key_element_node = p_dictionary->elements[i].key; + if (key_element_node->is_constant) { + update_const_expression_builtin_type(key_element_node, expected_key_type, "include"); + } + const GDScriptParser::DataType &actual_key_type = key_element_node->get_datatype(); + if (actual_key_type.has_no_type() || actual_key_type.is_variant() || !actual_key_type.is_hard_type()) { + mark_node_unsafe(key_element_node); + } else if (!is_type_compatible(expected_key_type, actual_key_type, true, p_dictionary)) { + if (is_type_compatible(actual_key_type, expected_key_type)) { + mark_node_unsafe(key_element_node); + } else { + push_error(vformat(R"(Cannot have a key of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_key_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), key_element_node); + return; + } + } + + GDScriptParser::ExpressionNode *value_element_node = p_dictionary->elements[i].value; + if (value_element_node->is_constant) { + update_const_expression_builtin_type(value_element_node, expected_value_type, "include"); + } + const GDScriptParser::DataType &actual_value_type = value_element_node->get_datatype(); + if (actual_value_type.has_no_type() || actual_value_type.is_variant() || !actual_value_type.is_hard_type()) { + mark_node_unsafe(value_element_node); + } else if (!is_type_compatible(expected_value_type, actual_value_type, true, p_dictionary)) { + if (is_type_compatible(actual_value_type, expected_value_type)) { + mark_node_unsafe(value_element_node); + } else { + push_error(vformat(R"(Cannot have a value of type "%s" in a dictionary of type "Dictionary[%s, %s]".)", actual_value_type.to_string(), expected_key_type.to_string(), expected_value_type.to_string()), value_element_node); + return; + } + } + } + + GDScriptParser::DataType dictionary_type = p_dictionary->get_datatype(); + dictionary_type.set_container_element_type(0, expected_key_type); + dictionary_type.set_container_element_type(1, expected_value_type); + p_dictionary->set_datatype(dictionary_type); +} + void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assignment) { reduce_expression(p_assignment->assigned_value); @@ -2770,9 +2847,12 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig } } - // Check if assigned value is an array literal, so we can make it a typed array too if appropriate. + // Check if assigned value is an array/dictionary literal, so we can make it a typed container too if appropriate. if (p_assignment->assigned_value->type == GDScriptParser::Node::ARRAY && assignee_type.is_hard_type() && assignee_type.has_container_element_type(0)) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value), assignee_type.get_container_element_type(0)); + } else if (p_assignment->assigned_value->type == GDScriptParser::Node::DICTIONARY && assignee_type.is_hard_type() && assignee_type.has_container_element_types()) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_assignment->assigned_value), + assignee_type.get_container_element_type_or_variant(0), assignee_type.get_container_element_type_or_variant(1)); } if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) { @@ -2850,8 +2930,8 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig // weak non-variant assignee and incompatible result downgrades_assignee = true; } - } else if (assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) { - // typed array assignee and untyped array result + } else if ((assignee_type.has_container_element_type(0) && !op_type.has_container_element_type(0)) || (assignee_type.has_container_element_type(1) && !op_type.has_container_element_type(1))) { + // Typed assignee and untyped result. mark_node_unsafe(p_assignment); } } @@ -3049,10 +3129,13 @@ const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) { bool all_is_constant = true; HashMap<int, GDScriptParser::ArrayNode *> arrays; // For array literal to potentially type when passing. + HashMap<int, GDScriptParser::DictionaryNode *> dictionaries; // Same, but for dictionaries. for (int i = 0; i < p_call->arguments.size(); i++) { reduce_expression(p_call->arguments[i]); if (p_call->arguments[i]->type == GDScriptParser::Node::ARRAY) { arrays[i] = static_cast<GDScriptParser::ArrayNode *>(p_call->arguments[i]); + } else if (p_call->arguments[i]->type == GDScriptParser::Node::DICTIONARY) { + dictionaries[i] = static_cast<GDScriptParser::DictionaryNode *>(p_call->arguments[i]); } all_is_constant = all_is_constant && p_call->arguments[i]->is_constant; } @@ -3245,6 +3328,26 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a push_error(vformat(R"(No constructor of "%s" matches the signature "%s".)", Variant::get_type_name(builtin_type), signature), p_call); } } + +#ifdef DEBUG_ENABLED + // Consider `Signal(self, "my_signal")` as an implicit use of the signal. + if (builtin_type == Variant::SIGNAL && p_call->arguments.size() >= 2) { + const GDScriptParser::ExpressionNode *object_arg = p_call->arguments[0]; + if (object_arg && object_arg->type == GDScriptParser::Node::SELF) { + const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[1]; + if (signal_arg && signal_arg->is_constant) { + const StringName &signal_name = signal_arg->reduced_value; + if (parser->current_class->has_member(signal_name)) { + const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name); + if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + member.signal->usages++; + } + } + } + } + } +#endif + p_call->set_datatype(call_type); return; } else if (GDScriptUtilityFunctions::function_exists(function_name)) { @@ -3437,6 +3540,14 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a update_array_literal_element_type(E.value, par_types.get(index).get_container_element_type(0)); } } + for (const KeyValue<int, GDScriptParser::DictionaryNode *> &E : dictionaries) { + int index = E.key; + if (index < par_types.size() && par_types.get(index).is_hard_type() && par_types.get(index).has_container_element_types()) { + GDScriptParser::DataType key = par_types.get(index).get_container_element_type_or_variant(0); + GDScriptParser::DataType value = par_types.get(index).get_container_element_type_or_variant(1); + update_dictionary_literal_element_type(E.value, key, value); + } + } validate_call_arg(par_types, default_arg_count, method_flags.has_flag(METHOD_FLAG_VARARG), p_call); if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) { @@ -3479,6 +3590,20 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a parser->push_warning(p_call, GDScriptWarning::STATIC_CALLED_ON_INSTANCE, p_call->function_name, caller_type); } + + // Consider `emit_signal()`, `connect()`, and `disconnect()` as implicit uses of the signal. + if (is_self && (p_call->function_name == SNAME("emit_signal") || p_call->function_name == SNAME("connect") || p_call->function_name == SNAME("disconnect")) && !p_call->arguments.is_empty()) { + const GDScriptParser::ExpressionNode *signal_arg = p_call->arguments[0]; + if (signal_arg && signal_arg->is_constant) { + const StringName &signal_name = signal_arg->reduced_value; + if (parser->current_class->has_member(signal_name)) { + const GDScriptParser::ClassNode::Member &member = parser->current_class->get_member(signal_name); + if (member.type == GDScriptParser::ClassNode::Member::SIGNAL) { + member.signal->usages++; + } + } + } + } #endif // DEBUG_ENABLED call_type = return_type; @@ -3567,6 +3692,11 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) { update_array_literal_element_type(static_cast<GDScriptParser::ArrayNode *>(p_cast->operand), cast_type.get_container_element_type(0)); } + if (p_cast->operand->type == GDScriptParser::Node::DICTIONARY && cast_type.has_container_element_types()) { + update_dictionary_literal_element_type(static_cast<GDScriptParser::DictionaryNode *>(p_cast->operand), + cast_type.get_container_element_type_or_variant(0), cast_type.get_container_element_type_or_variant(1)); + } + if (!cast_type.is_variant()) { GDScriptParser::DataType op_type = p_cast->operand->get_datatype(); if (op_type.is_variant() || !op_type.is_hard_type()) { @@ -4591,10 +4721,23 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri reduce_identifier_from_base(p_subscript->attribute, &base_type); GDScriptParser::DataType attr_type = p_subscript->attribute->get_datatype(); if (attr_type.is_set()) { - valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type; - result_type = attr_type; - p_subscript->is_constant = p_subscript->attribute->is_constant; - p_subscript->reduced_value = p_subscript->attribute->reduced_value; + if (base_type.builtin_type == Variant::DICTIONARY && base_type.has_container_element_types()) { + Variant::Type key_type = base_type.get_container_element_type_or_variant(0).builtin_type; + valid = key_type == Variant::NIL || key_type == Variant::STRING || key_type == Variant::STRING_NAME; + if (base_type.has_container_element_type(1)) { + result_type = base_type.get_container_element_type(1); + result_type.type_source = base_type.type_source; + } else { + result_type.builtin_type = Variant::NIL; + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + } + } else { + valid = !attr_type.is_pseudo_type || p_can_be_pseudo_type; + result_type = attr_type; + p_subscript->is_constant = p_subscript->attribute->is_constant; + p_subscript->reduced_value = p_subscript->attribute->reduced_value; + } } else if (!base_type.is_meta_type || !base_type.is_constant) { valid = base_type.kind != GDScriptParser::DataType::BUILTIN; #ifdef DEBUG_ENABLED @@ -4701,8 +4844,40 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::SIGNAL: case Variant::STRING_NAME: break; - // Here for completeness. + // Support depends on if the dictionary has a typed key, otherwise anything is valid. case Variant::DICTIONARY: + if (base_type.has_container_element_type(0)) { + GDScriptParser::DataType key_type = base_type.get_container_element_type(0); + switch (index_type.builtin_type) { + // Null value will be treated as an empty object, allow. + case Variant::NIL: + error = key_type.builtin_type != Variant::OBJECT; + break; + // Objects are parsed for validity in a similar manner to container types. + case Variant::OBJECT: + if (key_type.builtin_type == Variant::OBJECT) { + error = !key_type.can_reference(index_type); + } else { + error = key_type.builtin_type != Variant::NIL; + } + break; + // String and StringName interchangeable in this context. + case Variant::STRING: + case Variant::STRING_NAME: + error = key_type.builtin_type != Variant::STRING_NAME && key_type.builtin_type != Variant::STRING; + break; + // Ints are valid indices for floats, but not the other way around. + case Variant::INT: + error = key_type.builtin_type != Variant::INT && key_type.builtin_type != Variant::FLOAT; + break; + // All other cases require the types to match exactly. + default: + error = key_type.builtin_type != index_type.builtin_type; + break; + } + } + break; + // Here for completeness. case Variant::VARIANT_MAX: break; } @@ -4791,7 +4966,6 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri case Variant::PROJECTION: case Variant::PLANE: case Variant::COLOR: - case Variant::DICTIONARY: case Variant::OBJECT: result_type.kind = GDScriptParser::DataType::VARIANT; result_type.type_source = GDScriptParser::DataType::UNDETECTED; @@ -4806,6 +4980,16 @@ void GDScriptAnalyzer::reduce_subscript(GDScriptParser::SubscriptNode *p_subscri result_type.type_source = GDScriptParser::DataType::UNDETECTED; } break; + // Can have two element types, but we only care about the value. + case Variant::DICTIONARY: + if (base_type.has_container_element_type(1)) { + result_type = base_type.get_container_element_type(1); + result_type.type_source = base_type.type_source; + } else { + result_type.kind = GDScriptParser::DataType::VARIANT; + result_type.type_source = GDScriptParser::DataType::UNDETECTED; + } + break; // Here for completeness. case Variant::VARIANT_MAX: break; @@ -4985,7 +5169,9 @@ Variant GDScriptAnalyzer::make_array_reduced_value(GDScriptParser::ArrayNode *p_ } Variant GDScriptAnalyzer::make_dictionary_reduced_value(GDScriptParser::DictionaryNode *p_dictionary, bool &is_reduced) { - Dictionary dictionary; + Dictionary dictionary = p_dictionary->get_datatype().has_container_element_types() + ? make_dictionary_from_element_datatype(p_dictionary->get_datatype().get_container_element_type_or_variant(0), p_dictionary->get_datatype().get_container_element_type_or_variant(1)) + : Dictionary(); for (int i = 0; i < p_dictionary->elements.size(); i++) { const GDScriptParser::DictionaryNode::Pair &element = p_dictionary->elements[i]; @@ -5072,6 +5258,49 @@ Array GDScriptAnalyzer::make_array_from_element_datatype(const GDScriptParser::D return array; } +Dictionary GDScriptAnalyzer::make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node) { + Dictionary dictionary; + StringName key_name; + Variant key_script; + StringName value_name; + Variant value_script; + + if (p_key_element_datatype.builtin_type == Variant::OBJECT) { + Ref<Script> script_type = p_key_element_datatype.script_type; + if (p_key_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { + Error err = OK; + Ref<GDScript> scr = get_depended_shallow_script(p_key_element_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_key_element_datatype.script_path), p_source_node); + return dictionary; + } + script_type.reference_ptr(scr->find_class(p_key_element_datatype.class_type->fqcn)); + } + + key_name = p_key_element_datatype.native_type; + key_script = script_type; + } + + if (p_value_element_datatype.builtin_type == Variant::OBJECT) { + Ref<Script> script_type = p_value_element_datatype.script_type; + if (p_value_element_datatype.kind == GDScriptParser::DataType::CLASS && script_type.is_null()) { + Error err = OK; + Ref<GDScript> scr = get_depended_shallow_script(p_value_element_datatype.script_path, err); + if (err) { + push_error(vformat(R"(Error while getting cache for script "%s".)", p_value_element_datatype.script_path), p_source_node); + return dictionary; + } + script_type.reference_ptr(scr->find_class(p_value_element_datatype.class_type->fqcn)); + } + + value_name = p_value_element_datatype.native_type; + value_script = script_type; + } + + dictionary.set_typed(p_key_element_datatype.builtin_type, key_name, key_script, p_value_element_datatype.builtin_type, value_name, value_script); + return dictionary; +} + Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNode *p_variable) { Variant result = Variant(); @@ -5087,6 +5316,10 @@ Variant GDScriptAnalyzer::make_variable_default_value(GDScriptParser::VariableNo if (datatype.kind == GDScriptParser::DataType::BUILTIN && datatype.builtin_type != Variant::OBJECT) { if (datatype.builtin_type == Variant::ARRAY && datatype.has_container_element_type(0)) { result = make_array_from_element_datatype(datatype.get_container_element_type(0)); + } else if (datatype.builtin_type == Variant::DICTIONARY && datatype.has_container_element_types()) { + GDScriptParser::DataType key = datatype.get_container_element_type_or_variant(0); + GDScriptParser::DataType value = datatype.get_container_element_type_or_variant(1); + result = make_dictionary_from_element_datatype(key, value); } else { VariantInternal::initialize(&result, datatype.builtin_type); } @@ -5115,6 +5348,22 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_variant(const Variant &p_va } else if (array.get_typed_builtin() != Variant::NIL) { result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)array.get_typed_builtin()))); } + } else if (p_value.get_type() == Variant::DICTIONARY) { + const Dictionary &dict = p_value; + if (dict.get_typed_key_script()) { + result.set_container_element_type(0, type_from_metatype(make_script_meta_type(dict.get_typed_key_script()))); + } else if (dict.get_typed_key_class_name()) { + result.set_container_element_type(0, type_from_metatype(make_native_meta_type(dict.get_typed_key_class_name()))); + } else if (dict.get_typed_key_builtin() != Variant::NIL) { + result.set_container_element_type(0, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_key_builtin()))); + } + if (dict.get_typed_value_script()) { + result.set_container_element_type(1, type_from_metatype(make_script_meta_type(dict.get_typed_value_script()))); + } else if (dict.get_typed_value_class_name()) { + result.set_container_element_type(1, type_from_metatype(make_native_meta_type(dict.get_typed_value_class_name()))); + } else if (dict.get_typed_value_builtin() != Variant::NIL) { + result.set_container_element_type(1, type_from_metatype(make_builtin_meta_type((Variant::Type)dict.get_typed_value_builtin()))); + } } else if (p_value.get_type() == Variant::OBJECT) { // Object is treated as a native type, not a builtin type. result.kind = GDScriptParser::DataType::NATIVE; @@ -5247,6 +5496,60 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_property(const PropertyInfo } elem_type.is_constant = false; result.set_container_element_type(0, elem_type); + } else if (p_property.type == Variant::DICTIONARY && p_property.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + // Check element type. + StringName key_elem_type_name = p_property.hint_string.get_slice(";", 0); + GDScriptParser::DataType key_elem_type; + key_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + Variant::Type key_elem_builtin_type = GDScriptParser::get_builtin_type(key_elem_type_name); + if (key_elem_builtin_type < Variant::VARIANT_MAX) { + // Builtin type. + key_elem_type.kind = GDScriptParser::DataType::BUILTIN; + key_elem_type.builtin_type = key_elem_builtin_type; + } else if (class_exists(key_elem_type_name)) { + key_elem_type.kind = GDScriptParser::DataType::NATIVE; + key_elem_type.builtin_type = Variant::OBJECT; + key_elem_type.native_type = key_elem_type_name; + } else if (ScriptServer::is_global_class(key_elem_type_name)) { + // Just load this as it shouldn't be a GDScript. + Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(key_elem_type_name)); + key_elem_type.kind = GDScriptParser::DataType::SCRIPT; + key_elem_type.builtin_type = Variant::OBJECT; + key_elem_type.native_type = script->get_instance_base_type(); + key_elem_type.script_type = script; + } else { + ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary."); + } + key_elem_type.is_constant = false; + + StringName value_elem_type_name = p_property.hint_string.get_slice(";", 1); + GDScriptParser::DataType value_elem_type; + value_elem_type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT; + + Variant::Type value_elem_builtin_type = GDScriptParser::get_builtin_type(value_elem_type_name); + if (value_elem_builtin_type < Variant::VARIANT_MAX) { + // Builtin type. + value_elem_type.kind = GDScriptParser::DataType::BUILTIN; + value_elem_type.builtin_type = value_elem_builtin_type; + } else if (class_exists(value_elem_type_name)) { + value_elem_type.kind = GDScriptParser::DataType::NATIVE; + value_elem_type.builtin_type = Variant::OBJECT; + value_elem_type.native_type = value_elem_type_name; + } else if (ScriptServer::is_global_class(value_elem_type_name)) { + // Just load this as it shouldn't be a GDScript. + Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(value_elem_type_name)); + value_elem_type.kind = GDScriptParser::DataType::SCRIPT; + value_elem_type.builtin_type = Variant::OBJECT; + value_elem_type.native_type = script->get_instance_base_type(); + value_elem_type.script_type = script; + } else { + ERR_FAIL_V_MSG(result, "Could not find element type from property hint of a typed dictionary."); + } + value_elem_type.is_constant = false; + + result.set_container_element_type(0, key_elem_type); + result.set_container_element_type(1, value_elem_type); } else if (p_property.type == Variant::INT) { // Check if it's enum. if ((p_property.usage & PROPERTY_USAGE_CLASS_IS_ENUM) && p_property.class_name != StringName()) { @@ -5667,6 +5970,15 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType & valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0); } } + if (valid && p_target.builtin_type == Variant::DICTIONARY && p_source.builtin_type == Variant::DICTIONARY) { + // Check the element types. + if (p_target.has_container_element_type(0) && p_source.has_container_element_type(0)) { + valid = p_target.get_container_element_type(0) == p_source.get_container_element_type(0); + } + if (valid && p_target.has_container_element_type(1) && p_source.has_container_element_type(1)) { + valid = p_target.get_container_element_type(1) == p_source.get_container_element_type(1); + } + } return valid; } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index 25e5aa9a2c..3b781409a4 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -125,6 +125,7 @@ class GDScriptAnalyzer { // Helpers. Array make_array_from_element_datatype(const GDScriptParser::DataType &p_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); + Dictionary make_dictionary_from_element_datatype(const GDScriptParser::DataType &p_key_element_datatype, const GDScriptParser::DataType &p_value_element_datatype, const GDScriptParser::Node *p_source_node = nullptr); GDScriptParser::DataType type_from_variant(const Variant &p_value, const GDScriptParser::Node *p_source); static GDScriptParser::DataType type_from_metatype(const GDScriptParser::DataType &p_meta_type); GDScriptParser::DataType type_from_property(const PropertyInfo &p_property, bool p_is_arg = false, bool p_is_readonly = false) const; @@ -137,6 +138,7 @@ class GDScriptAnalyzer { GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source); void update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false); void update_array_literal_element_type(GDScriptParser::ArrayNode *p_array, const GDScriptParser::DataType &p_element_type); + void update_dictionary_literal_element_type(GDScriptParser::DictionaryNode *p_dictionary, const GDScriptParser::DataType &p_key_element_type, const GDScriptParser::DataType &p_value_element_type); bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr); void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr); void mark_node_unsafe(const GDScriptParser::Node *p_node); diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 4cda3d3037..b77c641eb5 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -634,6 +634,18 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (p_type.builtin_type == Variant::DICTIONARY && p_type.has_container_element_types()) { + const GDScriptDataType &key_element_type = p_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_element_type = p_type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_DICTIONARY); + append(p_target); + append(p_source); + append(get_constant_pos(key_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_element_type.builtin_type); + append(key_element_type.native_type); + append(value_element_type.builtin_type); + append(value_element_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_TYPE_TEST_BUILTIN); append(p_target); @@ -889,6 +901,18 @@ void GDScriptByteCodeGenerator::write_assign_with_conversion(const Address &p_ta append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) { + const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY); + append(p_target); + append(p_source); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); append(p_target); @@ -935,6 +959,18 @@ void GDScriptByteCodeGenerator::write_assign(const Address &p_target, const Addr append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type == Variant::DICTIONARY && p_target.type.has_container_element_types()) { + const GDScriptDataType &key_type = p_target.type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = p_target.type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_DICTIONARY); + append(p_target); + append(p_source); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else if (p_target.type.kind == GDScriptDataType::BUILTIN && p_source.type.kind == GDScriptDataType::BUILTIN && p_target.type.builtin_type != p_source.type.builtin_type) { // Need conversion. append_opcode(GDScriptFunction::OPCODE_ASSIGN_TYPED_BUILTIN); @@ -1434,6 +1470,23 @@ void GDScriptByteCodeGenerator::write_construct_dictionary(const Address &p_targ ct.cleanup(); } +void GDScriptByteCodeGenerator::write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) { + append_opcode_and_argcount(GDScriptFunction::OPCODE_CONSTRUCT_TYPED_DICTIONARY, 3 + p_arguments.size()); + for (int i = 0; i < p_arguments.size(); i++) { + append(p_arguments[i]); + } + CallTarget ct = get_call_target(p_target); + append(ct.target); + append(get_constant_pos(p_key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(p_value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(p_arguments.size() / 2); // This is number of key-value pairs, so only half of actual arguments. + append(p_key_type.builtin_type); + append(p_key_type.native_type); + append(p_value_type.builtin_type); + append(p_value_type.native_type); + ct.cleanup(); +} + void GDScriptByteCodeGenerator::write_await(const Address &p_target, const Address &p_operand) { append_opcode(GDScriptFunction::OPCODE_AWAIT); append(p_operand); @@ -1711,6 +1764,19 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (function->return_type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type == Variant::DICTIONARY && + function->return_type.has_container_element_types()) { + // Typed dictionary. + const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY); + append(p_return_value); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else if (function->return_type.kind == GDScriptDataType::BUILTIN && p_return_value.type.kind == GDScriptDataType::BUILTIN && function->return_type.builtin_type != p_return_value.type.builtin_type) { // Add conversion. append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); @@ -1735,6 +1801,17 @@ void GDScriptByteCodeGenerator::write_return(const Address &p_return_value) { append(get_constant_pos(element_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); append(element_type.builtin_type); append(element_type.native_type); + } else if (function->return_type.builtin_type == Variant::DICTIONARY && function->return_type.has_container_element_types()) { + const GDScriptDataType &key_type = function->return_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = function->return_type.get_container_element_type_or_variant(1); + append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_DICTIONARY); + append(p_return_value); + append(get_constant_pos(key_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(get_constant_pos(value_type.script_type) | (GDScriptFunction::ADDR_TYPE_CONSTANT << GDScriptFunction::ADDR_BITS)); + append(key_type.builtin_type); + append(key_type.native_type); + append(value_type.builtin_type); + append(value_type.native_type); } else { append_opcode(GDScriptFunction::OPCODE_RETURN_TYPED_BUILTIN); append(p_return_value); @@ -1803,6 +1880,13 @@ void GDScriptByteCodeGenerator::clear_address(const Address &p_address) { case Variant::BOOL: write_assign_false(p_address); break; + case Variant::DICTIONARY: + if (p_address.type.has_container_element_types()) { + write_construct_typed_dictionary(p_address, p_address.type.get_container_element_type_or_variant(0), p_address.type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>()); + } else { + write_construct(p_address, p_address.type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); + } + break; case Variant::ARRAY: if (p_address.type.has_container_element_type(0)) { write_construct_typed_array(p_address, p_address.type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>()); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index 34f56a2f5c..6303db71fd 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -529,6 +529,7 @@ public: virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) override; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) override; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) override; + virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) override; virtual void write_await(const Address &p_target, const Address &p_operand) override; virtual void write_if(const Address &p_condition) override; virtual void write_else() override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index c1c0b61395..f3c4acf1c3 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -142,6 +142,7 @@ public: virtual void write_construct_array(const Address &p_target, const Vector<Address> &p_arguments) = 0; virtual void write_construct_typed_array(const Address &p_target, const GDScriptDataType &p_element_type, const Vector<Address> &p_arguments) = 0; virtual void write_construct_dictionary(const Address &p_target, const Vector<Address> &p_arguments) = 0; + virtual void write_construct_typed_dictionary(const Address &p_target, const GDScriptDataType &p_key_type, const GDScriptDataType &p_value_type, const Vector<Address> &p_arguments) = 0; virtual void write_await(const Address &p_target, const Address &p_operand) = 0; virtual void write_if(const Address &p_condition) = 0; virtual void write_else() = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index d8b44a558f..eebf282633 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -532,10 +532,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code Vector<GDScriptCodeGenerator::Address> elements; // Create the result temporary first since it's the last to be killed. - GDScriptDataType dict_type; - dict_type.has_type = true; - dict_type.kind = GDScriptDataType::BUILTIN; - dict_type.builtin_type = Variant::DICTIONARY; + GDScriptDataType dict_type = _gdtype_from_datatype(dn->get_datatype(), codegen.script); GDScriptCodeGenerator::Address result = codegen.add_temporary(dict_type); for (int i = 0; i < dn->elements.size(); i++) { @@ -566,7 +563,11 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code elements.push_back(element); } - gen->write_construct_dictionary(result, elements); + if (dict_type.has_container_element_types()) { + gen->write_construct_typed_dictionary(result, dict_type.get_container_element_type_or_variant(0), dict_type.get_container_element_type_or_variant(1), elements); + } else { + gen->write_construct_dictionary(result, elements); + } for (int i = 0; i < elements.size(); i++) { if (elements[i].mode == GDScriptCodeGenerator::Address::TEMPORARY) { @@ -2325,8 +2326,11 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ GDScriptCodeGenerator::Address dst_address(GDScriptCodeGenerator::Address::MEMBER, codegen.script->member_indices[field->identifier->name].index, field_type); - if (field_type.has_container_element_type(0)) { + if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) { codegen.generator->write_construct_typed_array(dst_address, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>()); + } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) { + codegen.generator->write_construct_typed_dictionary(dst_address, field_type.get_container_element_type_or_variant(0), + field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>()); } else if (field_type.kind == GDScriptDataType::BUILTIN) { codegen.generator->write_construct(dst_address, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); } @@ -2515,11 +2519,17 @@ GDScriptFunction *GDScriptCompiler::_make_static_initializer(Error &r_error, GDS if (field_type.has_type) { codegen.generator->write_newline(field->start_line); - if (field_type.has_container_element_type(0)) { + if (field_type.builtin_type == Variant::ARRAY && field_type.has_container_element_type(0)) { GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); codegen.generator->write_construct_typed_array(temp, field_type.get_container_element_type(0), Vector<GDScriptCodeGenerator::Address>()); codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index); codegen.generator->pop_temporary(); + } else if (field_type.builtin_type == Variant::DICTIONARY && field_type.has_container_element_types()) { + GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); + codegen.generator->write_construct_typed_dictionary(temp, field_type.get_container_element_type_or_variant(0), + field_type.get_container_element_type_or_variant(1), Vector<GDScriptCodeGenerator::Address>()); + codegen.generator->write_set_static_variable(temp, class_addr, p_script->static_variables_indices[field->identifier->name].index); + codegen.generator->pop_temporary(); } else if (field_type.kind == GDScriptDataType::BUILTIN) { GDScriptCodeGenerator::Address temp = codegen.add_temporary(field_type); codegen.generator->write_construct(temp, field_type.builtin_type, Vector<GDScriptCodeGenerator::Address>()); diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 0331045078..bc063693a3 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -176,6 +176,47 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 6; } break; + case OPCODE_TYPE_TEST_DICTIONARY: { + text += "type test "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + text += " is Dictionary["; + + Ref<Script> key_script_type = get_constant(_code_ptr[ip + 3] & ADDR_MASK); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5]; + StringName key_native_type = get_global_name(_code_ptr[ip + 6]); + + if (key_script_type.is_valid() && key_script_type->is_valid()) { + text += "script("; + text += GDScript::debug_get_script_name(key_script_type); + text += ")"; + } else if (key_native_type != StringName()) { + text += key_native_type; + } else { + text += Variant::get_type_name(key_builtin_type); + } + + text += ", "; + + Ref<Script> value_script_type = get_constant(_code_ptr[ip + 4] & ADDR_MASK); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7]; + StringName value_native_type = get_global_name(_code_ptr[ip + 8]); + + if (value_script_type.is_valid() && value_script_type->is_valid()) { + text += "script("; + text += GDScript::debug_get_script_name(value_script_type); + text += ")"; + } else if (value_native_type != StringName()) { + text += value_native_type; + } else { + text += Variant::get_type_name(value_builtin_type); + } + + text += "]"; + + incr += 9; + } break; case OPCODE_TYPE_TEST_NATIVE: { text += "type test "; text += DADDR(1); @@ -399,6 +440,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 6; } break; + case OPCODE_ASSIGN_TYPED_DICTIONARY: { + text += "assign typed dictionary "; + text += DADDR(1); + text += " = "; + text += DADDR(2); + + incr += 9; + } break; case OPCODE_ASSIGN_TYPED_NATIVE: { text += "assign typed native ("; text += DADDR(3); @@ -564,6 +613,58 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 3 + argc * 2; } break; + case OPCODE_CONSTRUCT_TYPED_DICTIONARY: { + int instr_var_args = _code_ptr[++ip]; + int argc = _code_ptr[ip + 1 + instr_var_args]; + + Ref<Script> key_script_type = get_constant(_code_ptr[ip + argc * 2 + 2] & ADDR_MASK); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 5]; + StringName key_native_type = get_global_name(_code_ptr[ip + argc * 2 + 6]); + + String key_type_name; + if (key_script_type.is_valid() && key_script_type->is_valid()) { + key_type_name = "script(" + GDScript::debug_get_script_name(key_script_type) + ")"; + } else if (key_native_type != StringName()) { + key_type_name = key_native_type; + } else { + key_type_name = Variant::get_type_name(key_builtin_type); + } + + Ref<Script> value_script_type = get_constant(_code_ptr[ip + argc * 2 + 3] & ADDR_MASK); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + argc * 2 + 7]; + StringName value_native_type = get_global_name(_code_ptr[ip + argc * 2 + 8]); + + String value_type_name; + if (value_script_type.is_valid() && value_script_type->is_valid()) { + value_type_name = "script(" + GDScript::debug_get_script_name(value_script_type) + ")"; + } else if (value_native_type != StringName()) { + value_type_name = value_native_type; + } else { + value_type_name = Variant::get_type_name(value_builtin_type); + } + + text += "make_typed_dict ("; + text += key_type_name; + text += ", "; + text += value_type_name; + text += ") "; + + text += DADDR(1 + argc * 2); + text += " = {"; + + for (int i = 0; i < argc; i++) { + if (i > 0) { + text += ", "; + } + text += DADDR(1 + i * 2 + 0); + text += ": "; + text += DADDR(1 + i * 2 + 1); + } + + text += "}"; + + incr += 9 + argc * 2; + } break; case OPCODE_CALL: case OPCODE_CALL_RETURN: case OPCODE_CALL_ASYNC: { @@ -978,6 +1079,12 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; + case OPCODE_RETURN_TYPED_DICTIONARY: { + text += "return typed dictionary "; + text += DADDR(1); + + incr += 8; + } break; case OPCODE_RETURN_TYPED_NATIVE: { text += "return typed native ("; text += DADDR(2); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 636339ef1d..524f528f76 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -697,6 +697,10 @@ static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg, co return _trim_parent_class(class_name, p_base_class); } else if (p_info.type == Variant::ARRAY && p_info.hint == PROPERTY_HINT_ARRAY_TYPE && !p_info.hint_string.is_empty()) { return "Array[" + _trim_parent_class(p_info.hint_string, p_base_class) + "]"; + } else if (p_info.type == Variant::DICTIONARY && p_info.hint == PROPERTY_HINT_DICTIONARY_TYPE && !p_info.hint_string.is_empty()) { + const String key = p_info.hint_string.get_slice(";", 0); + const String value = p_info.hint_string.get_slice(";", 1); + return "Dictionary[" + _trim_parent_class(key, p_base_class) + ", " + _trim_parent_class(value, p_base_class) + "]"; } else if (p_info.type == Variant::NIL) { if (p_is_arg || (p_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT)) { return "Variant"; @@ -1997,7 +2001,7 @@ static bool _guess_expression_type(GDScriptParser::CompletionContext &p_context, } // Look for valid indexing in other types - if (!found && (index.value.get_type() == Variant::STRING || index.value.get_type() == Variant::NODE_PATH)) { + if (!found && (index.value.is_string() || index.value.get_type() == Variant::NODE_PATH)) { StringName id = index.value; found = _guess_identifier_type_from_base(c, base, id, r_type); } else if (!found && index.type.kind == GDScriptParser::DataType::BUILTIN) { @@ -2112,7 +2116,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, // Look in blocks first. int last_assign_line = -1; const GDScriptParser::ExpressionNode *last_assigned_expression = nullptr; - GDScriptParser::DataType id_type; + GDScriptCompletionIdentifier id_type; GDScriptParser::SuiteNode *suite = p_context.current_suite; bool is_function_parameter = false; @@ -2134,7 +2138,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, if (can_be_local && suite && suite->has_local(p_identifier->name)) { const GDScriptParser::SuiteNode::Local &local = suite->get_local(p_identifier->name); - id_type = local.get_datatype(); + id_type.type = local.get_datatype(); // Check initializer as the first assignment. switch (local.type) { @@ -2172,7 +2176,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base.type.is_meta_type = p_context.current_function && p_context.current_function->is_static; if (_guess_identifier_type_from_base(p_context, base, p_identifier->name, base_identifier)) { - id_type = base_identifier.type; + id_type = base_identifier; } } } @@ -2212,7 +2216,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, c.current_line = type_test->operand->start_line; c.current_suite = suite; if (type_test->test_datatype.is_hard_type()) { - id_type = type_test->test_datatype; + id_type.type = type_test->test_datatype; if (last_assign_line < c.current_line) { // Override last assignment. last_assign_line = c.current_line; @@ -2230,10 +2234,10 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, c.current_line = last_assign_line; GDScriptCompletionIdentifier assigned_type; if (_guess_expression_type(c, last_assigned_expression, assigned_type)) { - if (id_type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type, assigned_type.type)) { + if (id_type.type.is_set() && assigned_type.type.is_set() && !GDScriptAnalyzer::check_type_compatibility(id_type.type, assigned_type.type)) { // The assigned type is incompatible. The annotated type takes priority. + r_type = id_type; r_type.assigned_expression = last_assigned_expression; - r_type.type = id_type; } else { r_type = assigned_type; } @@ -2251,8 +2255,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, GDScriptParser::FunctionNode *parent_function = base_type.class_type->get_member(p_context.current_function->identifier->name).function; if (parent_function->parameters_indices.has(p_identifier->name)) { const GDScriptParser::ParameterNode *parameter = parent_function->parameters[parent_function->parameters_indices[p_identifier->name]]; - if ((!id_type.is_set() || id_type.is_variant()) && parameter->get_datatype().is_hard_type()) { - id_type = parameter->get_datatype(); + if ((!id_type.type.is_set() || id_type.type.is_variant()) && parameter->get_datatype().is_hard_type()) { + id_type.type = parameter->get_datatype(); } if (parameter->initializer) { GDScriptParser::CompletionContext c = p_context; @@ -2268,7 +2272,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, base_type = base_type.class_type->base_type; break; case GDScriptParser::DataType::NATIVE: { - if (id_type.is_set() && !id_type.is_variant()) { + if (id_type.type.is_set() && !id_type.type.is_variant()) { base_type = GDScriptParser::DataType(); break; } @@ -2289,8 +2293,8 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (id_type.is_set() && !id_type.is_variant()) { - r_type.type = id_type; + if (id_type.type.is_set() && !id_type.type.is_variant()) { + r_type = id_type; return true; } diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index ac4bab6d84..6433072b55 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -93,6 +93,41 @@ public: } else { valid = false; } + } else if (valid && builtin_type == Variant::DICTIONARY && has_container_element_types()) { + Dictionary dictionary = p_variant; + if (dictionary.is_typed()) { + if (dictionary.is_typed_key()) { + GDScriptDataType key = get_container_element_type_or_variant(0); + Variant::Type key_builtin_type = (Variant::Type)dictionary.get_typed_key_builtin(); + StringName key_native_type = dictionary.get_typed_key_class_name(); + Ref<Script> key_script_type_ref = dictionary.get_typed_key_script(); + + if (key_script_type_ref.is_valid()) { + valid = (key.kind == SCRIPT || key.kind == GDSCRIPT) && key.script_type == key_script_type_ref.ptr(); + } else if (key_native_type != StringName()) { + valid = key.kind == NATIVE && key.native_type == key_native_type; + } else { + valid = key.kind == BUILTIN && key.builtin_type == key_builtin_type; + } + } + + if (valid && dictionary.is_typed_value()) { + GDScriptDataType value = get_container_element_type_or_variant(1); + Variant::Type value_builtin_type = (Variant::Type)dictionary.get_typed_value_builtin(); + StringName value_native_type = dictionary.get_typed_value_class_name(); + Ref<Script> value_script_type_ref = dictionary.get_typed_value_script(); + + if (value_script_type_ref.is_valid()) { + valid = (value.kind == SCRIPT || value.kind == GDSCRIPT) && value.script_type == value_script_type_ref.ptr(); + } else if (value_native_type != StringName()) { + valid = value.kind == NATIVE && value.native_type == value_native_type; + } else { + valid = value.kind == BUILTIN && value.builtin_type == value_builtin_type; + } + } + } else { + valid = false; + } } else if (!valid && p_allow_implicit_conversion) { valid = Variant::can_convert_strict(var_type, builtin_type); } @@ -156,6 +191,10 @@ public: } return true; case Variant::DICTIONARY: + if (has_container_element_types()) { + return get_container_element_type_or_variant(0).can_contain_object() || get_container_element_type_or_variant(1).can_contain_object(); + } + return true; case Variant::NIL: case Variant::OBJECT: return true; @@ -220,6 +259,7 @@ public: OPCODE_OPERATOR_VALIDATED, OPCODE_TYPE_TEST_BUILTIN, OPCODE_TYPE_TEST_ARRAY, + OPCODE_TYPE_TEST_DICTIONARY, OPCODE_TYPE_TEST_NATIVE, OPCODE_TYPE_TEST_SCRIPT, OPCODE_SET_KEYED, @@ -242,6 +282,7 @@ public: OPCODE_ASSIGN_FALSE, OPCODE_ASSIGN_TYPED_BUILTIN, OPCODE_ASSIGN_TYPED_ARRAY, + OPCODE_ASSIGN_TYPED_DICTIONARY, OPCODE_ASSIGN_TYPED_NATIVE, OPCODE_ASSIGN_TYPED_SCRIPT, OPCODE_CAST_TO_BUILTIN, @@ -252,6 +293,7 @@ public: OPCODE_CONSTRUCT_ARRAY, OPCODE_CONSTRUCT_TYPED_ARRAY, OPCODE_CONSTRUCT_DICTIONARY, + OPCODE_CONSTRUCT_TYPED_DICTIONARY, OPCODE_CALL, OPCODE_CALL_RETURN, OPCODE_CALL_ASYNC, @@ -280,6 +322,7 @@ public: OPCODE_RETURN, OPCODE_RETURN_TYPED_BUILTIN, OPCODE_RETURN_TYPED_ARRAY, + OPCODE_RETURN_TYPED_DICTIONARY, OPCODE_RETURN_TYPED_NATIVE, OPCODE_RETURN_TYPED_SCRIPT, OPCODE_ITERATE_BEGIN, diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 2162a727b3..2de5811bca 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -150,7 +150,7 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) : function(p_function) { - ERR_FAIL_NULL(p_script.ptr()); + ERR_FAIL_COND(p_script.is_null()); ERR_FAIL_NULL(p_function); script = p_script; captures = p_captures; @@ -282,7 +282,7 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) : function(p_function) { - ERR_FAIL_NULL(p_self.ptr()); + ERR_FAIL_COND(p_self.is_null()); ERR_FAIL_NULL(p_function); reference = p_self; object = p_self.ptr(); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 582305d900..65aa150be3 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -249,7 +249,7 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet if (!for_completion) { return; } - if (completion_context.node != p_for_node) { + if (p_for_node == nullptr || completion_context.node != p_for_node) { return; } CompletionContext context; @@ -264,8 +264,8 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet completion_context = context; } -void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument) { - if (!for_completion) { +void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node, int p_argument, bool p_force) { + if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { return; } if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { @@ -283,8 +283,8 @@ void GDScriptParser::make_completion_context(CompletionType p_type, Node *p_node completion_context = context; } -void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type) { - if (!for_completion) { +void GDScriptParser::make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force) { + if (!for_completion || (!p_force && completion_context.type != COMPLETION_NONE)) { return; } if (previous.cursor_place != GDScriptTokenizerText::CURSOR_MIDDLE && previous.cursor_place != GDScriptTokenizerText::CURSOR_END && current.cursor_place == GDScriptTokenizerText::CURSOR_NONE) { @@ -2471,7 +2471,7 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr } // Completion can appear whenever an expression is expected. - make_completion_context(COMPLETION_IDENTIFIER, nullptr); + make_completion_context(COMPLETION_IDENTIFIER, nullptr, -1, false); GDScriptTokenizer::Token token = current; GDScriptTokenizer::Token::Type token_type = token.type; @@ -2488,8 +2488,17 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr advance(); // Only consume the token if there's a valid rule. + // After a token was consumed, update the completion context regardless of a previously set context. + ExpressionNode *previous_operand = (this->*prefix_rule)(nullptr, p_can_assign); +#ifdef TOOLS_ENABLED + // HACK: We can't create a context in parse_identifier since it is used in places were we don't want completion. + if (previous_operand != nullptr && previous_operand->type == GDScriptParser::Node::IDENTIFIER && prefix_rule == static_cast<ParseFunction>(&GDScriptParser::parse_identifier)) { + make_completion_context(COMPLETION_IDENTIFIER, previous_operand); + } +#endif + while (p_precedence <= get_rule(current.type)->precedence) { if (previous_operand == nullptr || (p_stop_on_assign && current.type == GDScriptTokenizer::Token::EQUAL) || lambda_ended) { return previous_operand; @@ -2924,6 +2933,11 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } assignment->assignee = p_previous_operand; assignment->assigned_value = parse_expression(false); +#ifdef TOOLS_ENABLED + if (assignment->assigned_value != nullptr && assignment->assigned_value->type == GDScriptParser::Node::IDENTIFIER) { + override_completion_context(assignment->assigned_value, COMPLETION_ASSIGN, assignment); + } +#endif if (assignment->assigned_value == nullptr) { push_error(R"(Expected an expression after "=".)"); } @@ -3554,7 +3568,7 @@ GDScriptParser::TypeNode *GDScriptParser::parse_type(bool p_allow_void) { type->type_chain.push_back(type_element); if (match(GDScriptTokenizer::Token::BRACKET_OPEN)) { - // Typed collection (like Array[int]). + // Typed collection (like Array[int], Dictionary[String, int]). bool first_pass = true; do { TypeNode *container_type = parse_type(false); // Don't allow void for element type. @@ -4371,6 +4385,14 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta export_type.type_source = variable->datatype.type_source; } + bool is_dict = false; + if (export_type.builtin_type == Variant::DICTIONARY && export_type.has_container_element_types()) { + is_dict = true; + DataType inner_type = export_type.get_container_element_type_or_variant(1); + export_type = export_type.get_container_element_type_or_variant(0); + export_type.set_container_element_type(0, inner_type); // Store earlier extracted value within key to separately parse after. + } + bool use_default_variable_type_check = true; if (p_annotation->name == SNAME("@export_range")) { @@ -4398,8 +4420,13 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta } if (export_type.is_variant() || export_type.has_no_type()) { - push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); - return false; + if (is_dict) { + // Dictionary allowed to have a variant key/value. + export_type.kind = GDScriptParser::DataType::BUILTIN; + } else { + push_error(R"(Cannot use simple "@export" annotation because the type of the initialized value can't be inferred.)", p_annotation); + return false; + } } switch (export_type.kind) { @@ -4459,6 +4486,90 @@ bool GDScriptParser::export_annotations(AnnotationNode *p_annotation, Node *p_ta push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation); return false; } + + if (is_dict) { + String key_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + key_prefix += "/" + itos(variable->export_info.hint); + } + key_prefix += ":" + variable->export_info.hint_string; + + // Now parse value. + export_type = export_type.get_container_element_type(0); + + if (export_type.is_variant() || export_type.has_no_type()) { + export_type.kind = GDScriptParser::DataType::BUILTIN; + } + switch (export_type.kind) { + case GDScriptParser::DataType::BUILTIN: + variable->export_info.type = export_type.builtin_type; + variable->export_info.hint = PROPERTY_HINT_NONE; + variable->export_info.hint_string = String(); + break; + case GDScriptParser::DataType::NATIVE: + case GDScriptParser::DataType::SCRIPT: + case GDScriptParser::DataType::CLASS: { + const StringName class_name = _find_narrowest_native_or_global_class(export_type); + if (ClassDB::is_parent_class(export_type.native_type, SNAME("Resource"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_RESOURCE_TYPE; + variable->export_info.hint_string = class_name; + } else if (ClassDB::is_parent_class(export_type.native_type, SNAME("Node"))) { + variable->export_info.type = Variant::OBJECT; + variable->export_info.hint = PROPERTY_HINT_NODE_TYPE; + variable->export_info.hint_string = class_name; + } else { + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); + return false; + } + } break; + case GDScriptParser::DataType::ENUM: { + if (export_type.is_meta_type) { + variable->export_info.type = Variant::DICTIONARY; + } else { + variable->export_info.type = Variant::INT; + variable->export_info.hint = PROPERTY_HINT_ENUM; + + String enum_hint_string; + bool first = true; + for (const KeyValue<StringName, int64_t> &E : export_type.enum_values) { + if (!first) { + enum_hint_string += ","; + } else { + first = false; + } + enum_hint_string += E.key.operator String().capitalize().xml_escape(); + enum_hint_string += ":"; + enum_hint_string += String::num_int64(E.value).xml_escape(); + } + + variable->export_info.hint_string = enum_hint_string; + variable->export_info.usage |= PROPERTY_USAGE_CLASS_IS_ENUM; + variable->export_info.class_name = String(export_type.native_type).replace("::", "."); + } + } break; + default: + push_error(R"(Export type can only be built-in, a resource, a node, or an enum.)", p_annotation); + return false; + } + + if (variable->export_info.hint == PROPERTY_HINT_NODE_TYPE && !ClassDB::is_parent_class(p_class->base_type.native_type, SNAME("Node"))) { + push_error(vformat(R"(Node export is only supported in Node-derived classes, but the current class inherits "%s".)", p_class->base_type.to_string()), p_annotation); + return false; + } + + String value_prefix = itos(variable->export_info.type); + if (variable->export_info.hint) { + value_prefix += "/" + itos(variable->export_info.hint); + } + value_prefix += ":" + variable->export_info.hint_string; + + variable->export_info.type = Variant::DICTIONARY; + variable->export_info.hint = PROPERTY_HINT_TYPE_STRING; + variable->export_info.hint_string = key_prefix + ";" + value_prefix; + variable->export_info.usage = PROPERTY_USAGE_DEFAULT; + variable->export_info.class_name = StringName(); + } } else if (p_annotation->name == SNAME("@export_enum")) { use_default_variable_type_check = false; @@ -4780,7 +4891,10 @@ String GDScriptParser::DataType::to_string() const { return "null"; } if (builtin_type == Variant::ARRAY && has_container_element_type(0)) { - return vformat("Array[%s]", container_element_types[0].to_string()); + return vformat("Array[%s]", get_container_element_type(0).to_string()); + } + if (builtin_type == Variant::DICTIONARY && has_container_element_types()) { + return vformat("Dictionary[%s, %s]", get_container_element_type_or_variant(0).to_string(), get_container_element_type_or_variant(1).to_string()); } return Variant::get_type_name(builtin_type); case NATIVE: @@ -4795,9 +4909,9 @@ String GDScriptParser::DataType::to_string() const { return class_type->fqcn; case SCRIPT: { if (is_meta_type) { - return script_type != nullptr ? script_type->get_class_name().operator String() : ""; + return script_type.is_valid() ? script_type->get_class_name().operator String() : ""; } - String name = script_type != nullptr ? script_type->get_name() : ""; + String name = script_type.is_valid() ? script_type->get_name() : ""; if (!name.is_empty()) { return name; } @@ -4869,6 +4983,72 @@ PropertyInfo GDScriptParser::DataType::to_property_info(const String &p_name) co case UNRESOLVED: break; } + } else if (builtin_type == Variant::DICTIONARY && has_container_element_types()) { + const DataType key_type = get_container_element_type_or_variant(0); + const DataType value_type = get_container_element_type_or_variant(1); + if ((key_type.kind == VARIANT && value_type.kind == VARIANT) || key_type.kind == RESOLVING || + key_type.kind == UNRESOLVED || value_type.kind == RESOLVING || value_type.kind == UNRESOLVED) { + break; + } + String key_hint, value_hint; + switch (key_type.kind) { + case BUILTIN: + key_hint = Variant::get_type_name(key_type.builtin_type); + break; + case NATIVE: + key_hint = key_type.native_type; + break; + case SCRIPT: + if (key_type.script_type.is_valid() && key_type.script_type->get_global_name() != StringName()) { + key_hint = key_type.script_type->get_global_name(); + } else { + key_hint = key_type.native_type; + } + break; + case CLASS: + if (key_type.class_type != nullptr && key_type.class_type->get_global_name() != StringName()) { + key_hint = key_type.class_type->get_global_name(); + } else { + key_hint = key_type.native_type; + } + break; + case ENUM: + key_hint = String(key_type.native_type).replace("::", "."); + break; + default: + key_hint = "Variant"; + break; + } + switch (value_type.kind) { + case BUILTIN: + value_hint = Variant::get_type_name(value_type.builtin_type); + break; + case NATIVE: + value_hint = value_type.native_type; + break; + case SCRIPT: + if (value_type.script_type.is_valid() && value_type.script_type->get_global_name() != StringName()) { + value_hint = value_type.script_type->get_global_name(); + } else { + value_hint = value_type.native_type; + } + break; + case CLASS: + if (value_type.class_type != nullptr && value_type.class_type->get_global_name() != StringName()) { + value_hint = value_type.class_type->get_global_name(); + } else { + value_hint = value_type.native_type; + } + break; + case ENUM: + value_hint = String(value_type.native_type).replace("::", "."); + break; + default: + value_hint = "Variant"; + break; + } + result.hint = PROPERTY_HINT_DICTIONARY_TYPE; + result.hint_string = key_hint + ";" + value_hint; } break; case NATIVE: @@ -4953,6 +5133,50 @@ GDScriptParser::DataType GDScriptParser::DataType::get_typed_container_type() co return type; } +bool GDScriptParser::DataType::can_reference(const GDScriptParser::DataType &p_other) const { + if (p_other.is_meta_type) { + return false; + } else if (builtin_type != p_other.builtin_type) { + return false; + } else if (builtin_type != Variant::OBJECT) { + return true; + } + + if (native_type == StringName()) { + return true; + } else if (p_other.native_type == StringName()) { + return false; + } else if (native_type != p_other.native_type && !ClassDB::is_parent_class(p_other.native_type, native_type)) { + return false; + } + + Ref<Script> script = script_type; + if (kind == GDScriptParser::DataType::CLASS && script.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(script_path, err); + ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", script_path)); + script.reference_ptr(scr->find_class(class_type->fqcn)); + } + + Ref<Script> script_other = p_other.script_type; + if (p_other.kind == GDScriptParser::DataType::CLASS && script_other.is_null()) { + Error err = OK; + Ref<GDScript> scr = GDScriptCache::get_shallow_script(p_other.script_path, err); + ERR_FAIL_COND_V_MSG(err, false, vformat(R"(Error while getting cache for script "%s".)", p_other.script_path)); + script_other.reference_ptr(scr->find_class(p_other.class_type->fqcn)); + } + + if (script.is_null()) { + return true; + } else if (script_other.is_null()) { + return false; + } else if (script != script_other && !script_other->inherits_script(script)) { + return false; + } + + return true; +} + void GDScriptParser::complete_extents(Node *p_node) { while (!nodes_in_progress.is_empty() && nodes_in_progress.back()->get() != p_node) { ERR_PRINT("Parser bug: Mismatch in extents tracking stack."); diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 43c5a48fa7..7840474a89 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -189,6 +189,8 @@ public: GDScriptParser::DataType get_typed_container_type() const; + bool can_reference(const DataType &p_other) const; + bool operator==(const DataType &p_other) const { if (type_source == UNDETECTED || p_other.type_source == UNDETECTED) { return true; // Can be considered equal for parsing purposes. @@ -1455,8 +1457,11 @@ private: } void apply_pending_warnings(); #endif - void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1); - void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type); + // Setting p_force to false will prevent the completion context from being update if a context was already set before. + // This should only be done when we push context before we consumed any tokens for the corresponding structure. + // See parse_precedence for an example. + void make_completion_context(CompletionType p_type, Node *p_node, int p_argument = -1, bool p_force = true); + void make_completion_context(CompletionType p_type, Variant::Type p_builtin_type, bool p_force = true); // In some cases it might become necessary to alter the completion context after parsing a subexpression. // For example to not override COMPLETE_CALL_ARGUMENTS with COMPLETION_NONE from string literals. void override_completion_context(const Node *p_for_node, CompletionType p_type, Node *p_node, int p_argument = -1); diff --git a/modules/gdscript/gdscript_utility_functions.cpp b/modules/gdscript/gdscript_utility_functions.cpp index 3e1de628d2..59dd983ed2 100644 --- a/modules/gdscript/gdscript_utility_functions.cpp +++ b/modules/gdscript/gdscript_utility_functions.cpp @@ -232,7 +232,7 @@ struct GDScriptUtilityFunctionsDefinitions { static inline void load(Variant *r_ret, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) { VALIDATE_ARG_COUNT(1); - if (p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING; diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index ddb0cf9502..4617a0dbb9 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -84,9 +84,15 @@ static String _get_var_type(const Variant *p_var) { if (p_var->get_type() == Variant::ARRAY) { basestr = "Array"; const Array *p_array = VariantInternal::get_array(p_var); - Variant::Type builtin_type = (Variant::Type)p_array->get_typed_builtin(); - if (builtin_type != Variant::NIL) { - basestr += "[" + _get_element_type(builtin_type, p_array->get_typed_class_name(), p_array->get_typed_script()) + "]"; + if (p_array->is_typed()) { + basestr += "[" + _get_element_type((Variant::Type)p_array->get_typed_builtin(), p_array->get_typed_class_name(), p_array->get_typed_script()) + "]"; + } + } else if (p_var->get_type() == Variant::DICTIONARY) { + basestr = "Dictionary"; + const Dictionary *p_dictionary = VariantInternal::get_dictionary(p_var); + if (p_dictionary->is_typed()) { + basestr += "[" + _get_element_type((Variant::Type)p_dictionary->get_typed_key_builtin(), p_dictionary->get_typed_key_class_name(), p_dictionary->get_typed_key_script()) + + ", " + _get_element_type((Variant::Type)p_dictionary->get_typed_value_builtin(), p_dictionary->get_typed_value_class_name(), p_dictionary->get_typed_value_script()) + "]"; } } else { basestr = Variant::get_type_name(p_var->get_type()); @@ -120,6 +126,16 @@ Variant GDScriptFunction::_get_default_variant_for_data_type(const GDScriptDataT } return array; + } else if (p_data_type.builtin_type == Variant::DICTIONARY) { + Dictionary dict; + // Typed dictionary. + if (p_data_type.has_container_element_types()) { + const GDScriptDataType &key_type = p_data_type.get_container_element_type_or_variant(0); + const GDScriptDataType &value_type = p_data_type.get_container_element_type_or_variant(1); + dict.set_typed(key_type.builtin_type, key_type.native_type, key_type.script_type, value_type.builtin_type, value_type.native_type, value_type.script_type); + } + + return dict; } else { Callable::CallError ce; Variant variant; @@ -153,6 +169,9 @@ String GDScriptFunction::_get_call_error(const String &p_where, const Variant ** if (p_err.expected == Variant::ARRAY && p_argptrs[p_err.argument]->get_type() == p_err.expected) { return "Invalid type in " + p_where + ". The array of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed array argument."; } + if (p_err.expected == Variant::DICTIONARY && p_argptrs[p_err.argument]->get_type() == p_err.expected) { + return "Invalid type in " + p_where + ". The dictionary of argument " + itos(p_err.argument + 1) + " (" + _get_var_type(p_argptrs[p_err.argument]) + ") does not have the same element type as the expected typed dictionary argument."; + } #endif // DEBUG_ENABLED return "Invalid type in " + p_where + ". Cannot convert argument " + itos(p_err.argument + 1) + " from " + Variant::get_type_name(p_argptrs[p_err.argument]->get_type()) + " to " + Variant::get_type_name(Variant::Type(p_err.expected)) + "."; case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: @@ -215,6 +234,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_OPERATOR_VALIDATED, \ &&OPCODE_TYPE_TEST_BUILTIN, \ &&OPCODE_TYPE_TEST_ARRAY, \ + &&OPCODE_TYPE_TEST_DICTIONARY, \ &&OPCODE_TYPE_TEST_NATIVE, \ &&OPCODE_TYPE_TEST_SCRIPT, \ &&OPCODE_SET_KEYED, \ @@ -237,6 +257,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_ASSIGN_FALSE, \ &&OPCODE_ASSIGN_TYPED_BUILTIN, \ &&OPCODE_ASSIGN_TYPED_ARRAY, \ + &&OPCODE_ASSIGN_TYPED_DICTIONARY, \ &&OPCODE_ASSIGN_TYPED_NATIVE, \ &&OPCODE_ASSIGN_TYPED_SCRIPT, \ &&OPCODE_CAST_TO_BUILTIN, \ @@ -247,6 +268,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_CONSTRUCT_ARRAY, \ &&OPCODE_CONSTRUCT_TYPED_ARRAY, \ &&OPCODE_CONSTRUCT_DICTIONARY, \ + &&OPCODE_CONSTRUCT_TYPED_DICTIONARY, \ &&OPCODE_CALL, \ &&OPCODE_CALL_RETURN, \ &&OPCODE_CALL_ASYNC, \ @@ -275,6 +297,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_RETURN, \ &&OPCODE_RETURN_TYPED_BUILTIN, \ &&OPCODE_RETURN_TYPED_ARRAY, \ + &&OPCODE_RETURN_TYPED_DICTIONARY, \ &&OPCODE_RETURN_TYPED_NATIVE, \ &&OPCODE_RETURN_TYPED_SCRIPT, \ &&OPCODE_ITERATE_BEGIN, \ @@ -548,7 +571,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a return _get_default_variant_for_data_type(return_type); } if (argument_types[i].kind == GDScriptDataType::BUILTIN) { - if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) { + if (argument_types[i].builtin_type == Variant::DICTIONARY && argument_types[i].has_container_element_types()) { + const GDScriptDataType &arg_key_type = argument_types[i].get_container_element_type_or_variant(0); + const GDScriptDataType &arg_value_type = argument_types[i].get_container_element_type_or_variant(1); + Dictionary dict(p_args[i]->operator Dictionary(), arg_key_type.builtin_type, arg_key_type.native_type, arg_key_type.script_type, arg_value_type.builtin_type, arg_value_type.native_type, arg_value_type.script_type); + memnew_placement(&stack[i + 3], Variant(dict)); + } else if (argument_types[i].builtin_type == Variant::ARRAY && argument_types[i].has_container_element_type(0)) { const GDScriptDataType &arg_type = argument_types[i].container_element_types[0]; Array array(p_args[i]->operator Array(), arg_type.builtin_type, arg_type.native_type, arg_type.script_type); memnew_placement(&stack[i + 3], Variant(array)); @@ -827,6 +855,36 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_TYPE_TEST_DICTIONARY) { + CHECK_SPACE(9); + + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(value, 1); + + GET_VARIANT_PTR(key_script_type, 2); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5]; + int key_native_type_idx = _code_ptr[ip + 6]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_VARIANT_PTR(value_script_type, 3); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7]; + int value_native_type_idx = _code_ptr[ip + 8]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + bool result = false; + if (value->get_type() == Variant::DICTIONARY) { + Dictionary *dictionary = VariantInternal::get_dictionary(value); + result = dictionary->get_typed_key_builtin() == ((uint32_t)key_builtin_type) && dictionary->get_typed_key_class_name() == key_native_type && dictionary->get_typed_key_script() == *key_script_type && + dictionary->get_typed_value_builtin() == ((uint32_t)value_builtin_type) && dictionary->get_typed_value_class_name() == value_native_type && dictionary->get_typed_value_script() == *value_script_type; + } + + *dst = result; + ip += 9; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_TYPE_TEST_NATIVE) { CHECK_SPACE(4); @@ -1384,6 +1442,50 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_DICTIONARY) { + CHECK_SPACE(9); + GET_VARIANT_PTR(dst, 0); + GET_VARIANT_PTR(src, 1); + + GET_VARIANT_PTR(key_script_type, 2); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 5]; + int key_native_type_idx = _code_ptr[ip + 6]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_VARIANT_PTR(value_script_type, 3); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 7]; + int value_native_type_idx = _code_ptr[ip + 8]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + if (src->get_type() != Variant::DICTIONARY) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to assign a value of type "%s" to a variable of type "Dictionary[%s, %s]".)", + _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + Dictionary *dictionary = VariantInternal::get_dictionary(src); + + if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type || + dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to assign a dictionary of type "%s" to a variable of type "Dictionary[%s, %s]".)", + _get_var_type(src), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + *dst = *src; + + ip += 9; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_ASSIGN_TYPED_NATIVE) { CHECK_SPACE(4); GET_VARIANT_PTR(dst, 0); @@ -1703,12 +1805,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_INSTRUCTION_ARG(dst, argc * 2); + *dst = Variant(); // Clear potential previous typed dictionary. + *dst = dict; ip += 2; } DISPATCH_OPCODE; + OPCODE(OPCODE_CONSTRUCT_TYPED_DICTIONARY) { + LOAD_INSTRUCTION_ARGS + CHECK_SPACE(6 + instr_arg_count); + ip += instr_arg_count; + + int argc = _code_ptr[ip + 1]; + + GET_INSTRUCTION_ARG(key_script_type, argc * 2 + 1); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 2]; + int key_native_type_idx = _code_ptr[ip + 3]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_INSTRUCTION_ARG(value_script_type, argc * 2 + 2); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 4]; + int value_native_type_idx = _code_ptr[ip + 5]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + Dictionary dict; + + for (int i = 0; i < argc; i++) { + GET_INSTRUCTION_ARG(k, i * 2 + 0); + GET_INSTRUCTION_ARG(v, i * 2 + 1); + dict[*k] = *v; + } + + GET_INSTRUCTION_ARG(dst, argc * 2); + + *dst = Variant(); // Clear potential previous typed dictionary. + + *dst = Dictionary(dict, key_builtin_type, key_native_type, *key_script_type, value_builtin_type, value_native_type, *value_script_type); + + ip += 6; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_CALL_ASYNC) OPCODE(OPCODE_CALL_RETURN) OPCODE(OPCODE_CALL) { @@ -2657,6 +2798,51 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a OPCODE_BREAK; } + OPCODE(OPCODE_RETURN_TYPED_DICTIONARY) { + CHECK_SPACE(8); + GET_VARIANT_PTR(r, 0); + + GET_VARIANT_PTR(key_script_type, 1); + Variant::Type key_builtin_type = (Variant::Type)_code_ptr[ip + 4]; + int key_native_type_idx = _code_ptr[ip + 5]; + GD_ERR_BREAK(key_native_type_idx < 0 || key_native_type_idx >= _global_names_count); + const StringName key_native_type = _global_names_ptr[key_native_type_idx]; + + GET_VARIANT_PTR(value_script_type, 2); + Variant::Type value_builtin_type = (Variant::Type)_code_ptr[ip + 6]; + int value_native_type_idx = _code_ptr[ip + 7]; + GD_ERR_BREAK(value_native_type_idx < 0 || value_native_type_idx >= _global_names_count); + const StringName value_native_type = _global_names_ptr[value_native_type_idx]; + + if (r->get_type() != Variant::DICTIONARY) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Dictionary[%s, %s]".)", + _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + Dictionary *dictionary = VariantInternal::get_dictionary(r); + + if (dictionary->get_typed_key_builtin() != ((uint32_t)key_builtin_type) || dictionary->get_typed_key_class_name() != key_native_type || dictionary->get_typed_key_script() != *key_script_type || + dictionary->get_typed_value_builtin() != ((uint32_t)value_builtin_type) || dictionary->get_typed_value_class_name() != value_native_type || dictionary->get_typed_value_script() != *value_script_type) { +#ifdef DEBUG_ENABLED + err_text = vformat(R"(Trying to return a dictionary of type "%s" where expected return type is "Dictionary[%s, %s]".)", + _get_var_type(r), _get_element_type(key_builtin_type, key_native_type, *key_script_type), + _get_element_type(value_builtin_type, value_native_type, *value_script_type)); +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + + retvalue = *dictionary; + +#ifdef DEBUG_ENABLED + exit_ok = true; +#endif // DEBUG_ENABLED + OPCODE_BREAK; + } + OPCODE(OPCODE_RETURN_TYPED_NATIVE) { CHECK_SPACE(3); GET_VARIANT_PTR(r, 0); diff --git a/modules/gdscript/language_server/gdscript_language_protocol.cpp b/modules/gdscript/language_server/gdscript_language_protocol.cpp index 03d830741b..b636dbe580 100644 --- a/modules/gdscript/language_server/gdscript_language_protocol.cpp +++ b/modules/gdscript/language_server/gdscript_language_protocol.cpp @@ -196,7 +196,7 @@ Dictionary GDScriptLanguageProtocol::initialize(const Dictionary &p_params) { ERR_FAIL_COND_V_MSG(!clients.has(latest_client_id), ret.to_json(), vformat("GDScriptLanguageProtocol: Can't initialize invalid peer '%d'.", latest_client_id)); Ref<LSPeer> peer = clients.get(latest_client_id); - if (peer != nullptr) { + if (peer.is_valid()) { String msg = Variant(request).to_json_string(); msg = format_output(msg); (*peer)->res_queue.push_back(msg.utf8()); @@ -298,7 +298,7 @@ void GDScriptLanguageProtocol::notify_client(const String &p_method, const Varia } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_NULL(peer); + ERR_FAIL_COND(peer.is_null()); Dictionary message = make_notification(p_method, p_params); String msg = Variant(message).to_json_string(); @@ -319,7 +319,7 @@ void GDScriptLanguageProtocol::request_client(const String &p_method, const Vari } ERR_FAIL_COND(!clients.has(p_client_id)); Ref<LSPeer> peer = clients.get(p_client_id); - ERR_FAIL_NULL(peer); + ERR_FAIL_COND(peer.is_null()); Dictionary message = make_request(p_method, p_params, next_server_id); next_server_id++; diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp index 2224bb0040..fa5f279db9 100644 --- a/modules/gdscript/language_server/gdscript_text_document.cpp +++ b/modules/gdscript/language_server/gdscript_text_document.cpp @@ -309,7 +309,7 @@ Dictionary GDScriptTextDocument::resolve(const Dictionary &p_params) { params.load(p_params["data"]); symbol = GDScriptLanguageProtocol::get_singleton()->get_workspace()->resolve_symbol(params, item.label, item.kind == lsp::CompletionItemKind::Method || item.kind == lsp::CompletionItemKind::Function); - } else if (data.get_type() == Variant::STRING) { + } else if (data.is_string()) { String query = data; Vector<String> param_symbols = query.split(SYMBOL_SEPERATOR, false); diff --git a/modules/gdscript/language_server/godot_lsp.h b/modules/gdscript/language_server/godot_lsp.h index 13c2693390..bdf339f5fe 100644 --- a/modules/gdscript/language_server/godot_lsp.h +++ b/modules/gdscript/language_server/godot_lsp.h @@ -1064,7 +1064,7 @@ struct CompletionItem { } if (p_dict.has("documentation")) { Variant doc = p_dict["documentation"]; - if (doc.get_type() == Variant::STRING) { + if (doc.is_string()) { documentation.value = doc; } else if (doc.get_type() == Variant::DICTIONARY) { Dictionary v = doc; diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd new file mode 100644 index 0000000000..c180cca03c --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.gd @@ -0,0 +1,3 @@ +func test(): + for key: int in { "a": 1 }: + print(key) diff --git a/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out new file mode 100644 index 0000000000..8530783673 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/for_loop_wrong_specified_type_with_literal_dictionary.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "String" as "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd new file mode 100644 index 0000000000..75d1b7fe62 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Dictionary[float, float] = { 1.0: 0.0 } + var typed: Dictionary[int, int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out new file mode 100644 index 0000000000..e05d4be8c9 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assign_differently_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot assign a value of type Dictionary[float, float] to variable "typed" with specified type Dictionary[int, int]. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd new file mode 100644 index 0000000000..e0af71823a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.gd @@ -0,0 +1,2 @@ +func test(): + const dict: Dictionary[int, int] = { "Hello": "World" } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out new file mode 100644 index 0000000000..8530783673 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_assignment.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot include a value of type "String" as "int". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd new file mode 100644 index 0000000000..814ba12aef --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.gd @@ -0,0 +1,4 @@ +func test(): + var unconvertible := 1 + var typed: Dictionary[Object, Object] = { unconvertible: unconvertible } + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out new file mode 100644 index 0000000000..9d6c9d9144 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_init_with_unconvertible_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Cannot have a key of type "int" in a dictionary of type "Dictionary[Object, Object]". diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd new file mode 100644 index 0000000000..73d8ce2b96 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Dictionary[int, int]): + print(typed.size()) + +func test(): + var differently: Dictionary[float, float] = { 1.0: 0.0 } + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out new file mode 100644 index 0000000000..302109cf8a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/errors/typed_dictionary_pass_differently_to_typed.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Invalid argument for "expect_typed()" function: argument 1 should be "Dictionary[int, int]" but is "Dictionary[float, float]". diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd new file mode 100644 index 0000000000..65f5e7da07 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.gd @@ -0,0 +1,20 @@ +func print_untyped(dictionary = { 0: 1 }) -> void: + print(dictionary) + print(dictionary.get_typed_key_builtin()) + print(dictionary.get_typed_value_builtin()) + +func print_inferred(dictionary := { 2: 3 }) -> void: + print(dictionary) + print(dictionary.get_typed_key_builtin()) + print(dictionary.get_typed_value_builtin()) + +func print_typed(dictionary: Dictionary[int, int] = { 4: 5 }) -> void: + print(dictionary) + print(dictionary.get_typed_key_builtin()) + print(dictionary.get_typed_value_builtin()) + +func test(): + print_untyped() + print_inferred() + print_typed() + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out new file mode 100644 index 0000000000..c31561bee3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_as_default_parameter.out @@ -0,0 +1,11 @@ +GDTEST_OK +{ 0: 1 } +0 +0 +{ 2: 3 } +0 +0 +{ 4: 5 } +2 +2 +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd new file mode 100644 index 0000000000..0aa3de2c4a --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.gd @@ -0,0 +1,4 @@ +func test(): + var dict := { 0: 0 } + dict[0] = 1 + print(dict[0]) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out new file mode 100644 index 0000000000..a7f1357bb2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_inferred_access_isnt_constant.out @@ -0,0 +1,2 @@ +GDTEST_OK +1 diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd new file mode 100644 index 0000000000..9d3fffd1de --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.gd @@ -0,0 +1,214 @@ +class A: pass +class B extends A: pass + +enum E { E0 = 391, E1 = 193 } + +func floats_identity(floats: Dictionary[float, float]): return floats + +class Members: + var one: Dictionary[int, int] = { 104: 401 } + var two: Dictionary[int, int] = one + + func check_passing() -> bool: + Utils.check(str(one) == '{ 104: 401 }') + Utils.check(str(two) == '{ 104: 401 }') + two[582] = 285 + Utils.check(str(one) == '{ 104: 401, 582: 285 }') + Utils.check(str(two) == '{ 104: 401, 582: 285 }') + two = { 486: 684 } + Utils.check(str(one) == '{ 104: 401, 582: 285 }') + Utils.check(str(two) == '{ 486: 684 }') + return true + + +@warning_ignore("unsafe_method_access") +@warning_ignore("assert_always_true") +@warning_ignore("return_value_discarded") +func test(): + var untyped_basic = { 459: 954 } + Utils.check(str(untyped_basic) == '{ 459: 954 }') + Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_NIL) + + var inferred_basic := { 366: 663 } + Utils.check(str(inferred_basic) == '{ 366: 663 }') + Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_NIL) + + var typed_basic: Dictionary = { 521: 125 } + Utils.check(str(typed_basic) == '{ 521: 125 }') + Utils.check(typed_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(typed_basic.get_typed_value_builtin() == TYPE_NIL) + + + var empty_floats: Dictionary[float, float] = {} + Utils.check(str(empty_floats) == '{ }') + Utils.check(empty_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(empty_floats.get_typed_value_builtin() == TYPE_FLOAT) + + untyped_basic = empty_floats + Utils.check(untyped_basic.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(untyped_basic.get_typed_value_builtin() == TYPE_FLOAT) + + inferred_basic = empty_floats + Utils.check(inferred_basic.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(inferred_basic.get_typed_value_builtin() == TYPE_FLOAT) + + typed_basic = empty_floats + Utils.check(typed_basic.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(typed_basic.get_typed_value_builtin() == TYPE_FLOAT) + + empty_floats[705.0] = 507.0 + untyped_basic[430.0] = 34.0 + inferred_basic[263.0] = 362.0 + typed_basic[518.0] = 815.0 + Utils.check(str(empty_floats) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(untyped_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(inferred_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + Utils.check(str(typed_basic) == '{ 705: 507, 430: 34, 263: 362, 518: 815 }') + + + const constant_float := 950.0 + const constant_int := 170 + var typed_float := 954.0 + var filled_floats: Dictionary[float, float] = { constant_float: constant_int, typed_float: empty_floats[430.0] + empty_floats[263.0] } + Utils.check(str(filled_floats) == '{ 950: 170, 954: 396 }') + Utils.check(filled_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(filled_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var casted_floats := { empty_floats[263.0] * 2: empty_floats[263.0] / 2 } as Dictionary[float, float] + Utils.check(str(casted_floats) == '{ 724: 181 }') + Utils.check(casted_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(casted_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var returned_floats = (func () -> Dictionary[float, float]: return { 554: 455 }).call() + Utils.check(str(returned_floats) == '{ 554: 455 }') + Utils.check(returned_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(returned_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var passed_floats = floats_identity({ 663.0 if randf() > 0.5 else 663.0: 366.0 if randf() <= 0.5 else 366.0 }) + Utils.check(str(passed_floats) == '{ 663: 366 }') + Utils.check(passed_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(passed_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var default_floats = (func (floats: Dictionary[float, float] = { 364.0: 463.0 }): return floats).call() + Utils.check(str(default_floats) == '{ 364: 463 }') + Utils.check(default_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(default_floats.get_typed_value_builtin() == TYPE_FLOAT) + + var typed_int := 556 + var converted_floats: Dictionary[float, float] = { typed_int: typed_int } + converted_floats[498.0] = 894 + Utils.check(str(converted_floats) == '{ 556: 556, 498: 894 }') + Utils.check(converted_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(converted_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + const constant_basic = { 228: 822 } + Utils.check(str(constant_basic) == '{ 228: 822 }') + Utils.check(constant_basic.get_typed_key_builtin() == TYPE_NIL) + Utils.check(constant_basic.get_typed_value_builtin() == TYPE_NIL) + + const constant_floats: Dictionary[float, float] = { constant_float - constant_basic[228] - constant_int: constant_float + constant_basic[228] + constant_int } + Utils.check(str(constant_floats) == '{ -42: 1942 }') + Utils.check(constant_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(constant_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + var source_floats: Dictionary[float, float] = { 999.74: 47.999 } + untyped_basic = source_floats + var destination_floats: Dictionary[float, float] = untyped_basic + destination_floats[999.74] -= 0.999 + Utils.check(str(source_floats) == '{ 999.74: 47 }') + Utils.check(str(untyped_basic) == '{ 999.74: 47 }') + Utils.check(str(destination_floats) == '{ 999.74: 47 }') + Utils.check(destination_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(destination_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + var duplicated_floats := empty_floats.duplicate() + duplicated_floats.erase(705.0) + duplicated_floats.erase(430.0) + duplicated_floats.erase(518.0) + duplicated_floats[263.0] *= 3 + Utils.check(str(duplicated_floats) == '{ 263: 1086 }') + Utils.check(duplicated_floats.get_typed_key_builtin() == TYPE_FLOAT) + Utils.check(duplicated_floats.get_typed_value_builtin() == TYPE_FLOAT) + + + var b_objects: Dictionary[int, B] = { 0: B.new(), 1: B.new() as A, 2: null } + Utils.check(b_objects.size() == 3) + Utils.check(b_objects.get_typed_value_builtin() == TYPE_OBJECT) + Utils.check(b_objects.get_typed_value_script() == B) + + var a_objects: Dictionary[int, A] = { 0: A.new(), 1: B.new(), 2: null, 3: b_objects[0] } + Utils.check(a_objects.size() == 4) + Utils.check(a_objects.get_typed_value_builtin() == TYPE_OBJECT) + Utils.check(a_objects.get_typed_value_script() == A) + + var a_passed = (func check_a_passing(p_objects: Dictionary[int, A]): return p_objects.size()).call(a_objects) + Utils.check(a_passed == 4) + + var b_passed = (func check_b_passing(basic: Dictionary): return basic[0] != null).call(b_objects) + Utils.check(b_passed == true) + + + var empty_strings: Dictionary[String, String] = {} + var empty_bools: Dictionary[bool, bool] = {} + var empty_basic_one := {} + var empty_basic_two := {} + Utils.check(empty_strings == empty_bools) + Utils.check(empty_basic_one == empty_basic_two) + Utils.check(empty_strings.hash() == empty_bools.hash()) + Utils.check(empty_basic_one.hash() == empty_basic_two.hash()) + + + var assign_source: Dictionary[int, int] = { 527: 725 } + var assign_target: Dictionary[int, int] = {} + assign_target.assign(assign_source) + Utils.check(str(assign_source) == '{ 527: 725 }') + Utils.check(str(assign_target) == '{ 527: 725 }') + assign_source[657] = 756 + Utils.check(str(assign_source) == '{ 527: 725, 657: 756 }') + Utils.check(str(assign_target) == '{ 527: 725 }') + + + var defaults_passed = (func check_defaults_passing(one: Dictionary[int, int] = {}, two := one): + one[887] = 788 + two[198] = 891 + Utils.check(str(one) == '{ 887: 788, 198: 891 }') + Utils.check(str(two) == '{ 887: 788, 198: 891 }') + two = {130: 31} + Utils.check(str(one) == '{ 887: 788, 198: 891 }') + Utils.check(str(two) == '{ 130: 31 }') + return true + ).call() + Utils.check(defaults_passed == true) + + + var members := Members.new() + var members_passed := members.check_passing() + Utils.check(members_passed == true) + + + var typed_enums: Dictionary[E, E] = {} + typed_enums[E.E0] = E.E1 + Utils.check(str(typed_enums) == '{ 391: 193 }') + Utils.check(typed_enums.get_typed_key_builtin() == TYPE_INT) + Utils.check(typed_enums.get_typed_value_builtin() == TYPE_INT) + + const const_enums: Dictionary[E, E] = {} + Utils.check(const_enums.get_typed_key_builtin() == TYPE_INT) + Utils.check(const_enums.get_typed_key_class_name() == &'') + Utils.check(const_enums.get_typed_value_builtin() == TYPE_INT) + Utils.check(const_enums.get_typed_value_class_name() == &'') + + + var a := A.new() + var b := B.new() + var typed_natives: Dictionary[RefCounted, RefCounted] = { a: b } + var typed_scripts = Dictionary(typed_natives, TYPE_OBJECT, "RefCounted", A, TYPE_OBJECT, "RefCounted", B) + Utils.check(typed_scripts[a] == b) + + + print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_usage.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd new file mode 100644 index 0000000000..f4a23ade14 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.gd @@ -0,0 +1,9 @@ +class Inner: + var prop = "Inner" + +var dict: Dictionary[int, Inner] = { 0: Inner.new() } + + +func test(): + var element: Inner = dict[0] + print(element.prop) diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out new file mode 100644 index 0000000000..8f250d2632 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_dictionary_with_custom_class.out @@ -0,0 +1,2 @@ +GDTEST_OK +Inner diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out index 8d953818eb..e6a1cab77d 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/confusable_capture_reassignment.out @@ -15,5 +15,5 @@ GDTEST_OK >> Line: 16 >> CONFUSABLE_CAPTURE_REASSIGNMENT >> Reassigning lambda capture does not modify the outer local variable "array_assign". -lambda 2 2 12 (2, 0) [2] [2] { "x": 2 } -outer 2 1 1 (1, 0) [1] [2] { "x": 2 } +lambda 2 2 12 (2, 0) [2] [2] { &"x": 2 } +outer 2 1 1 (1, 0) [1] [2] { &"x": 2 } diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd index d937dfdcfe..37f118dc5d 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.gd @@ -1,12 +1,29 @@ -signal s1() -signal s2() -signal s3() +# Doesn't produce the warning: +signal used_as_first_class_signal() +signal used_with_signal_constructor() +signal used_with_signal_emit() +signal used_with_object_emit_signal() +signal used_with_object_connect() +signal used_with_object_disconnect() +signal used_with_self_prefix() + +# Produce the warning: +signal used_with_dynamic_name() +signal just_unused() @warning_ignore("unused_signal") -signal s4() +signal unused_but_ignored() func no_exec(): - s1.emit() - print(s2) + print(used_as_first_class_signal) + print(Signal(self, "used_with_signal_constructor")) + used_with_signal_emit.emit() + print(emit_signal("used_with_object_emit_signal")) + print(connect("used_with_object_connect", Callable())) + disconnect("used_with_object_disconnect", Callable()) + print(self.emit_signal("used_with_self_prefix")) + + var dynamic_name := "used_with_dynamic_name" + print(emit_signal(dynamic_name)) func test(): pass diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out index ff57017830..39ddf91c76 100644 --- a/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out +++ b/modules/gdscript/tests/scripts/analyzer/warnings/unused_signal.out @@ -1,5 +1,9 @@ GDTEST_OK >> WARNING ->> Line: 3 +>> Line: 11 >> UNUSED_SIGNAL ->> The signal "s3" is declared but never explicitly used in the class. +>> The signal "used_with_dynamic_name" is declared but never explicitly used in the class. +>> WARNING +>> Line: 12 +>> UNUSED_SIGNAL +>> The signal "just_unused" is declared but never explicitly used in the class. diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg new file mode 100644 index 0000000000..e4759ac76b --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.cfg @@ -0,0 +1,19 @@ +[output] +include=[ + {"display": "new"}, + {"display": "SIZE_EXPAND"}, + {"display": "SIZE_EXPAND_FILL"}, + {"display": "SIZE_FILL"}, + {"display": "SIZE_SHRINK_BEGIN"}, + {"display": "SIZE_SHRINK_CENTER"}, + {"display": "SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd new file mode 100644 index 0000000000..4aeafb2e0a --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = Control.➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg new file mode 100644 index 0000000000..e4759ac76b --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.cfg @@ -0,0 +1,19 @@ +[output] +include=[ + {"display": "new"}, + {"display": "SIZE_EXPAND"}, + {"display": "SIZE_EXPAND_FILL"}, + {"display": "SIZE_FILL"}, + {"display": "SIZE_SHRINK_BEGIN"}, + {"display": "SIZE_SHRINK_CENTER"}, + {"display": "SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd new file mode 100644 index 0000000000..47e9bd5a67 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_attribute_identifier.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = Control.SIZE➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg new file mode 100644 index 0000000000..5cc4ec5fd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd new file mode 100644 index 0000000000..5c96720bd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_identifier.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = Con➡ + pass diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg new file mode 100644 index 0000000000..5cc4ec5fd3 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.cfg @@ -0,0 +1,12 @@ +[output] +include=[ + {"display": "Control.SIZE_EXPAND"}, + {"display": "Control.SIZE_EXPAND_FILL"}, + {"display": "Control.SIZE_FILL"}, + {"display": "Control.SIZE_SHRINK_BEGIN"}, + {"display": "Control.SIZE_SHRINK_CENTER"}, + {"display": "Control.SIZE_SHRINK_END"}, +] +exclude=[ + {"display": "contro_var"} +] diff --git a/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd new file mode 100644 index 0000000000..d8bf13e51d --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/assignment_options/enum_no_identifier.gd @@ -0,0 +1,7 @@ +extends Control + +var contro_var + +func _ready(): + size_flags_horizontal = ➡ + pass diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out index 553d40d953..a8ef52583d 100644 --- a/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_lua_style.out @@ -1,2 +1,2 @@ GDTEST_OK -{ "a": 1, "b": 2, "with spaces": 3, "2": 4 } +{ &"a": 1, &"b": 2, &"with spaces": 3, &"2": 4 } diff --git a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out index cf79845f53..4e404e1d26 100644 --- a/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out +++ b/modules/gdscript/tests/scripts/parser/features/dictionary_mixed_syntax.out @@ -1,2 +1,2 @@ GDTEST_OK -{ "hello": { "world": { "is": "beautiful" } } } +{ "hello": { &"world": { "is": "beautiful" } } } diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd new file mode 100644 index 0000000000..57e6489484 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.gd @@ -0,0 +1,5 @@ +func test(): + var my_dictionary: Dictionary[int, String] = { 1: "one", 2: "two", 3: "three" } + var inferred_dictionary := { 1: "one", 2: "two", 3: "three" } # This is Dictionary[int, String]. + print(my_dictionary) + print(inferred_dictionary) diff --git a/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out new file mode 100644 index 0000000000..6021c338ee --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/features/typed_dictionaries.out @@ -0,0 +1,3 @@ +GDTEST_OK +{ 1: "one", 2: "two", 3: "three" } +{ 1: "one", 2: "two", 3: "three" } diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd new file mode 100644 index 0000000000..75004742a2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.gd @@ -0,0 +1,4 @@ +func test(): + var basic := { 1: 1 } + var typed: Dictionary[int, int] = basic + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out new file mode 100644 index 0000000000..cadb17f570 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_assign_basic_to_typed.gd +>> 3 +>> Trying to assign a dictionary of type "Dictionary" to a variable of type "Dictionary[int, int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd new file mode 100644 index 0000000000..e5ab4a1a85 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.gd @@ -0,0 +1,4 @@ +func test(): + var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float] + var typed: Dictionary[int, int] = differently + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out new file mode 100644 index 0000000000..fe1e5d1285 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_differently_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_assign_differently_typed.gd +>> 3 +>> Trying to assign a dictionary of type "Dictionary[float, float]" to a variable of type "Dictionary[int, int]". diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd new file mode 100644 index 0000000000..6cc0e57255 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.gd @@ -0,0 +1,7 @@ +class Foo: pass +class Bar extends Foo: pass +class Baz extends Foo: pass + +func test(): + var typed: Dictionary[Bar, Bar] = { Baz.new() as Foo: Baz.new() as Foo } + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out new file mode 100644 index 0000000000..18a4c360e2 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_assign_wrong_to_typed.out @@ -0,0 +1,5 @@ +GDTEST_RUNTIME_ERROR +>> ERROR +>> Method/function failed. +>> Unable to convert key from "Object" to "Object". +not ok diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd new file mode 100644 index 0000000000..8f7d732584 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Dictionary[int, int]): + print(typed.size()) + +func test(): + var basic := { 1: 1 } + expect_typed(basic) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out new file mode 100644 index 0000000000..fb45461701 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_basic_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_pass_basic_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_basic_to_typed.gd)'. The dictionary of argument 1 (Dictionary) does not have the same element type as the expected typed dictionary argument. diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd new file mode 100644 index 0000000000..978a9fdfee --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.gd @@ -0,0 +1,7 @@ +func expect_typed(typed: Dictionary[int, int]): + print(typed.size()) + +func test(): + var differently: Variant = { 1.0: 0.0 } as Dictionary[float, float] + expect_typed(differently) + print('not ok') diff --git a/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out new file mode 100644 index 0000000000..4036a1bf01 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/errors/typed_dictionary_pass_differently_to_typed.out @@ -0,0 +1,6 @@ +GDTEST_RUNTIME_ERROR +>> SCRIPT ERROR +>> on function: test() +>> runtime/errors/typed_dictionary_pass_differently_to_typed.gd +>> 6 +>> Invalid type in function 'expect_typed' in base 'RefCounted (typed_dictionary_pass_differently_to_typed.gd)'. The dictionary of argument 1 (Dictionary[float, float]) does not have the same element type as the expected typed dictionary argument. diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd index 94bac1974f..de5eaabb79 100644 --- a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.gd @@ -7,11 +7,11 @@ func test(): stringname_dict[&"abc"] = 24 print("String key is TYPE_STRING: ", typeof(string_dict.keys()[0]) == TYPE_STRING) - print("StringName key is TYPE_STRING: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING) + print("StringName key is TYPE_STRING_NAME: ", typeof(stringname_dict.keys()[0]) == TYPE_STRING_NAME) print("StringName gets String: ", string_dict.get(&"abc")) print("String gets StringName: ", stringname_dict.get("abc")) stringname_dict[&"abc"] = 42 - # They compare equal because StringName keys are converted to String. + # They compare equal because StringName keys are considered equivalent to String keys. print("String Dictionary == StringName Dictionary: ", string_dict == stringname_dict) diff --git a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out index ab5b89d55c..a1461912bf 100644 --- a/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out +++ b/modules/gdscript/tests/scripts/runtime/features/dictionary_string_stringname_equivalent.out @@ -1,6 +1,6 @@ GDTEST_OK String key is TYPE_STRING: true -StringName key is TYPE_STRING: true +StringName key is TYPE_STRING_NAME: true StringName gets String: 42 String gets StringName: 24 String Dictionary == StringName Dictionary: true diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd index bc899a3a6f..393500bd9b 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.gd @@ -28,13 +28,18 @@ func test(): prints(var_to_str(e), var_to_str(elem)) print("Test String-keys dictionary.") - var d1 := {a = 1, b = 2, c = 3} + var d1 := { a = 1, b = 2, c = 3 } for k: StringName in d1: var key := k prints(var_to_str(k), var_to_str(key)) print("Test RefCounted-keys dictionary.") - var d2 := {RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3} + var d2 := { RefCounted.new(): 1, Resource.new(): 2, ConfigFile.new(): 3 } for k: RefCounted in d2: var key := k prints(k.get_class(), key.get_class()) + + print("Test implicitly typed dictionary literal.") + for k: StringName in { x = 123, y = 456, z = 789 }: + var key := k + prints(var_to_str(k), var_to_str(key)) diff --git a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out index eeebdc4be5..89cc1b76fe 100644 --- a/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out +++ b/modules/gdscript/tests/scripts/runtime/features/for_loop_iterator_specified_types.out @@ -27,3 +27,7 @@ Test RefCounted-keys dictionary. RefCounted RefCounted Resource Resource ConfigFile ConfigFile +Test implicitly typed dictionary literal. +&"x" &"x" +&"y" &"y" +&"z" &"z" diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd index 91d5a501c8..4ce53aa395 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -31,6 +31,16 @@ var test_var_hard_array_my_enum: Array[MyEnum] var test_var_hard_array_resource: Array[Resource] var test_var_hard_array_this: Array[TestMemberInfo] var test_var_hard_array_my_class: Array[MyClass] +var test_var_hard_dictionary: Dictionary +var test_var_hard_dictionary_int_variant: Dictionary[int, Variant] +var test_var_hard_dictionary_variant_int: Dictionary[Variant, int] +var test_var_hard_dictionary_int_int: Dictionary[int, int] +var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type] +var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode] +var test_var_hard_dictionary_my_enum: Dictionary[MyEnum, MyEnum] +var test_var_hard_dictionary_resource: Dictionary[Resource, Resource] +var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo] +var test_var_hard_dictionary_my_class: Dictionary[MyClass, MyClass] var test_var_hard_resource: Resource var test_var_hard_this: TestMemberInfo var test_var_hard_my_class: MyClass @@ -43,17 +53,17 @@ func test_func_weak_null(): return null func test_func_weak_int(): return 1 func test_func_hard_variant() -> Variant: return null func test_func_hard_int() -> int: return 1 -func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d = 2): pass +func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e = 2): pass func test_func_args_2(_a = 1, _b = _a, _c = [2], _d = 3): pass signal test_signal_1() signal test_signal_2(a: Variant, b) -signal test_signal_3(a: int, b: Array[int]) -signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) -signal test_signal_5(a: MyEnum, b: Array[MyEnum]) -signal test_signal_6(a: Resource, b: Array[Resource]) -signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) -signal test_signal_8(a: MyClass, b: Array[MyClass]) +signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type]) +signal test_signal_5(a: MyEnum, b: Array[MyEnum], c: Dictionary[MyEnum, MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo]) +signal test_signal_8(a: MyClass, b: Array[MyClass], c: Dictionary[MyClass, MyClass]) func no_exec(): test_signal_1.emit() diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.out b/modules/gdscript/tests/scripts/runtime/features/member_info.out index 7c826ac05a..2baf451aa5 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.out +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.out @@ -23,6 +23,16 @@ var test_var_hard_array_my_enum: Array[TestMemberInfo.MyEnum] var test_var_hard_array_resource: Array[Resource] var test_var_hard_array_this: Array[TestMemberInfo] var test_var_hard_array_my_class: Array[RefCounted] +var test_var_hard_dictionary: Dictionary +var test_var_hard_dictionary_int_variant: Dictionary[int, Variant] +var test_var_hard_dictionary_variant_int: Dictionary[Variant, int] +var test_var_hard_dictionary_int_int: Dictionary[int, int] +var test_var_hard_dictionary_variant_type: Dictionary[Variant.Type, Variant.Type] +var test_var_hard_dictionary_node_process_mode: Dictionary[Node.ProcessMode, Node.ProcessMode] +var test_var_hard_dictionary_my_enum: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum] +var test_var_hard_dictionary_resource: Dictionary[Resource, Resource] +var test_var_hard_dictionary_this: Dictionary[TestMemberInfo, TestMemberInfo] +var test_var_hard_dictionary_my_class: Dictionary[RefCounted, RefCounted] var test_var_hard_resource: Resource var test_var_hard_this: TestMemberInfo var test_var_hard_my_class: RefCounted @@ -33,13 +43,13 @@ func test_func_weak_null() -> Variant func test_func_weak_int() -> Variant func test_func_hard_variant() -> Variant func test_func_hard_int() -> int -func test_func_args_1(_a: int, _b: Array[int], _c: int = 1, _d: Variant = 2) -> void +func test_func_args_1(_a: int, _b: Array[int], _c: Dictionary[int, int], _d: int = 1, _e: Variant = 2) -> void func test_func_args_2(_a: Variant = 1, _b: Variant = null, _c: Variant = null, _d: Variant = 3) -> void signal test_signal_1() signal test_signal_2(a: Variant, b: Variant) -signal test_signal_3(a: int, b: Array[int]) -signal test_signal_4(a: Variant.Type, b: Array[Variant.Type]) -signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum]) -signal test_signal_6(a: Resource, b: Array[Resource]) -signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo]) -signal test_signal_8(a: RefCounted, b: Array[RefCounted]) +signal test_signal_3(a: int, b: Array[int], c: Dictionary[int, int]) +signal test_signal_4(a: Variant.Type, b: Array[Variant.Type], c: Dictionary[Variant.Type, Variant.Type]) +signal test_signal_5(a: TestMemberInfo.MyEnum, b: Array[TestMemberInfo.MyEnum], c: Dictionary[TestMemberInfo.MyEnum, TestMemberInfo.MyEnum]) +signal test_signal_6(a: Resource, b: Array[Resource], c: Dictionary[Resource, Resource]) +signal test_signal_7(a: TestMemberInfo, b: Array[TestMemberInfo], c: Dictionary[TestMemberInfo, TestMemberInfo]) +signal test_signal_8(a: RefCounted, b: Array[RefCounted], c: Dictionary[RefCounted, RefCounted]) diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd new file mode 100644 index 0000000000..0371ee5630 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.gd @@ -0,0 +1,6 @@ +func test_param(dictionary: Dictionary[int, String]) -> void: + print(dictionary.get_typed_key_builtin() == TYPE_INT) + print(dictionary.get_typed_value_builtin() == TYPE_STRING) + +func test() -> void: + test_param({ 123: "some_string" }) diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out new file mode 100644 index 0000000000..9d111a8322 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_implicit_cast_param.out @@ -0,0 +1,3 @@ +GDTEST_OK +true +true diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd new file mode 100644 index 0000000000..ee51440d80 --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.gd @@ -0,0 +1,7 @@ +func test(): + var untyped: Variant = 32 + var typed: Dictionary[int, int] = { untyped: untyped } + Utils.check(typed.get_typed_key_builtin() == TYPE_INT) + Utils.check(typed.get_typed_value_builtin() == TYPE_INT) + Utils.check(str(typed) == '{ 32: 32 }') + print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out new file mode 100644 index 0000000000..1b47ed10dc --- /dev/null +++ b/modules/gdscript/tests/scripts/runtime/features/typed_dictionary_init_with_untyped_in_literal.out @@ -0,0 +1,2 @@ +GDTEST_OK +ok diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 5d615d8557..1e2788f765 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -24,6 +24,11 @@ static func get_type(property: Dictionary, is_return: bool = false) -> String: if str(property.hint_string).is_empty(): return "Array[<unknown type>]" return "Array[%s]" % property.hint_string + TYPE_DICTIONARY: + if property.hint == PROPERTY_HINT_DICTIONARY_TYPE: + if str(property.hint_string).is_empty(): + return "Dictionary[<unknown type>, <unknown type>]" + return "Dictionary[%s]" % str(property.hint_string).replace(";", ", ") TYPE_OBJECT: if not str(property.class_name).is_empty(): return property.class_name @@ -188,6 +193,8 @@ static func get_property_hint_name(hint: PropertyHint) -> String: return "PROPERTY_HINT_INT_IS_POINTER" PROPERTY_HINT_ARRAY_TYPE: return "PROPERTY_HINT_ARRAY_TYPE" + PROPERTY_HINT_DICTIONARY_TYPE: + return "PROPERTY_HINT_DICTIONARY_TYPE" PROPERTY_HINT_LOCALE_ID: return "PROPERTY_HINT_LOCALE_ID" PROPERTY_HINT_LOCALIZABLE_STRING: diff --git a/modules/gltf/extensions/gltf_document_extension.cpp b/modules/gltf/extensions/gltf_document_extension.cpp index 9fdd6034a9..c6540ebb22 100644 --- a/modules/gltf/extensions/gltf_document_extension.cpp +++ b/modules/gltf/extensions/gltf_document_extension.cpp @@ -56,7 +56,7 @@ void GLTFDocumentExtension::_bind_methods() { // Import process. Error GLTFDocumentExtension::import_preflight(Ref<GLTFState> p_state, Vector<String> p_extensions) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_import_preflight, p_state, p_extensions, err); return err; @@ -69,16 +69,16 @@ Vector<String> GLTFDocumentExtension::get_supported_extensions() { } Error GLTFDocumentExtension::parse_node_extensions(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &p_extensions) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_parse_node_extensions, p_state, p_gltf_node, p_extensions, err); return err; } Error GLTFDocumentExtension::parse_image_data(Ref<GLTFState> p_state, const PackedByteArray &p_image_data, const String &p_mime_type, Ref<Image> r_image) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(r_image, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(r_image.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_parse_image_data, p_state, p_image_data, p_mime_type, r_image, err); return err; @@ -91,31 +91,31 @@ String GLTFDocumentExtension::get_image_file_extension() { } Error GLTFDocumentExtension::parse_texture_json(Ref<GLTFState> p_state, const Dictionary &p_texture_json, Ref<GLTFTexture> r_gltf_texture) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(r_gltf_texture, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(r_gltf_texture.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_parse_texture_json, p_state, p_texture_json, r_gltf_texture, err); return err; } Node3D *GLTFDocumentExtension::generate_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_parent) { - ERR_FAIL_NULL_V(p_state, nullptr); - ERR_FAIL_NULL_V(p_gltf_node, nullptr); + ERR_FAIL_COND_V(p_state.is_null(), nullptr); + ERR_FAIL_COND_V(p_gltf_node.is_null(), nullptr); Node3D *ret_node = nullptr; GDVIRTUAL_CALL(_generate_scene_node, p_state, p_gltf_node, p_scene_parent, ret_node); return ret_node; } Error GLTFDocumentExtension::import_post_parse(Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_import_post_parse, p_state, err); return err; } Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); ERR_FAIL_NULL_V(p_node, ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_import_node, p_state, p_gltf_node, r_dict, p_node, err); @@ -124,7 +124,7 @@ Error GLTFDocumentExtension::import_node(Ref<GLTFState> p_state, Ref<GLTFNode> p Error GLTFDocumentExtension::import_post(Ref<GLTFState> p_state, Node *p_root) { ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_import_post, p_state, p_root, err); return err; @@ -139,14 +139,14 @@ Error GLTFDocumentExtension::export_preflight(Ref<GLTFState> p_state, Node *p_ro } void GLTFDocumentExtension::convert_scene_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Node *p_scene_node) { - ERR_FAIL_NULL(p_state); - ERR_FAIL_NULL(p_gltf_node); + ERR_FAIL_COND(p_state.is_null()); + ERR_FAIL_COND(p_gltf_node.is_null()); ERR_FAIL_NULL(p_scene_node); GDVIRTUAL_CALL(_convert_scene_node, p_state, p_gltf_node, p_scene_node); } Error GLTFDocumentExtension::export_preserialize(Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_export_preserialize, p_state, err); return err; @@ -160,38 +160,38 @@ Vector<String> GLTFDocumentExtension::get_saveable_image_formats() { PackedByteArray GLTFDocumentExtension::serialize_image_to_bytes(Ref<GLTFState> p_state, Ref<Image> p_image, Dictionary p_image_dict, const String &p_image_format, float p_lossy_quality) { PackedByteArray ret; - ERR_FAIL_NULL_V(p_state, ret); - ERR_FAIL_NULL_V(p_image, ret); + ERR_FAIL_COND_V(p_state.is_null(), ret); + ERR_FAIL_COND_V(p_image.is_null(), ret); GDVIRTUAL_CALL(_serialize_image_to_bytes, p_state, p_image, p_image_dict, p_image_format, p_lossy_quality, ret); return ret; } Error GLTFDocumentExtension::save_image_at_path(Ref<GLTFState> p_state, Ref<Image> p_image, const String &p_file_path, const String &p_image_format, float p_lossy_quality) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_image.is_null(), ERR_INVALID_PARAMETER); Error ret = OK; GDVIRTUAL_CALL(_save_image_at_path, p_state, p_image, p_file_path, p_image_format, p_lossy_quality, ret); return ret; } Error GLTFDocumentExtension::serialize_texture_json(Ref<GLTFState> p_state, Dictionary p_texture_json, Ref<GLTFTexture> p_gltf_texture, const String &p_image_format) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_gltf_texture, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_texture.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_serialize_texture_json, p_state, p_texture_json, p_gltf_texture, p_image_format, err); return err; } Error GLTFDocumentExtension::export_node(Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node, Dictionary &r_dict, Node *p_node) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_gltf_node, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_gltf_node.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_export_node, p_state, p_gltf_node, r_dict, p_node, err); return err; } Error GLTFDocumentExtension::export_post(Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); Error err = OK; GDVIRTUAL_CALL(_export_post, p_state, err); return err; diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp index 64117349e0..cde30bce18 100644 --- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp +++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp @@ -45,7 +45,7 @@ void GLTFDocumentExtensionConvertImporterMesh::_copy_meta(Object *p_src_object, Error GLTFDocumentExtensionConvertImporterMesh::import_post(Ref<GLTFState> p_state, Node *p_root) { ERR_FAIL_NULL_V(p_root, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); List<Node *> queue; queue.push_back(p_root); List<Node *> delete_queue; diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp index 0340eb11b5..0f2246ce18 100644 --- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp @@ -229,7 +229,7 @@ Ref<GLTFPhysicsShape> GLTFPhysicsShape::from_resource(const Ref<Shape3D> &p_shap } Ref<Shape3D> GLTFPhysicsShape::to_resource(bool p_cache_shapes) { - if (!p_cache_shapes || _shape_cache == nullptr) { + if (!p_cache_shapes || _shape_cache.is_null()) { if (shape_type == "box") { Ref<BoxShape3D> box; box.instantiate(); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 69973a34dd..4653df7afe 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -299,8 +299,8 @@ Error GLTFDocument::_parse_json(const String &p_path, Ref<GLTFState> p_state) { } Error GLTFDocument::_parse_glb(Ref<FileAccess> p_file, Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_file, ERR_INVALID_PARAMETER); - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_file.is_null(), ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); ERR_FAIL_COND_V(p_file->get_position() != 0, ERR_FILE_CANT_READ); uint32_t magic = p_file->get_32(); ERR_FAIL_COND_V(magic != 0x46546C67, ERR_FILE_UNRECOGNIZED); //glTF @@ -3282,7 +3282,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { const int material = p["material"]; ERR_FAIL_INDEX_V(material, p_state->materials.size(), ERR_FILE_CORRUPT); Ref<Material> mat3d = p_state->materials[material]; - ERR_FAIL_NULL_V(mat3d, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(mat3d.is_null(), ERR_FILE_CORRUPT); Ref<BaseMaterial3D> base_material = mat3d; if (has_vertex_color && base_material.is_valid()) { @@ -3298,7 +3298,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { } mat = mat3d; } - ERR_FAIL_NULL_V(mat, ERR_FILE_CORRUPT); + ERR_FAIL_COND_V(mat.is_null(), ERR_FILE_CORRUPT); mat_name = mat->get_name(); } import_mesh->add_surface(primitive, array, morphs, @@ -3601,7 +3601,7 @@ void GLTFDocument::_parse_image_save_image(Ref<GLTFState> p_state, const Vector< } Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_path) { - ERR_FAIL_NULL_V(p_state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(p_state.is_null(), ERR_INVALID_PARAMETER); if (!p_state->json.has("images")) { return OK; } @@ -6967,14 +6967,14 @@ Dictionary _serialize_texture_transform_uv(Vector2 p_offset, Vector2 p_scale) { } Dictionary GLTFDocument::_serialize_texture_transform_uv1(Ref<BaseMaterial3D> p_material) { - ERR_FAIL_NULL_V(p_material, Dictionary()); + ERR_FAIL_COND_V(p_material.is_null(), Dictionary()); Vector3 offset = p_material->get_uv1_offset(); Vector3 scale = p_material->get_uv1_scale(); return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y)); } Dictionary GLTFDocument::_serialize_texture_transform_uv2(Ref<BaseMaterial3D> p_material) { - ERR_FAIL_NULL_V(p_material, Dictionary()); + ERR_FAIL_COND_V(p_material.is_null(), Dictionary()); Vector3 offset = p_material->get_uv2_offset(); Vector3 scale = p_material->get_uv2_scale(); return _serialize_texture_transform_uv(Vector2(offset.x, offset.y), Vector2(scale.x, scale.y)); @@ -7345,7 +7345,7 @@ Error GLTFDocument::_parse_gltf_state(Ref<GLTFState> p_state, const String &p_se PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) { Ref<GLTFState> state = p_state; - ERR_FAIL_NULL_V(state, PackedByteArray()); + ERR_FAIL_COND_V(state.is_null(), PackedByteArray()); // For buffers, set the state filename to an empty string, but // don't touch the base path, in case the user set it manually. state->filename = ""; @@ -7357,7 +7357,7 @@ PackedByteArray GLTFDocument::generate_buffer(Ref<GLTFState> p_state) { Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_path) { Ref<GLTFState> state = p_state; - ERR_FAIL_NULL_V(state, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(state.is_null(), ERR_INVALID_PARAMETER); state->base_path = p_path.get_base_dir(); state->filename = p_path.get_file(); Error err = _serialize(state); @@ -7373,7 +7373,7 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_ Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) { Ref<GLTFState> state = p_state; - ERR_FAIL_NULL_V(state, nullptr); + ERR_FAIL_COND_V(state.is_null(), nullptr); ERR_FAIL_INDEX_V(0, state->root_nodes.size(), nullptr); Error err = OK; p_state->set_bake_fps(p_bake_fps); @@ -7491,7 +7491,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint Error err; Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ, &err); ERR_FAIL_COND_V(err != OK, ERR_FILE_CANT_OPEN); - ERR_FAIL_NULL_V(file, ERR_FILE_CANT_OPEN); + ERR_FAIL_COND_V(file.is_null(), ERR_FILE_CANT_OPEN); String base_path = p_base_path; if (base_path.is_empty()) { base_path = p_path.get_base_dir(); @@ -7508,7 +7508,7 @@ Error GLTFDocument::append_from_file(String p_path, Ref<GLTFState> p_state, uint } Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) { - ERR_FAIL_NULL_V(p_state, ERR_PARSE_ERROR); + ERR_FAIL_COND_V(p_state.is_null(), ERR_PARSE_ERROR); if (p_state->json.has("extensionsUsed")) { Vector<String> ext_array = p_state->json["extensionsUsed"]; p_state->extensions_used = ext_array; diff --git a/modules/interactive_music/audio_stream_interactive.cpp b/modules/interactive_music/audio_stream_interactive.cpp index 8472c4e352..8656be988d 100644 --- a/modules/interactive_music/audio_stream_interactive.cpp +++ b/modules/interactive_music/audio_stream_interactive.cpp @@ -777,7 +777,7 @@ void AudioStreamPlaybackInteractive::_queue(int p_to_clip_index, bool p_is_auto_ if (stream->clips[p_to_clip_index].auto_advance == AudioStreamInteractive::AUTO_ADVANCE_ENABLED) { int next_clip = stream->clips[p_to_clip_index].auto_advance_next_clip; - if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && next_clip != playback_current && (!transition.use_filler_clip || next_clip != transition.filler_clip)) { + if (next_clip >= 0 && next_clip < (int)stream->clip_count && states[next_clip].playback.is_valid() && next_clip != p_to_clip_index && (!transition.use_filler_clip || next_clip != transition.filler_clip)) { auto_advance_to = next_clip; } } @@ -905,7 +905,9 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_ // time to start! from_frame = state.fade_wait * mix_rate; state.fade_wait = 0; - queue_next = state.auto_advance; + if (state.fade_speed == 0.0) { + queue_next = state.auto_advance; + } playback_current = p_state_idx; state.first_mix = false; } else { @@ -919,7 +921,6 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_ state.playback->mix(temp_buffer + from_frame, 1.0, p_frames - from_frame); double frame_fade_inc = state.fade_speed * frame_inc; - for (int i = from_frame; i < p_frames; i++) { if (state.fade_wait) { // This is for fade out of existing stream; @@ -933,6 +934,7 @@ void AudioStreamPlaybackInteractive::_mix_internal_state(int p_state_idx, int p_ state.fade_speed = 0.0; frame_fade_inc = 0.0; state.fade_volume = 1.0; + queue_next = state.auto_advance; } } else if (frame_fade_inc < 0.0) { state.fade_volume += frame_fade_inc; diff --git a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml index e8f8e7b760..17448724d1 100644 --- a/modules/interactive_music/doc_classes/AudioStreamInteractive.xml +++ b/modules/interactive_music/doc_classes/AudioStreamInteractive.xml @@ -4,7 +4,7 @@ Audio stream that can playback music interactively, combining clips and a transition table. </brief_description> <description> - This is an audio stream that can playback music interactively, combining clips and a transition table. Clips must be added first, and the transition rules via the [method add_transition]. Additionally, this stream export a property parameter to control the playback via [AudioStreamPlayer], [AudioStreamPlayer2D], or [AudioStreamPlayer3D]. + This is an audio stream that can playback music interactively, combining clips and a transition table. Clips must be added first, and then the transition rules via the [method add_transition]. Additionally, this stream exports a property parameter to control the playback via [AudioStreamPlayer], [AudioStreamPlayer2D], or [AudioStreamPlayer3D]. The way this is used is by filling a number of clips, then configuring the transition table. From there, clips are selected for playback and the music will smoothly go from the current to the new one while using the corresponding transition rule defined in the transition table. </description> <tutorials> diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 47a5b23895..8ba6f9e2ba 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -44,6 +44,9 @@ #if defined(VULKAN_ENABLED) #include "drivers/vulkan/rendering_context_driver_vulkan.h" #endif +#if defined(METAL_ENABLED) +#include "drivers/metal/rendering_context_driver_metal.h" +#endif //uncomment this if you want to see textures from all the process saved //#define DEBUG_TEXTURES @@ -790,6 +793,35 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref<RDShade return BAKE_OK; } +LightmapperRD::BakeError LightmapperRD::_pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices) { + Vector<RD::Uniform> uniforms = dilate_or_denoise_common_uniforms(source_light_tex, dest_light_tex); + + RID compute_shader_pack = rd->shader_create_from_spirv(compute_shader->get_spirv_stages("pack_coeffs")); + ERR_FAIL_COND_V(compute_shader_pack.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES); //internal check, should not happen + RID compute_shader_pack_pipeline = rd->compute_pipeline_create(compute_shader_pack); + + RID dilate_uniform_set = rd->uniform_set_create(uniforms, compute_shader_pack, 1); + + RD::ComputeListID compute_list = rd->compute_list_begin(); + rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_pack_pipeline); + rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); + rd->compute_list_bind_uniform_set(compute_list, dilate_uniform_set, 1); + push_constant.region_ofs[0] = 0; + push_constant.region_ofs[1] = 0; + Vector3i group_size(Math::division_round_up(atlas_size.x, 8), Math::division_round_up(atlas_size.y, 8), 1); //restore group size + + for (int i = 0; i < atlas_slices; i++) { + push_constant.atlas_slice = i; + rd->compute_list_set_push_constant(compute_list, &push_constant, sizeof(PushConstant)); + rd->compute_list_dispatch(compute_list, group_size.x, group_size.y, group_size.z); + //no barrier, let them run all together + } + rd->compute_list_end(); + rd->free(compute_shader_pack); + + return BAKE_OK; +} + Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) { Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index); Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data); @@ -1043,10 +1075,16 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RenderingDevice *rd = RenderingServer::get_singleton()->create_local_rendering_device(); if (rd == nullptr) { #if defined(RD_ENABLED) -#if defined(VULKAN_ENABLED) - rcd = memnew(RenderingContextDriverVulkan); +#if defined(METAL_ENABLED) + rcd = memnew(RenderingContextDriverMetal); rd = memnew(RenderingDevice); #endif +#if defined(VULKAN_ENABLED) + if (rcd == nullptr) { + rcd = memnew(RenderingContextDriverVulkan); + rd = memnew(RenderingDevice); + } +#endif #endif if (rcd != nullptr && rd != nullptr) { err = rcd->initialize(); @@ -1993,6 +2031,14 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d } } + if (p_bake_sh) { + SWAP(light_accum_tex, light_accum_tex2); + BakeError error = _pack_l1(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices); + if (unlikely(error != BAKE_OK)) { + return error; + } + } + #ifdef DEBUG_TEXTURES for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 59c2d52e69..f43da39670 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -273,6 +273,7 @@ class LightmapperRD : public Lightmapper { BakeError _dilate(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices); BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata); + BakeError _pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices); Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name); Ref<Image> _read_pfm(const String &p_name); diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index 88fc316679..2c85fff6f3 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -6,6 +6,7 @@ dilate = "#define MODE_DILATE"; unocclude = "#define MODE_UNOCCLUDE"; light_probes = "#define MODE_LIGHT_PROBES"; denoise = "#define MODE_DENOISE"; +pack_coeffs = "#define MODE_PACK_L1_COEFFS"; #[compute] @@ -63,7 +64,7 @@ layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light; layout(set = 1, binding = 5) uniform texture2D environment; #endif -#if defined(MODE_DILATE) || defined(MODE_DENOISE) +#if defined(MODE_DILATE) || defined(MODE_DENOISE) || defined(MODE_PACK_L1_COEFFS) layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2DArray dest_light; layout(set = 1, binding = 1) uniform texture2DArray source_light; #endif @@ -1037,4 +1038,28 @@ void main() { imageStore(dest_light, ivec3(atlas_pos, lightmap_slice), vec4(denoised_rgb, input_light.a)); } #endif + +#ifdef MODE_PACK_L1_COEFFS + vec4 base_coeff = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4), 0); + + for (int i = 1; i < 4; i++) { + vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice * 4 + i), 0); + + if (abs(base_coeff.r) > 0.0) { + c.r /= (base_coeff.r * 8); + } + + if (abs(base_coeff.g) > 0.0) { + c.g /= (base_coeff.g * 8); + } + + if (abs(base_coeff.b) > 0.0) { + c.b /= (base_coeff.b * 8); + } + + c.rgb += vec3(0.5); + c.rgb = clamp(c.rgb, vec3(0.0), vec3(1.0)); + imageStore(dest_light, ivec3(atlas_pos, params.atlas_slice * 4 + i), c); + } +#endif } diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py index efbf298f99..9f88b0575e 100755 --- a/modules/mono/build_scripts/build_assemblies.py +++ b/modules/mono/build_scripts/build_assemblies.py @@ -194,7 +194,7 @@ def run_msbuild(tools: ToolsLocation, sln: str, chdir_to: str, msbuild_args: Opt return subprocess.call(args, env=msbuild_env, cwd=chdir_to) -def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision): +def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated): target_filenames = [ "GodotSharp.dll", "GodotSharp.pdb", @@ -217,6 +217,8 @@ def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, pre args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] if precision == "double": args += ["/p:GodotFloat64=true"] + if no_deprecated: + args += ["/p:GodotNoDeprecated=true"] sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args) @@ -336,12 +338,14 @@ def generate_sdk_package_versions(): f.write(constants) -def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision): +def build_all( + msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local, precision, no_deprecated +): # Generate SdkPackageVersions.props and VersionDocsUrl constant generate_sdk_package_versions() # Godot API - exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision) + exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local, precision, no_deprecated) if exit_code != 0: return exit_code @@ -364,6 +368,8 @@ def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, p args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] if precision == "double": args += ["/p:GodotFloat64=true"] + if no_deprecated: + args += ["/p:GodotNoDeprecated=true"] sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln") exit_code = run_msbuild(msbuild_tool, sln=sln, chdir_to=module_dir, msbuild_args=args) if exit_code != 0: @@ -390,6 +396,12 @@ def main(): parser.add_argument( "--precision", type=str, default="single", choices=["single", "double"], help="Floating-point precision level" ) + parser.add_argument( + "--no-deprecated", + action="store_true", + default=False, + help="Build GodotSharp without using deprecated features. This is required, if the engine was built with 'deprecated=no'.", + ) args = parser.parse_args() @@ -414,6 +426,7 @@ def main(): args.dev_debug, push_nupkgs_local, args.precision, + args.no_deprecated, ) sys.exit(exit_code) diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 6d561c1566..177859f270 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -2796,7 +2796,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const if (GDMonoCache::godot_api_cache_updated) { GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &scr); - ERR_FAIL_NULL_V_MSG(scr, Ref<Resource>(), "Could not create C# script '" + real_path + "'."); + ERR_FAIL_COND_V_MSG(scr.is_null(), Ref<Resource>(), "Could not create C# script '" + real_path + "'."); } else { scr = Ref<CSharpScript>(memnew(CSharpScript)); } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs index 6a3884dabf..c734dc7be1 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedFields_ScriptProperties.generated.cs @@ -807,7 +807,7 @@ partial class ExportedFields properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@_fieldRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@_fieldGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@_fieldGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)31, name: PropertyName.@_fieldEmptyInt64Array, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); return properties; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs index 3c48740773..0de840aa34 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Tests/TestData/GeneratedSources/ExportedProperties_ScriptProperties.generated.cs @@ -925,7 +925,7 @@ partial class ExportedProperties properties.Add(new(type: (global::Godot.Variant.Type)23, name: PropertyName.@PropertyRid, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotArray, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); - properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)0, hintString: "", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); + properties.Add(new(type: (global::Godot.Variant.Type)27, name: PropertyName.@PropertyGodotGenericDictionary, hint: (global::Godot.PropertyHint)38, hintString: "4/0:;1/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); properties.Add(new(type: (global::Godot.Variant.Type)28, name: PropertyName.@PropertyGodotGenericArray, hint: (global::Godot.PropertyHint)23, hintString: "2/0:", usage: (global::Godot.PropertyUsageFlags)4102, exported: true)); return properties; } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs index 4cf6a9f431..bb4c4824e7 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs @@ -88,7 +88,8 @@ namespace Godot.SourceGenerators HideQuaternionEdit = 35, Password = 36, LayersAvoidance = 37, - Max = 38 + DictionaryType = 38, + Max = 39 } [Flags] diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs index d272832950..f5f51722b4 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -274,6 +274,14 @@ namespace Godot.SourceGenerators return null; } + public static ITypeSymbol[]? GetGenericElementTypes(ITypeSymbol typeSymbol) + { + if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType) + return genericType.TypeArguments.ToArray(); + + return null; + } + private static StringBuilder Append(this StringBuilder source, string a, string b) => source.Append(a).Append(b); diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs index 29bae6413d..0f86b3b91c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; @@ -728,8 +729,72 @@ namespace Godot.SourceGenerators if (!isTypeArgument && variantType == VariantType.Dictionary) { - // TODO: Dictionaries are not supported in the inspector - return false; + var elementTypes = MarshalUtils.GetGenericElementTypes(type); + + if (elementTypes == null) + return false; // Non-generic Dictionary, so there's no hint to add + Debug.Assert(elementTypes.Length == 2); + + var keyElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[0], typeCache)!.Value; + var keyElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(keyElementMarshalType)!.Value; + var keyIsPresetHint = false; + var keyHintString = (string?)null; + + if (keyElementVariantType == VariantType.String || keyElementVariantType == VariantType.StringName) + keyIsPresetHint = GetStringArrayEnumHint(keyElementVariantType, exportAttr, out keyHintString); + + if (!keyIsPresetHint) + { + bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[0], + exportAttr, keyElementVariantType, isTypeArgument: true, + out var keyElementHint, out var keyElementHintString); + + // Format: type/hint:hint_string + if (hintRes) + { + keyHintString = (int)keyElementVariantType + "/" + (int)keyElementHint + ":"; + + if (keyElementHintString != null) + keyHintString += keyElementHintString; + } + else + { + keyHintString = (int)keyElementVariantType + "/" + (int)PropertyHint.None + ":"; + } + } + + var valueElementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementTypes[1], typeCache)!.Value; + var valueElementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(valueElementMarshalType)!.Value; + var valueIsPresetHint = false; + var valueHintString = (string?)null; + + if (valueElementVariantType == VariantType.String || valueElementVariantType == VariantType.StringName) + valueIsPresetHint = GetStringArrayEnumHint(valueElementVariantType, exportAttr, out valueHintString); + + if (!valueIsPresetHint) + { + bool hintRes = TryGetMemberExportHint(typeCache, elementTypes[1], + exportAttr, valueElementVariantType, isTypeArgument: true, + out var valueElementHint, out var valueElementHintString); + + // Format: type/hint:hint_string + if (hintRes) + { + valueHintString = (int)valueElementVariantType + "/" + (int)valueElementHint + ":"; + + if (valueElementHintString != null) + valueHintString += valueElementHintString; + } + else + { + valueHintString = (int)valueElementVariantType + "/" + (int)PropertyHint.None + ":"; + } + } + + hint = PropertyHint.DictionaryType; + + hintString = keyHintString != null && valueHintString != null ? $"{keyHintString};{valueHintString}" : null; + return hintString != null; } return false; diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index b26f6d1bbf..2ec073e4fa 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -3809,6 +3809,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) { imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic"; imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string)); + } else if (return_info.type == Variant::DICTIONARY && return_info.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic"; + Vector<String> split = return_info.hint_string.split(";"); + imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(0))); + imethod.return_type.generic_type_parameters.push_back(TypeReference(split.get(1))); } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { imethod.return_type.cname = return_info.hint_string; } else if (return_info.type == Variant::NIL && return_info.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { @@ -3836,6 +3841,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); + } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; + Vector<String> split = arginfo.hint_string.split(";"); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0))); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1))); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; } else if (arginfo.type == Variant::NIL) { @@ -3963,6 +3973,11 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); + } else if (arginfo.type == Variant::DICTIONARY && arginfo.hint == PROPERTY_HINT_DICTIONARY_TYPE) { + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; + Vector<String> split = arginfo.hint_string.split(";"); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(0))); + iarg.type.generic_type_parameters.push_back(TypeReference(split.get(1))); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; } else if (arginfo.type == Variant::NIL) { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index b838f8eac7..3a3134d160 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -135,7 +135,7 @@ <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <!-- Compat Sources --> - <ItemGroup> + <ItemGroup Condition=" '$(GodotNoDeprecated)' == '' "> <Compile Include="Compat.cs" /> </ItemGroup> <!-- diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index 65b4824f94..715c1a4d51 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -36,7 +36,7 @@ </ProjectReference> </ItemGroup> <!-- Compat Sources --> - <ItemGroup> + <ItemGroup Condition=" '$(GodotNoDeprecated)' == '' "> <Compile Include="Compat.cs" /> </ItemGroup> <!-- diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 80e44011be..3935854a29 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -194,7 +194,7 @@ private: if (!has_data) { // 3. Extract the data to a temporary location to load from there. Ref<DirAccess> da = DirAccess::create_for_path(packed_path); - ERR_FAIL_NULL(da); + ERR_FAIL_COND(da.is_null()); ERR_FAIL_COND(da->copy_dir(packed_path, data_dir_root) != OK); } api_assemblies_dir = data_dir_root; diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp index d5d4b465d8..212fd1ef6b 100644 --- a/modules/multiplayer/editor/editor_network_profiler.cpp +++ b/modules/multiplayer/editor/editor_network_profiler.cpp @@ -227,10 +227,10 @@ void EditorNetworkProfiler::add_sync_frame_data(const SyncInfo &p_frame) { sync_data[p_frame.synchronizer].outgoing_syncs += p_frame.outgoing_syncs; } SyncInfo &info = sync_data[p_frame.synchronizer]; - if (info.incoming_syncs) { + if (p_frame.incoming_syncs) { info.incoming_size = p_frame.incoming_size / p_frame.incoming_syncs; } - if (info.outgoing_syncs) { + if (p_frame.outgoing_syncs) { info.outgoing_size = p_frame.outgoing_size / p_frame.outgoing_syncs; } } diff --git a/modules/multiplayer/scene_rpc_interface.cpp b/modules/multiplayer/scene_rpc_interface.cpp index 1463598ddc..592bb18a71 100644 --- a/modules/multiplayer/scene_rpc_interface.cpp +++ b/modules/multiplayer/scene_rpc_interface.cpp @@ -82,7 +82,7 @@ void SceneRPCInterface::_parse_rpc_config(const Variant &p_config, bool p_for_no Array names = config.keys(); names.sort(); // Ensure ID order for (int i = 0; i < names.size(); i++) { - ERR_CONTINUE(names[i].get_type() != Variant::STRING && names[i].get_type() != Variant::STRING_NAME); + ERR_CONTINUE(!names[i].is_string()); String name = names[i].operator String(); ERR_CONTINUE(config[name].get_type() != Variant::DICTIONARY); ERR_CONTINUE(!config[name].operator Dictionary().has("rpc_mode")); diff --git a/modules/navigation/3d/nav_mesh_queries_3d.cpp b/modules/navigation/3d/nav_mesh_queries_3d.cpp new file mode 100644 index 0000000000..70207f86ce --- /dev/null +++ b/modules/navigation/3d/nav_mesh_queries_3d.cpp @@ -0,0 +1,715 @@ +/**************************************************************************/ +/* nav_mesh_queries_3d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef _3D_DISABLED + +#include "nav_mesh_queries_3d.h" + +#include "../nav_base.h" + +#include "core/math/geometry_3d.h" + +#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a))) + +#define APPEND_METADATA(poly) \ + if (r_path_types) { \ + r_path_types->push_back(poly->owner->get_type()); \ + } \ + if (r_path_rids) { \ + r_path_rids->push_back(poly->owner->get_self()); \ + } \ + if (r_path_owners) { \ + r_path_owners->push_back(poly->owner->get_owner_id()); \ + } + +Vector3 NavMeshQueries3D::polygons_get_random_point(const LocalVector<gd::Polygon> &p_polygons, uint32_t p_navigation_layers, bool p_uniformly) { + const LocalVector<gd::Polygon> ®ion_polygons = p_polygons; + + if (region_polygons.is_empty()) { + return Vector3(); + } + + if (p_uniformly) { + real_t accumulated_area = 0; + RBMap<real_t, uint32_t> region_area_map; + + for (uint32_t rp_index = 0; rp_index < region_polygons.size(); rp_index++) { + const gd::Polygon ®ion_polygon = region_polygons[rp_index]; + real_t polyon_area = region_polygon.surface_area; + + if (polyon_area == 0.0) { + continue; + } + region_area_map[accumulated_area] = rp_index; + accumulated_area += polyon_area; + } + if (region_area_map.is_empty() || accumulated_area == 0) { + // All polygons have no real surface / no area. + return Vector3(); + } + + real_t region_area_map_pos = Math::random(real_t(0), accumulated_area); + + RBMap<real_t, uint32_t>::Iterator region_E = region_area_map.find_closest(region_area_map_pos); + ERR_FAIL_COND_V(!region_E, Vector3()); + uint32_t rrp_polygon_index = region_E->value; + ERR_FAIL_UNSIGNED_INDEX_V(rrp_polygon_index, region_polygons.size(), Vector3()); + + const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index]; + + real_t accumulated_polygon_area = 0; + RBMap<real_t, uint32_t> polygon_area_map; + + for (uint32_t rpp_index = 2; rpp_index < rr_polygon.points.size(); rpp_index++) { + real_t face_area = Face3(rr_polygon.points[0].pos, rr_polygon.points[rpp_index - 1].pos, rr_polygon.points[rpp_index].pos).get_area(); + + if (face_area == 0.0) { + continue; + } + polygon_area_map[accumulated_polygon_area] = rpp_index; + accumulated_polygon_area += face_area; + } + if (polygon_area_map.is_empty() || accumulated_polygon_area == 0) { + // All faces have no real surface / no area. + return Vector3(); + } + + real_t polygon_area_map_pos = Math::random(real_t(0), accumulated_polygon_area); + + RBMap<real_t, uint32_t>::Iterator polygon_E = polygon_area_map.find_closest(polygon_area_map_pos); + ERR_FAIL_COND_V(!polygon_E, Vector3()); + uint32_t rrp_face_index = polygon_E->value; + ERR_FAIL_UNSIGNED_INDEX_V(rrp_face_index, rr_polygon.points.size(), Vector3()); + + const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos); + + Vector3 face_random_position = face.get_random_point_inside(); + return face_random_position; + + } else { + uint32_t rrp_polygon_index = Math::random(int(0), region_polygons.size() - 1); + + const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index]; + + uint32_t rrp_face_index = Math::random(int(2), rr_polygon.points.size() - 1); + + const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos); + + Vector3 face_random_position = face.get_random_point_inside(); + return face_random_position; + } +} + +Vector<Vector3> NavMeshQueries3D::polygons_get_path(const LocalVector<gd::Polygon> &p_polygons, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up, uint32_t p_link_polygons_size) { + // Clear metadata outputs. + if (r_path_types) { + r_path_types->clear(); + } + if (r_path_rids) { + r_path_rids->clear(); + } + if (r_path_owners) { + r_path_owners->clear(); + } + + // Find the start poly and the end poly on this map. + const gd::Polygon *begin_poly = nullptr; + const gd::Polygon *end_poly = nullptr; + Vector3 begin_point; + Vector3 end_point; + real_t begin_d = FLT_MAX; + real_t end_d = FLT_MAX; + // Find the initial poly and the end poly on this map. + for (const gd::Polygon &p : p_polygons) { + // Only consider the polygon if it in a region with compatible layers. + if ((p_navigation_layers & p.owner->get_navigation_layers()) == 0) { + continue; + } + + // For each face check the distance between the origin/destination + for (size_t point_id = 2; point_id < p.points.size(); point_id++) { + const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); + + Vector3 point = face.get_closest_point_to(p_origin); + real_t distance_to_point = point.distance_to(p_origin); + if (distance_to_point < begin_d) { + begin_d = distance_to_point; + begin_poly = &p; + begin_point = point; + } + + point = face.get_closest_point_to(p_destination); + distance_to_point = point.distance_to(p_destination); + if (distance_to_point < end_d) { + end_d = distance_to_point; + end_poly = &p; + end_point = point; + } + } + } + + // Check for trivial cases + if (!begin_poly || !end_poly) { + return Vector<Vector3>(); + } + if (begin_poly == end_poly) { + if (r_path_types) { + r_path_types->resize(2); + r_path_types->write[0] = begin_poly->owner->get_type(); + r_path_types->write[1] = end_poly->owner->get_type(); + } + + if (r_path_rids) { + r_path_rids->resize(2); + (*r_path_rids)[0] = begin_poly->owner->get_self(); + (*r_path_rids)[1] = end_poly->owner->get_self(); + } + + if (r_path_owners) { + r_path_owners->resize(2); + r_path_owners->write[0] = begin_poly->owner->get_owner_id(); + r_path_owners->write[1] = end_poly->owner->get_owner_id(); + } + + Vector<Vector3> path; + path.resize(2); + path.write[0] = begin_point; + path.write[1] = end_point; + return path; + } + + // List of all reachable navigation polys. + LocalVector<gd::NavigationPoly> navigation_polys; + navigation_polys.resize(p_polygons.size() + p_link_polygons_size); + + // Initialize the matching navigation polygon. + gd::NavigationPoly &begin_navigation_poly = navigation_polys[begin_poly->id]; + begin_navigation_poly.poly = begin_poly; + begin_navigation_poly.entry = begin_point; + begin_navigation_poly.back_navigation_edge_pathway_start = begin_point; + begin_navigation_poly.back_navigation_edge_pathway_end = begin_point; + + // Heap of polygons to travel next. + gd::Heap<gd::NavigationPoly *, gd::NavPolyTravelCostGreaterThan, gd::NavPolyHeapIndexer> + traversable_polys; + traversable_polys.reserve(p_polygons.size() * 0.25); + + // This is an implementation of the A* algorithm. + int least_cost_id = begin_poly->id; + int prev_least_cost_id = -1; + bool found_route = false; + + const gd::Polygon *reachable_end = nullptr; + real_t distance_to_reachable_end = FLT_MAX; + bool is_reachable = true; + + while (true) { + // Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance. + for (const gd::Edge &edge : navigation_polys[least_cost_id].poly->edges) { + // Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon. + for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) { + const gd::Edge::Connection &connection = edge.connections[connection_index]; + + // Only consider the connection to another polygon if this polygon is in a region with compatible layers. + if ((p_navigation_layers & connection.polygon->owner->get_navigation_layers()) == 0) { + continue; + } + + const gd::NavigationPoly &least_cost_poly = navigation_polys[least_cost_id]; + real_t poly_enter_cost = 0.0; + real_t poly_travel_cost = least_cost_poly.poly->owner->get_travel_cost(); + + if (prev_least_cost_id != -1 && navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self()) { + poly_enter_cost = least_cost_poly.poly->owner->get_enter_cost(); + } + prev_least_cost_id = least_cost_id; + + Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end }; + const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway); + const real_t new_traveled_distance = least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost + poly_enter_cost + least_cost_poly.traveled_distance; + + // Check if the neighbor polygon has already been processed. + gd::NavigationPoly &neighbor_poly = navigation_polys[connection.polygon->id]; + if (neighbor_poly.poly != nullptr) { + // If the neighbor polygon hasn't been traversed yet and the new path leading to + // it is shorter, update the polygon. + if (neighbor_poly.traversable_poly_index < traversable_polys.size() && + new_traveled_distance < neighbor_poly.traveled_distance) { + neighbor_poly.back_navigation_poly_id = least_cost_id; + neighbor_poly.back_navigation_edge = connection.edge; + neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start; + neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end; + neighbor_poly.traveled_distance = new_traveled_distance; + neighbor_poly.distance_to_destination = + new_entry.distance_to(end_point) * + neighbor_poly.poly->owner->get_travel_cost(); + neighbor_poly.entry = new_entry; + + // Update the priority of the polygon in the heap. + traversable_polys.shift(neighbor_poly.traversable_poly_index); + } + } else { + // Initialize the matching navigation polygon. + neighbor_poly.poly = connection.polygon; + neighbor_poly.back_navigation_poly_id = least_cost_id; + neighbor_poly.back_navigation_edge = connection.edge; + neighbor_poly.back_navigation_edge_pathway_start = connection.pathway_start; + neighbor_poly.back_navigation_edge_pathway_end = connection.pathway_end; + neighbor_poly.traveled_distance = new_traveled_distance; + neighbor_poly.distance_to_destination = + new_entry.distance_to(end_point) * + neighbor_poly.poly->owner->get_travel_cost(); + neighbor_poly.entry = new_entry; + + // Add the polygon to the heap of polygons to traverse next. + traversable_polys.push(&neighbor_poly); + } + } + } + + // When the heap of traversable polygons is empty at this point it means the end polygon is + // unreachable. + if (traversable_polys.is_empty()) { + // Thus use the further reachable polygon + ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons"); + is_reachable = false; + if (reachable_end == nullptr) { + // The path is not found and there is not a way out. + break; + } + + // Set as end point the furthest reachable point. + end_poly = reachable_end; + end_d = FLT_MAX; + for (size_t point_id = 2; point_id < end_poly->points.size(); point_id++) { + Face3 f(end_poly->points[0].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos); + Vector3 spoint = f.get_closest_point_to(p_destination); + real_t dpoint = spoint.distance_to(p_destination); + if (dpoint < end_d) { + end_point = spoint; + end_d = dpoint; + } + } + + // Search all faces of start polygon as well. + bool closest_point_on_start_poly = false; + for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { + Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); + Vector3 spoint = f.get_closest_point_to(p_destination); + real_t dpoint = spoint.distance_to(p_destination); + if (dpoint < end_d) { + end_point = spoint; + end_d = dpoint; + closest_point_on_start_poly = true; + } + } + + if (closest_point_on_start_poly) { + // No point to run PostProcessing when start and end convex polygon is the same. + if (r_path_types) { + r_path_types->resize(2); + r_path_types->write[0] = begin_poly->owner->get_type(); + r_path_types->write[1] = begin_poly->owner->get_type(); + } + + if (r_path_rids) { + r_path_rids->resize(2); + (*r_path_rids)[0] = begin_poly->owner->get_self(); + (*r_path_rids)[1] = begin_poly->owner->get_self(); + } + + if (r_path_owners) { + r_path_owners->resize(2); + r_path_owners->write[0] = begin_poly->owner->get_owner_id(); + r_path_owners->write[1] = begin_poly->owner->get_owner_id(); + } + + Vector<Vector3> path; + path.resize(2); + path.write[0] = begin_point; + path.write[1] = end_point; + return path; + } + + for (gd::NavigationPoly &nav_poly : navigation_polys) { + nav_poly.poly = nullptr; + } + navigation_polys[begin_poly->id].poly = begin_poly; + + least_cost_id = begin_poly->id; + prev_least_cost_id = -1; + + reachable_end = nullptr; + + continue; + } + + // Pop the polygon with the lowest travel cost from the heap of traversable polygons. + least_cost_id = traversable_polys.pop()->poly->id; + + // Store the farthest reachable end polygon in case our goal is not reachable. + if (is_reachable) { + real_t distance = navigation_polys[least_cost_id].entry.distance_to(p_destination); + if (distance_to_reachable_end > distance) { + distance_to_reachable_end = distance; + reachable_end = navigation_polys[least_cost_id].poly; + } + } + + // Check if we reached the end + if (navigation_polys[least_cost_id].poly == end_poly) { + found_route = true; + break; + } + } + + // We did not find a route but we have both a start polygon and an end polygon at this point. + // Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon. + if (!found_route) { + end_d = FLT_MAX; + // Search all faces of the start polygon for the closest point to our target position. + for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { + Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); + Vector3 spoint = f.get_closest_point_to(p_destination); + real_t dpoint = spoint.distance_to(p_destination); + if (dpoint < end_d) { + end_point = spoint; + end_d = dpoint; + } + } + + if (r_path_types) { + r_path_types->resize(2); + r_path_types->write[0] = begin_poly->owner->get_type(); + r_path_types->write[1] = begin_poly->owner->get_type(); + } + + if (r_path_rids) { + r_path_rids->resize(2); + (*r_path_rids)[0] = begin_poly->owner->get_self(); + (*r_path_rids)[1] = begin_poly->owner->get_self(); + } + + if (r_path_owners) { + r_path_owners->resize(2); + r_path_owners->write[0] = begin_poly->owner->get_owner_id(); + r_path_owners->write[1] = begin_poly->owner->get_owner_id(); + } + + Vector<Vector3> path; + path.resize(2); + path.write[0] = begin_point; + path.write[1] = end_point; + return path; + } + + Vector<Vector3> path; + // Optimize the path. + if (p_optimize) { + // Set the apex poly/point to the end point + gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id]; + + Vector3 back_pathway[2] = { apex_poly->back_navigation_edge_pathway_start, apex_poly->back_navigation_edge_pathway_end }; + const Vector3 back_edge_closest_point = Geometry3D::get_closest_point_to_segment(end_point, back_pathway); + if (end_point.is_equal_approx(back_edge_closest_point)) { + // The end point is basically on top of the last crossed edge, funneling around the corners would at best do nothing. + // At worst it would add an unwanted path point before the last point due to precision issues so skip to the next polygon. + if (apex_poly->back_navigation_poly_id != -1) { + apex_poly = &navigation_polys[apex_poly->back_navigation_poly_id]; + } + } + + Vector3 apex_point = end_point; + + gd::NavigationPoly *left_poly = apex_poly; + Vector3 left_portal = apex_point; + gd::NavigationPoly *right_poly = apex_poly; + Vector3 right_portal = apex_point; + + gd::NavigationPoly *p = apex_poly; + + path.push_back(end_point); + APPEND_METADATA(end_poly); + + while (p) { + // Set left and right points of the pathway between polygons. + Vector3 left = p->back_navigation_edge_pathway_start; + Vector3 right = p->back_navigation_edge_pathway_end; + if (THREE_POINTS_CROSS_PRODUCT(apex_point, left, right).dot(p_map_up) < 0) { + SWAP(left, right); + } + + bool skip = false; + if (THREE_POINTS_CROSS_PRODUCT(apex_point, left_portal, left).dot(p_map_up) >= 0) { + //process + if (left_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, left, right_portal).dot(p_map_up) > 0) { + left_poly = p; + left_portal = left; + } else { + clip_path(navigation_polys, path, apex_poly, right_portal, right_poly, r_path_types, r_path_rids, r_path_owners, p_map_up); + + apex_point = right_portal; + p = right_poly; + left_poly = p; + apex_poly = p; + left_portal = apex_point; + right_portal = apex_point; + + path.push_back(apex_point); + APPEND_METADATA(apex_poly->poly); + skip = true; + } + } + + if (!skip && THREE_POINTS_CROSS_PRODUCT(apex_point, right_portal, right).dot(p_map_up) <= 0) { + //process + if (right_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, right, left_portal).dot(p_map_up) < 0) { + right_poly = p; + right_portal = right; + } else { + clip_path(navigation_polys, path, apex_poly, left_portal, left_poly, r_path_types, r_path_rids, r_path_owners, p_map_up); + + apex_point = left_portal; + p = left_poly; + right_poly = p; + apex_poly = p; + right_portal = apex_point; + left_portal = apex_point; + + path.push_back(apex_point); + APPEND_METADATA(apex_poly->poly); + } + } + + // Go to the previous polygon. + if (p->back_navigation_poly_id != -1) { + p = &navigation_polys[p->back_navigation_poly_id]; + } else { + // The end + p = nullptr; + } + } + + // If the last point is not the begin point, add it to the list. + if (path[path.size() - 1] != begin_point) { + path.push_back(begin_point); + APPEND_METADATA(begin_poly); + } + + path.reverse(); + if (r_path_types) { + r_path_types->reverse(); + } + if (r_path_rids) { + r_path_rids->reverse(); + } + if (r_path_owners) { + r_path_owners->reverse(); + } + + } else { + path.push_back(end_point); + APPEND_METADATA(end_poly); + + // Add mid points + int np_id = least_cost_id; + while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) { + if (navigation_polys[np_id].back_navigation_edge != -1) { + int prev = navigation_polys[np_id].back_navigation_edge; + int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size(); + Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5; + + path.push_back(point); + APPEND_METADATA(navigation_polys[np_id].poly); + } else { + path.push_back(navigation_polys[np_id].entry); + APPEND_METADATA(navigation_polys[np_id].poly); + } + + np_id = navigation_polys[np_id].back_navigation_poly_id; + } + + path.push_back(begin_point); + APPEND_METADATA(begin_poly); + + path.reverse(); + if (r_path_types) { + r_path_types->reverse(); + } + if (r_path_rids) { + r_path_rids->reverse(); + } + if (r_path_owners) { + r_path_owners->reverse(); + } + } + + // Ensure post conditions (path arrays MUST match in size). + CRASH_COND(r_path_types && path.size() != r_path_types->size()); + CRASH_COND(r_path_rids && path.size() != r_path_rids->size()); + CRASH_COND(r_path_owners && path.size() != r_path_owners->size()); + + return path; +} + +Vector3 NavMeshQueries3D::polygons_get_closest_point_to_segment(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) { + bool use_collision = p_use_collision; + Vector3 closest_point; + real_t closest_point_distance = FLT_MAX; + + for (const gd::Polygon &polygon : p_polygons) { + // For each face check the distance to the segment. + for (size_t point_id = 2; point_id < polygon.points.size(); point_id += 1) { + const Face3 face(polygon.points[0].pos, polygon.points[point_id - 1].pos, polygon.points[point_id].pos); + Vector3 intersection_point; + if (face.intersects_segment(p_from, p_to, &intersection_point)) { + const real_t d = p_from.distance_to(intersection_point); + if (!use_collision) { + closest_point = intersection_point; + use_collision = true; + closest_point_distance = d; + } else if (closest_point_distance > d) { + closest_point = intersection_point; + closest_point_distance = d; + } + } + // If segment does not itersect face, check the distance from segment's endpoints. + else if (!use_collision) { + const Vector3 p_from_closest = face.get_closest_point_to(p_from); + const real_t d_p_from = p_from.distance_to(p_from_closest); + if (closest_point_distance > d_p_from) { + closest_point = p_from_closest; + closest_point_distance = d_p_from; + } + + const Vector3 p_to_closest = face.get_closest_point_to(p_to); + const real_t d_p_to = p_to.distance_to(p_to_closest); + if (closest_point_distance > d_p_to) { + closest_point = p_to_closest; + closest_point_distance = d_p_to; + } + } + } + // Finally, check for a case when shortest distance is between some point located on a face's edge and some point located on a line segment. + if (!use_collision) { + for (size_t point_id = 0; point_id < polygon.points.size(); point_id += 1) { + Vector3 a, b; + + Geometry3D::get_closest_points_between_segments( + p_from, + p_to, + polygon.points[point_id].pos, + polygon.points[(point_id + 1) % polygon.points.size()].pos, + a, + b); + + const real_t d = a.distance_to(b); + if (d < closest_point_distance) { + closest_point_distance = d; + closest_point = b; + } + } + } + } + + return closest_point; +} + +Vector3 NavMeshQueries3D::polygons_get_closest_point(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) { + gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point); + return cp.point; +} + +Vector3 NavMeshQueries3D::polygons_get_closest_point_normal(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) { + gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point); + return cp.normal; +} + +gd::ClosestPointQueryResult NavMeshQueries3D::polygons_get_closest_point_info(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) { + gd::ClosestPointQueryResult result; + real_t closest_point_distance_squared = FLT_MAX; + + for (const gd::Polygon &polygon : p_polygons) { + for (size_t point_id = 2; point_id < polygon.points.size(); point_id += 1) { + const Face3 face(polygon.points[0].pos, polygon.points[point_id - 1].pos, polygon.points[point_id].pos); + const Vector3 closest_point_on_face = face.get_closest_point_to(p_point); + const real_t distance_squared_to_point = closest_point_on_face.distance_squared_to(p_point); + if (distance_squared_to_point < closest_point_distance_squared) { + result.point = closest_point_on_face; + result.normal = face.get_plane().normal; + result.owner = polygon.owner->get_self(); + closest_point_distance_squared = distance_squared_to_point; + } + } + } + + return result; +} + +RID NavMeshQueries3D::polygons_get_closest_point_owner(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point) { + gd::ClosestPointQueryResult cp = polygons_get_closest_point_info(p_polygons, p_point); + return cp.owner; +} + +void NavMeshQueries3D::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up) { + Vector3 from = path[path.size() - 1]; + + if (from.is_equal_approx(p_to_point)) { + return; + } + + Plane cut_plane; + cut_plane.normal = (from - p_to_point).cross(p_map_up); + if (cut_plane.normal == Vector3()) { + return; + } + cut_plane.normal.normalize(); + cut_plane.d = cut_plane.normal.dot(from); + + while (from_poly != p_to_poly) { + Vector3 pathway_start = from_poly->back_navigation_edge_pathway_start; + Vector3 pathway_end = from_poly->back_navigation_edge_pathway_end; + + ERR_FAIL_COND(from_poly->back_navigation_poly_id == -1); + from_poly = &p_navigation_polys[from_poly->back_navigation_poly_id]; + + if (!pathway_start.is_equal_approx(pathway_end)) { + Vector3 inters; + if (cut_plane.intersects_segment(pathway_start, pathway_end, &inters)) { + if (!inters.is_equal_approx(p_to_point) && !inters.is_equal_approx(path[path.size() - 1])) { + path.push_back(inters); + APPEND_METADATA(from_poly->poly); + } + } + } + } +} + +#endif // _3D_DISABLED diff --git a/modules/navigation/3d/nav_mesh_queries_3d.h b/modules/navigation/3d/nav_mesh_queries_3d.h new file mode 100644 index 0000000000..109bb2f971 --- /dev/null +++ b/modules/navigation/3d/nav_mesh_queries_3d.h @@ -0,0 +1,54 @@ +/**************************************************************************/ +/* nav_mesh_queries_3d.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 NAV_MESH_QUERIES_3D_H +#define NAV_MESH_QUERIES_3D_H + +#ifndef _3D_DISABLED + +#include "../nav_map.h" + +class NavMeshQueries3D { +public: + static Vector3 polygons_get_random_point(const LocalVector<gd::Polygon> &p_polygons, uint32_t p_navigation_layers, bool p_uniformly); + + static Vector<Vector3> polygons_get_path(const LocalVector<gd::Polygon> &p_polygons, Vector3 p_origin, Vector3 p_destination, bool p_optimize, uint32_t p_navigation_layers, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up, uint32_t p_link_polygons_size); + static Vector3 polygons_get_closest_point_to_segment(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision); + static Vector3 polygons_get_closest_point(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point); + static Vector3 polygons_get_closest_point_normal(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point); + static gd::ClosestPointQueryResult polygons_get_closest_point_info(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point); + static RID polygons_get_closest_point_owner(const LocalVector<gd::Polygon> &p_polygons, const Vector3 &p_point); + + static void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners, const Vector3 &p_map_up); +}; + +#endif // _3D_DISABLED + +#endif // NAV_MESH_QUERIES_3D_H diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index 0c91e8dea3..dd77e81b45 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -35,25 +35,13 @@ #include "nav_obstacle.h" #include "nav_region.h" +#include "3d/nav_mesh_queries_3d.h" + #include "core/config/project_settings.h" #include "core/object/worker_thread_pool.h" #include <Obstacle2d.h> -#define THREE_POINTS_CROSS_PRODUCT(m_a, m_b, m_c) (((m_c) - (m_a)).cross((m_b) - (m_a))) - -// Helper macro -#define APPEND_METADATA(poly) \ - if (r_path_types) { \ - r_path_types->push_back(poly->owner->get_type()); \ - } \ - if (r_path_rids) { \ - r_path_rids->push_back(poly->owner->get_self()); \ - } \ - if (r_path_owners) { \ - r_path_owners->push_back(poly->owner->get_owner_id()); \ - } - #ifdef DEBUG_ENABLED #define NAVMAP_ITERATION_ZERO_ERROR_MSG() \ ERR_PRINT_ONCE("NavigationServer navigation map query failed because it was made before first map synchronization.\n\ @@ -142,462 +130,9 @@ Vector<Vector3> NavMap::get_path(Vector3 p_origin, Vector3 p_destination, bool p return Vector<Vector3>(); } - // Clear metadata outputs. - if (r_path_types) { - r_path_types->clear(); - } - if (r_path_rids) { - r_path_rids->clear(); - } - if (r_path_owners) { - r_path_owners->clear(); - } - - // Find the start poly and the end poly on this map. - const gd::Polygon *begin_poly = nullptr; - const gd::Polygon *end_poly = nullptr; - Vector3 begin_point; - Vector3 end_point; - real_t begin_d = FLT_MAX; - real_t end_d = FLT_MAX; - // Find the initial poly and the end poly on this map. - for (const gd::Polygon &p : polygons) { - // Only consider the polygon if it in a region with compatible layers. - if ((p_navigation_layers & p.owner->get_navigation_layers()) == 0) { - continue; - } - - // For each face check the distance between the origin/destination - for (size_t point_id = 2; point_id < p.points.size(); point_id++) { - const Face3 face(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); - - Vector3 point = face.get_closest_point_to(p_origin); - real_t distance_to_point = point.distance_to(p_origin); - if (distance_to_point < begin_d) { - begin_d = distance_to_point; - begin_poly = &p; - begin_point = point; - } - - point = face.get_closest_point_to(p_destination); - distance_to_point = point.distance_to(p_destination); - if (distance_to_point < end_d) { - end_d = distance_to_point; - end_poly = &p; - end_point = point; - } - } - } - - // Check for trivial cases - if (!begin_poly || !end_poly) { - return Vector<Vector3>(); - } - if (begin_poly == end_poly) { - if (r_path_types) { - r_path_types->resize(2); - r_path_types->write[0] = begin_poly->owner->get_type(); - r_path_types->write[1] = end_poly->owner->get_type(); - } - - if (r_path_rids) { - r_path_rids->resize(2); - (*r_path_rids)[0] = begin_poly->owner->get_self(); - (*r_path_rids)[1] = end_poly->owner->get_self(); - } - - if (r_path_owners) { - r_path_owners->resize(2); - r_path_owners->write[0] = begin_poly->owner->get_owner_id(); - r_path_owners->write[1] = end_poly->owner->get_owner_id(); - } - - Vector<Vector3> path; - path.resize(2); - path.write[0] = begin_point; - path.write[1] = end_point; - return path; - } - - // List of all reachable navigation polys. - LocalVector<gd::NavigationPoly> navigation_polys; - navigation_polys.reserve(polygons.size() * 0.75); - - // Add the start polygon to the reachable navigation polygons. - gd::NavigationPoly begin_navigation_poly = gd::NavigationPoly(begin_poly); - begin_navigation_poly.self_id = 0; - begin_navigation_poly.entry = begin_point; - begin_navigation_poly.back_navigation_edge_pathway_start = begin_point; - begin_navigation_poly.back_navigation_edge_pathway_end = begin_point; - navigation_polys.push_back(begin_navigation_poly); - - // List of polygon IDs to visit. - List<uint32_t> to_visit; - to_visit.push_back(0); - - // This is an implementation of the A* algorithm. - int least_cost_id = 0; - int prev_least_cost_id = -1; - bool found_route = false; - - const gd::Polygon *reachable_end = nullptr; - real_t reachable_d = FLT_MAX; - bool is_reachable = true; - - while (true) { - // Takes the current least_cost_poly neighbors (iterating over its edges) and compute the traveled_distance. - for (const gd::Edge &edge : navigation_polys[least_cost_id].poly->edges) { - // Iterate over connections in this edge, then compute the new optimized travel distance assigned to this polygon. - for (int connection_index = 0; connection_index < edge.connections.size(); connection_index++) { - const gd::Edge::Connection &connection = edge.connections[connection_index]; - - // Only consider the connection to another polygon if this polygon is in a region with compatible layers. - if ((p_navigation_layers & connection.polygon->owner->get_navigation_layers()) == 0) { - continue; - } - - const gd::NavigationPoly &least_cost_poly = navigation_polys[least_cost_id]; - real_t poly_enter_cost = 0.0; - real_t poly_travel_cost = least_cost_poly.poly->owner->get_travel_cost(); - - if (prev_least_cost_id != -1 && (navigation_polys[prev_least_cost_id].poly->owner->get_self() != least_cost_poly.poly->owner->get_self())) { - poly_enter_cost = least_cost_poly.poly->owner->get_enter_cost(); - } - prev_least_cost_id = least_cost_id; - - Vector3 pathway[2] = { connection.pathway_start, connection.pathway_end }; - const Vector3 new_entry = Geometry3D::get_closest_point_to_segment(least_cost_poly.entry, pathway); - const real_t new_distance = (least_cost_poly.entry.distance_to(new_entry) * poly_travel_cost) + poly_enter_cost + least_cost_poly.traveled_distance; - - int64_t already_visited_polygon_index = navigation_polys.find(gd::NavigationPoly(connection.polygon)); - - if (already_visited_polygon_index != -1) { - // Polygon already visited, check if we can reduce the travel cost. - gd::NavigationPoly &avp = navigation_polys[already_visited_polygon_index]; - if (new_distance < avp.traveled_distance) { - avp.back_navigation_poly_id = least_cost_id; - avp.back_navigation_edge = connection.edge; - avp.back_navigation_edge_pathway_start = connection.pathway_start; - avp.back_navigation_edge_pathway_end = connection.pathway_end; - avp.traveled_distance = new_distance; - avp.entry = new_entry; - } - } else { - // Add the neighbor polygon to the reachable ones. - gd::NavigationPoly new_navigation_poly = gd::NavigationPoly(connection.polygon); - new_navigation_poly.self_id = navigation_polys.size(); - new_navigation_poly.back_navigation_poly_id = least_cost_id; - new_navigation_poly.back_navigation_edge = connection.edge; - new_navigation_poly.back_navigation_edge_pathway_start = connection.pathway_start; - new_navigation_poly.back_navigation_edge_pathway_end = connection.pathway_end; - new_navigation_poly.traveled_distance = new_distance; - new_navigation_poly.entry = new_entry; - navigation_polys.push_back(new_navigation_poly); - - // Add the neighbor polygon to the polygons to visit. - to_visit.push_back(navigation_polys.size() - 1); - } - } - } - - // Removes the least cost polygon from the list of polygons to visit so we can advance. - to_visit.erase(least_cost_id); - - // When the list of polygons to visit is empty at this point it means the End Polygon is not reachable - if (to_visit.size() == 0) { - // Thus use the further reachable polygon - ERR_BREAK_MSG(is_reachable == false, "It's not expect to not find the most reachable polygons"); - is_reachable = false; - if (reachable_end == nullptr) { - // The path is not found and there is not a way out. - break; - } - - // Set as end point the furthest reachable point. - end_poly = reachable_end; - end_d = FLT_MAX; - for (size_t point_id = 2; point_id < end_poly->points.size(); point_id++) { - Face3 f(end_poly->points[0].pos, end_poly->points[point_id - 1].pos, end_poly->points[point_id].pos); - Vector3 spoint = f.get_closest_point_to(p_destination); - real_t dpoint = spoint.distance_to(p_destination); - if (dpoint < end_d) { - end_point = spoint; - end_d = dpoint; - } - } - - // Search all faces of start polygon as well. - bool closest_point_on_start_poly = false; - for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { - Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); - Vector3 spoint = f.get_closest_point_to(p_destination); - real_t dpoint = spoint.distance_to(p_destination); - if (dpoint < end_d) { - end_point = spoint; - end_d = dpoint; - closest_point_on_start_poly = true; - } - } - - if (closest_point_on_start_poly) { - // No point to run PostProcessing when start and end convex polygon is the same. - if (r_path_types) { - r_path_types->resize(2); - r_path_types->write[0] = begin_poly->owner->get_type(); - r_path_types->write[1] = begin_poly->owner->get_type(); - } - - if (r_path_rids) { - r_path_rids->resize(2); - (*r_path_rids)[0] = begin_poly->owner->get_self(); - (*r_path_rids)[1] = begin_poly->owner->get_self(); - } - - if (r_path_owners) { - r_path_owners->resize(2); - r_path_owners->write[0] = begin_poly->owner->get_owner_id(); - r_path_owners->write[1] = begin_poly->owner->get_owner_id(); - } - - Vector<Vector3> path; - path.resize(2); - path.write[0] = begin_point; - path.write[1] = end_point; - return path; - } - - // Reset open and navigation_polys - gd::NavigationPoly np = navigation_polys[0]; - navigation_polys.clear(); - navigation_polys.push_back(np); - to_visit.clear(); - to_visit.push_back(0); - least_cost_id = 0; - prev_least_cost_id = -1; - - reachable_end = nullptr; - - continue; - } - - // Find the polygon with the minimum cost from the list of polygons to visit. - least_cost_id = -1; - real_t least_cost = FLT_MAX; - for (List<uint32_t>::Element *element = to_visit.front(); element != nullptr; element = element->next()) { - gd::NavigationPoly *np = &navigation_polys[element->get()]; - real_t cost = np->traveled_distance; - cost += (np->entry.distance_to(end_point) * np->poly->owner->get_travel_cost()); - if (cost < least_cost) { - least_cost_id = np->self_id; - least_cost = cost; - } - } - - ERR_BREAK(least_cost_id == -1); - - // Stores the further reachable end polygon, in case our goal is not reachable. - if (is_reachable) { - real_t d = navigation_polys[least_cost_id].entry.distance_to(p_destination); - if (reachable_d > d) { - reachable_d = d; - reachable_end = navigation_polys[least_cost_id].poly; - } - } - - // Check if we reached the end - if (navigation_polys[least_cost_id].poly == end_poly) { - found_route = true; - break; - } - } - - // We did not find a route but we have both a start polygon and an end polygon at this point. - // Usually this happens because there was not a single external or internal connected edge, e.g. our start polygon is an isolated, single convex polygon. - if (!found_route) { - end_d = FLT_MAX; - // Search all faces of the start polygon for the closest point to our target position. - for (size_t point_id = 2; point_id < begin_poly->points.size(); point_id++) { - Face3 f(begin_poly->points[0].pos, begin_poly->points[point_id - 1].pos, begin_poly->points[point_id].pos); - Vector3 spoint = f.get_closest_point_to(p_destination); - real_t dpoint = spoint.distance_to(p_destination); - if (dpoint < end_d) { - end_point = spoint; - end_d = dpoint; - } - } - - if (r_path_types) { - r_path_types->resize(2); - r_path_types->write[0] = begin_poly->owner->get_type(); - r_path_types->write[1] = begin_poly->owner->get_type(); - } - - if (r_path_rids) { - r_path_rids->resize(2); - (*r_path_rids)[0] = begin_poly->owner->get_self(); - (*r_path_rids)[1] = begin_poly->owner->get_self(); - } - - if (r_path_owners) { - r_path_owners->resize(2); - r_path_owners->write[0] = begin_poly->owner->get_owner_id(); - r_path_owners->write[1] = begin_poly->owner->get_owner_id(); - } - - Vector<Vector3> path; - path.resize(2); - path.write[0] = begin_point; - path.write[1] = end_point; - return path; - } - - Vector<Vector3> path; - // Optimize the path. - if (p_optimize) { - // Set the apex poly/point to the end point - gd::NavigationPoly *apex_poly = &navigation_polys[least_cost_id]; - - Vector3 back_pathway[2] = { apex_poly->back_navigation_edge_pathway_start, apex_poly->back_navigation_edge_pathway_end }; - const Vector3 back_edge_closest_point = Geometry3D::get_closest_point_to_segment(end_point, back_pathway); - if (end_point.is_equal_approx(back_edge_closest_point)) { - // The end point is basically on top of the last crossed edge, funneling around the corners would at best do nothing. - // At worst it would add an unwanted path point before the last point due to precision issues so skip to the next polygon. - if (apex_poly->back_navigation_poly_id != -1) { - apex_poly = &navigation_polys[apex_poly->back_navigation_poly_id]; - } - } - - Vector3 apex_point = end_point; - - gd::NavigationPoly *left_poly = apex_poly; - Vector3 left_portal = apex_point; - gd::NavigationPoly *right_poly = apex_poly; - Vector3 right_portal = apex_point; - - gd::NavigationPoly *p = apex_poly; - - path.push_back(end_point); - APPEND_METADATA(end_poly); - - while (p) { - // Set left and right points of the pathway between polygons. - Vector3 left = p->back_navigation_edge_pathway_start; - Vector3 right = p->back_navigation_edge_pathway_end; - if (THREE_POINTS_CROSS_PRODUCT(apex_point, left, right).dot(up) < 0) { - SWAP(left, right); - } - - bool skip = false; - if (THREE_POINTS_CROSS_PRODUCT(apex_point, left_portal, left).dot(up) >= 0) { - //process - if (left_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, left, right_portal).dot(up) > 0) { - left_poly = p; - left_portal = left; - } else { - clip_path(navigation_polys, path, apex_poly, right_portal, right_poly, r_path_types, r_path_rids, r_path_owners); - - apex_point = right_portal; - p = right_poly; - left_poly = p; - apex_poly = p; - left_portal = apex_point; - right_portal = apex_point; - - path.push_back(apex_point); - APPEND_METADATA(apex_poly->poly); - skip = true; - } - } - - if (!skip && THREE_POINTS_CROSS_PRODUCT(apex_point, right_portal, right).dot(up) <= 0) { - //process - if (right_portal == apex_point || THREE_POINTS_CROSS_PRODUCT(apex_point, right, left_portal).dot(up) < 0) { - right_poly = p; - right_portal = right; - } else { - clip_path(navigation_polys, path, apex_poly, left_portal, left_poly, r_path_types, r_path_rids, r_path_owners); - - apex_point = left_portal; - p = left_poly; - right_poly = p; - apex_poly = p; - right_portal = apex_point; - left_portal = apex_point; - - path.push_back(apex_point); - APPEND_METADATA(apex_poly->poly); - } - } - - // Go to the previous polygon. - if (p->back_navigation_poly_id != -1) { - p = &navigation_polys[p->back_navigation_poly_id]; - } else { - // The end - p = nullptr; - } - } - - // If the last point is not the begin point, add it to the list. - if (path[path.size() - 1] != begin_point) { - path.push_back(begin_point); - APPEND_METADATA(begin_poly); - } - - path.reverse(); - if (r_path_types) { - r_path_types->reverse(); - } - if (r_path_rids) { - r_path_rids->reverse(); - } - if (r_path_owners) { - r_path_owners->reverse(); - } - - } else { - path.push_back(end_point); - APPEND_METADATA(end_poly); - - // Add mid points - int np_id = least_cost_id; - while (np_id != -1 && navigation_polys[np_id].back_navigation_poly_id != -1) { - if (navigation_polys[np_id].back_navigation_edge != -1) { - int prev = navigation_polys[np_id].back_navigation_edge; - int prev_n = (navigation_polys[np_id].back_navigation_edge + 1) % navigation_polys[np_id].poly->points.size(); - Vector3 point = (navigation_polys[np_id].poly->points[prev].pos + navigation_polys[np_id].poly->points[prev_n].pos) * 0.5; - - path.push_back(point); - APPEND_METADATA(navigation_polys[np_id].poly); - } else { - path.push_back(navigation_polys[np_id].entry); - APPEND_METADATA(navigation_polys[np_id].poly); - } - - np_id = navigation_polys[np_id].back_navigation_poly_id; - } - - path.push_back(begin_point); - APPEND_METADATA(begin_poly); - - path.reverse(); - if (r_path_types) { - r_path_types->reverse(); - } - if (r_path_rids) { - r_path_rids->reverse(); - } - if (r_path_owners) { - r_path_owners->reverse(); - } - } - - // Ensure post conditions (path arrays MUST match in size). - CRASH_COND(r_path_types && path.size() != r_path_types->size()); - CRASH_COND(r_path_rids && path.size() != r_path_rids->size()); - CRASH_COND(r_path_owners && path.size() != r_path_owners->size()); - - return path; + return NavMeshQueries3D::polygons_get_path( + polygons, p_origin, p_destination, p_optimize, p_navigation_layers, + r_path_types, r_path_rids, r_path_owners, up, link_polygons.size()); } Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector3 &p_to, const bool p_use_collision) const { @@ -607,66 +142,7 @@ Vector3 NavMap::get_closest_point_to_segment(const Vector3 &p_from, const Vector return Vector3(); } - bool use_collision = p_use_collision; - Vector3 closest_point; - real_t closest_point_d = FLT_MAX; - - for (const gd::Polygon &p : polygons) { - // For each face check the distance to the segment - for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { - const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); - Vector3 inters; - if (f.intersects_segment(p_from, p_to, &inters)) { - const real_t d = p_from.distance_to(inters); - if (use_collision == false) { - closest_point = inters; - use_collision = true; - closest_point_d = d; - } else if (closest_point_d > d) { - closest_point = inters; - closest_point_d = d; - } - } - // If segment does not itersect face, check the distance from segment's endpoints. - else if (!use_collision) { - const Vector3 p_from_closest = f.get_closest_point_to(p_from); - const real_t d_p_from = p_from.distance_to(p_from_closest); - if (closest_point_d > d_p_from) { - closest_point = p_from_closest; - closest_point_d = d_p_from; - } - - const Vector3 p_to_closest = f.get_closest_point_to(p_to); - const real_t d_p_to = p_to.distance_to(p_to_closest); - if (closest_point_d > d_p_to) { - closest_point = p_to_closest; - closest_point_d = d_p_to; - } - } - } - // Finally, check for a case when shortest distance is between some point located on a face's edge and some point located on a line segment. - if (!use_collision) { - for (size_t point_id = 0; point_id < p.points.size(); point_id += 1) { - Vector3 a, b; - - Geometry3D::get_closest_points_between_segments( - p_from, - p_to, - p.points[point_id].pos, - p.points[(point_id + 1) % p.points.size()].pos, - a, - b); - - const real_t d = a.distance_to(b); - if (d < closest_point_d) { - closest_point_d = d; - closest_point = b; - } - } - } - } - - return closest_point; + return NavMeshQueries3D::polygons_get_closest_point_to_segment(polygons, p_from, p_to, p_use_collision); } Vector3 NavMap::get_closest_point(const Vector3 &p_point) const { @@ -675,8 +151,8 @@ Vector3 NavMap::get_closest_point(const Vector3 &p_point) const { NAVMAP_ITERATION_ZERO_ERROR_MSG(); return Vector3(); } - gd::ClosestPointQueryResult cp = get_closest_point_info(p_point); - return cp.point; + + return NavMeshQueries3D::polygons_get_closest_point(polygons, p_point); } Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const { @@ -685,8 +161,8 @@ Vector3 NavMap::get_closest_point_normal(const Vector3 &p_point) const { NAVMAP_ITERATION_ZERO_ERROR_MSG(); return Vector3(); } - gd::ClosestPointQueryResult cp = get_closest_point_info(p_point); - return cp.normal; + + return NavMeshQueries3D::polygons_get_closest_point_normal(polygons, p_point); } RID NavMap::get_closest_point_owner(const Vector3 &p_point) const { @@ -695,32 +171,14 @@ RID NavMap::get_closest_point_owner(const Vector3 &p_point) const { NAVMAP_ITERATION_ZERO_ERROR_MSG(); return RID(); } - gd::ClosestPointQueryResult cp = get_closest_point_info(p_point); - return cp.owner; + + return NavMeshQueries3D::polygons_get_closest_point_owner(polygons, p_point); } gd::ClosestPointQueryResult NavMap::get_closest_point_info(const Vector3 &p_point) const { RWLockRead read_lock(map_rwlock); - gd::ClosestPointQueryResult result; - real_t closest_point_ds = FLT_MAX; - - for (const gd::Polygon &p : polygons) { - // For each face check the distance to the point - for (size_t point_id = 2; point_id < p.points.size(); point_id += 1) { - const Face3 f(p.points[0].pos, p.points[point_id - 1].pos, p.points[point_id].pos); - const Vector3 inters = f.get_closest_point_to(p_point); - const real_t ds = inters.distance_squared_to(p_point); - if (ds < closest_point_ds) { - result.point = inters; - result.normal = f.get_plane().normal; - result.owner = p.owner->get_self(); - closest_point_ds = ds; - } - } - } - - return result; + return NavMeshQueries3D::polygons_get_closest_point_info(polygons, p_point); } void NavMap::add_region(NavRegion *p_region) { @@ -943,29 +401,30 @@ void NavMap::sync() { } // Resize the polygon count. - int count = 0; + int polygon_count = 0; for (const NavRegion *region : regions) { if (!region->get_enabled()) { continue; } - count += region->get_polygons().size(); + polygon_count += region->get_polygons().size(); } - polygons.resize(count); + polygons.resize(polygon_count); // Copy all region polygons in the map. - count = 0; + polygon_count = 0; for (const NavRegion *region : regions) { if (!region->get_enabled()) { continue; } const LocalVector<gd::Polygon> &polygons_source = region->get_polygons(); for (uint32_t n = 0; n < polygons_source.size(); n++) { - polygons[count + n] = polygons_source[n]; + polygons[polygon_count] = polygons_source[n]; + polygons[polygon_count].id = polygon_count; + polygon_count++; } - count += region->get_polygons().size(); } - _new_pm_polygon_count = polygons.size(); + _new_pm_polygon_count = polygon_count; // Group all edges per key. HashMap<gd::EdgeKey, Vector<gd::Edge::Connection>, gd::EdgeKey> connections; @@ -1136,6 +595,7 @@ void NavMap::sync() { // If we have both a start and end point, then create a synthetic polygon to route through. if (closest_start_polygon && closest_end_polygon) { gd::Polygon &new_polygon = link_polygons[link_poly_idx++]; + new_polygon.id = polygon_count++; new_polygon.owner = link; new_polygon.edges.clear(); @@ -1391,39 +851,6 @@ void NavMap::dispatch_callbacks() { } } -void NavMap::clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const { - Vector3 from = path[path.size() - 1]; - - if (from.is_equal_approx(p_to_point)) { - return; - } - Plane cut_plane; - cut_plane.normal = (from - p_to_point).cross(up); - if (cut_plane.normal == Vector3()) { - return; - } - cut_plane.normal.normalize(); - cut_plane.d = cut_plane.normal.dot(from); - - while (from_poly != p_to_poly) { - Vector3 pathway_start = from_poly->back_navigation_edge_pathway_start; - Vector3 pathway_end = from_poly->back_navigation_edge_pathway_end; - - ERR_FAIL_COND(from_poly->back_navigation_poly_id == -1); - from_poly = &p_navigation_polys[from_poly->back_navigation_poly_id]; - - if (!pathway_start.is_equal_approx(pathway_end)) { - Vector3 inters; - if (cut_plane.intersects_segment(pathway_start, pathway_end, &inters)) { - if (!inters.is_equal_approx(p_to_point) && !inters.is_equal_approx(path[path.size() - 1])) { - path.push_back(inters); - APPEND_METADATA(from_poly->poly); - } - } - } - } -} - void NavMap::_update_merge_rasterizer_cell_dimensions() { merge_rasterizer_cell_size = cell_size * merge_rasterizer_cell_scale; merge_rasterizer_cell_height = cell_height * merge_rasterizer_cell_scale; diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index c18ee7155b..b9120c04d9 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -232,7 +232,6 @@ private: void compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent); void compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent); - void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const; void _update_rvo_simulation(); void _update_rvo_obstacles_tree_2d(); void _update_rvo_agents_tree_2d(); diff --git a/modules/navigation/nav_region.cpp b/modules/navigation/nav_region.cpp index 85510bd416..7a44adecbc 100644 --- a/modules/navigation/nav_region.cpp +++ b/modules/navigation/nav_region.cpp @@ -32,6 +32,8 @@ #include "nav_map.h" +#include "3d/nav_mesh_queries_3d.h" + void NavRegion::set_map(NavMap *p_map) { if (map == p_map) { return; @@ -108,81 +110,7 @@ Vector3 NavRegion::get_random_point(uint32_t p_navigation_layers, bool p_uniform return Vector3(); } - const LocalVector<gd::Polygon> ®ion_polygons = get_polygons(); - - if (region_polygons.is_empty()) { - return Vector3(); - } - - if (p_uniformly) { - real_t accumulated_area = 0; - RBMap<real_t, uint32_t> region_area_map; - - for (uint32_t rp_index = 0; rp_index < region_polygons.size(); rp_index++) { - const gd::Polygon ®ion_polygon = region_polygons[rp_index]; - real_t polyon_area = region_polygon.surface_area; - - if (polyon_area == 0.0) { - continue; - } - region_area_map[accumulated_area] = rp_index; - accumulated_area += polyon_area; - } - if (region_area_map.is_empty() || accumulated_area == 0) { - // All polygons have no real surface / no area. - return Vector3(); - } - - real_t region_area_map_pos = Math::random(real_t(0), accumulated_area); - - RBMap<real_t, uint32_t>::Iterator region_E = region_area_map.find_closest(region_area_map_pos); - ERR_FAIL_COND_V(!region_E, Vector3()); - uint32_t rrp_polygon_index = region_E->value; - ERR_FAIL_UNSIGNED_INDEX_V(rrp_polygon_index, region_polygons.size(), Vector3()); - - const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index]; - - real_t accumulated_polygon_area = 0; - RBMap<real_t, uint32_t> polygon_area_map; - - for (uint32_t rpp_index = 2; rpp_index < rr_polygon.points.size(); rpp_index++) { - real_t face_area = Face3(rr_polygon.points[0].pos, rr_polygon.points[rpp_index - 1].pos, rr_polygon.points[rpp_index].pos).get_area(); - - if (face_area == 0.0) { - continue; - } - polygon_area_map[accumulated_polygon_area] = rpp_index; - accumulated_polygon_area += face_area; - } - if (polygon_area_map.is_empty() || accumulated_polygon_area == 0) { - // All faces have no real surface / no area. - return Vector3(); - } - - real_t polygon_area_map_pos = Math::random(real_t(0), accumulated_polygon_area); - - RBMap<real_t, uint32_t>::Iterator polygon_E = polygon_area_map.find_closest(polygon_area_map_pos); - ERR_FAIL_COND_V(!polygon_E, Vector3()); - uint32_t rrp_face_index = polygon_E->value; - ERR_FAIL_UNSIGNED_INDEX_V(rrp_face_index, rr_polygon.points.size(), Vector3()); - - const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos); - - Vector3 face_random_position = face.get_random_point_inside(); - return face_random_position; - - } else { - uint32_t rrp_polygon_index = Math::random(int(0), region_polygons.size() - 1); - - const gd::Polygon &rr_polygon = region_polygons[rrp_polygon_index]; - - uint32_t rrp_face_index = Math::random(int(2), rr_polygon.points.size() - 1); - - const Face3 face(rr_polygon.points[0].pos, rr_polygon.points[rrp_face_index - 1].pos, rr_polygon.points[rrp_face_index].pos); - - Vector3 face_random_position = face.get_random_point_inside(); - return face_random_position; - } + return NavMeshQueries3D::polygons_get_random_point(get_polygons(), p_navigation_layers, p_uniformly); } bool NavRegion::sync() { diff --git a/modules/navigation/nav_utils.h b/modules/navigation/nav_utils.h index c3939e9979..ba4c44b748 100644 --- a/modules/navigation/nav_utils.h +++ b/modules/navigation/nav_utils.h @@ -98,6 +98,9 @@ struct Edge { }; struct Polygon { + /// Id of the polygon in the map. + uint32_t id = UINT32_MAX; + /// Navigation region or link that contains this polygon. const NavBase *owner = nullptr; @@ -111,9 +114,11 @@ struct Polygon { }; struct NavigationPoly { - uint32_t self_id = 0; /// This poly. - const Polygon *poly; + const Polygon *poly = nullptr; + + /// Index in the heap of traversable polygons. + uint32_t traversable_poly_index = UINT32_MAX; /// Those 4 variables are used to travel the path backwards. int back_navigation_poly_id = -1; @@ -123,20 +128,44 @@ struct NavigationPoly { /// The entry position of this poly. Vector3 entry; - /// The distance to the destination. + /// The distance traveled until now (g cost). real_t traveled_distance = 0.0; + /// The distance to the destination (h cost). + real_t distance_to_destination = 0.0; - NavigationPoly() { poly = nullptr; } + /// The total travel cost (f cost). + real_t total_travel_cost() const { + return traveled_distance + distance_to_destination; + } - NavigationPoly(const Polygon *p_poly) : - poly(p_poly) {} + bool operator==(const NavigationPoly &p_other) const { + return poly == p_other.poly; + } - bool operator==(const NavigationPoly &other) const { - return poly == other.poly; + bool operator!=(const NavigationPoly &p_other) const { + return !(*this == p_other); } +}; + +struct NavPolyTravelCostGreaterThan { + // Returns `true` if the travel cost of `a` is higher than that of `b`. + bool operator()(const NavigationPoly *p_poly_a, const NavigationPoly *p_poly_b) const { + real_t f_cost_a = p_poly_a->total_travel_cost(); + real_t h_cost_a = p_poly_a->distance_to_destination; + real_t f_cost_b = p_poly_b->total_travel_cost(); + real_t h_cost_b = p_poly_b->distance_to_destination; - bool operator!=(const NavigationPoly &other) const { - return !operator==(other); + if (f_cost_a != f_cost_b) { + return f_cost_a > f_cost_b; + } else { + return h_cost_a > h_cost_b; + } + } +}; + +struct NavPolyHeapIndexer { + void operator()(NavigationPoly *p_poly, uint32_t p_heap_index) const { + p_poly->traversable_poly_index = p_heap_index; } }; @@ -146,6 +175,129 @@ struct ClosestPointQueryResult { RID owner; }; +template <typename T> +struct NoopIndexer { + void operator()(const T &p_value, uint32_t p_index) {} +}; + +/** + * A max-heap implementation that notifies of element index changes. + */ +template <typename T, typename LessThan = Comparator<T>, typename Indexer = NoopIndexer<T>> +class Heap { + LocalVector<T> _buffer; + + LessThan _less_than; + Indexer _indexer; + +public: + void reserve(uint32_t p_size) { + _buffer.reserve(p_size); + } + + uint32_t size() const { + return _buffer.size(); + } + + bool is_empty() const { + return _buffer.is_empty(); + } + + void push(const T &p_element) { + _buffer.push_back(p_element); + _indexer(p_element, _buffer.size() - 1); + _shift_up(_buffer.size() - 1); + } + + T pop() { + ERR_FAIL_COND_V_MSG(_buffer.is_empty(), T(), "Can't pop an empty heap."); + T value = _buffer[0]; + _indexer(value, UINT32_MAX); + if (_buffer.size() > 1) { + _buffer[0] = _buffer[_buffer.size() - 1]; + _indexer(_buffer[0], 0); + _buffer.remove_at(_buffer.size() - 1); + _shift_down(0); + } else { + _buffer.remove_at(_buffer.size() - 1); + } + return value; + } + + /** + * Update the position of the element in the heap if necessary. + */ + void shift(uint32_t p_index) { + ERR_FAIL_UNSIGNED_INDEX_MSG(p_index, _buffer.size(), "Heap element index is out of range."); + if (!_shift_up(p_index)) { + _shift_down(p_index); + } + } + + void clear() { + for (const T &value : _buffer) { + _indexer(value, UINT32_MAX); + } + _buffer.clear(); + } + + Heap() {} + + Heap(const LessThan &p_less_than) : + _less_than(p_less_than) {} + + Heap(const Indexer &p_indexer) : + _indexer(p_indexer) {} + + Heap(const LessThan &p_less_than, const Indexer &p_indexer) : + _less_than(p_less_than), _indexer(p_indexer) {} + +private: + bool _shift_up(uint32_t p_index) { + T value = _buffer[p_index]; + uint32_t current_index = p_index; + uint32_t parent_index = (current_index - 1) / 2; + while (current_index > 0 && _less_than(_buffer[parent_index], value)) { + _buffer[current_index] = _buffer[parent_index]; + _indexer(_buffer[current_index], current_index); + current_index = parent_index; + parent_index = (current_index - 1) / 2; + } + if (current_index != p_index) { + _buffer[current_index] = value; + _indexer(value, current_index); + return true; + } else { + return false; + } + } + + bool _shift_down(uint32_t p_index) { + T value = _buffer[p_index]; + uint32_t current_index = p_index; + uint32_t child_index = 2 * current_index + 1; + while (child_index < _buffer.size()) { + if (child_index + 1 < _buffer.size() && + _less_than(_buffer[child_index], _buffer[child_index + 1])) { + child_index++; + } + if (_less_than(_buffer[child_index], value)) { + break; + } + _buffer[current_index] = _buffer[child_index]; + _indexer(_buffer[current_index], current_index); + current_index = child_index; + child_index = 2 * current_index + 1; + } + if (current_index != p_index) { + _buffer[current_index] = value; + _indexer(value, current_index); + return true; + } else { + return false; + } + } +}; } // namespace gd #endif // NAV_UTILS_H diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp index 0960b2ad36..b55b1141e1 100644 --- a/modules/noise/noise_texture_2d.cpp +++ b/modules/noise/noise_texture_2d.cpp @@ -194,6 +194,9 @@ Ref<Image> NoiseTexture2D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradi void NoiseTexture2D::_update_texture() { bool use_thread = true; +#ifndef THREADS_ENABLED + use_thread = false; +#endif if (first_time) { use_thread = false; first_time = false; diff --git a/modules/noise/noise_texture_3d.cpp b/modules/noise/noise_texture_3d.cpp index 9047491344..e3cca8a09f 100644 --- a/modules/noise/noise_texture_3d.cpp +++ b/modules/noise/noise_texture_3d.cpp @@ -187,6 +187,9 @@ Ref<Image> NoiseTexture3D::_modulate_with_gradient(Ref<Image> p_image, Ref<Gradi void NoiseTexture3D::_update_texture() { bool use_thread = true; +#ifndef THREADS_ENABLED + use_thread = false; +#endif if (first_time) { use_thread = false; first_time = false; diff --git a/modules/noise/tests/test_noise_texture_2d.h b/modules/noise/tests/test_noise_texture_2d.h index 938e8fd6ab..0d18d66e74 100644 --- a/modules/noise/tests/test_noise_texture_2d.h +++ b/modules/noise/tests/test_noise_texture_2d.h @@ -135,7 +135,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") { noise_texture->set_noise(noise); CHECK(noise_texture->get_noise() == noise); noise_texture->set_noise(nullptr); - CHECK(noise_texture->get_noise() == nullptr); + CHECK(noise_texture->get_noise().is_null()); noise_texture->set_width(8); noise_texture->set_height(4); @@ -190,7 +190,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") { noise_texture->set_color_ramp(gradient); CHECK(noise_texture->get_color_ramp() == gradient); noise_texture->set_color_ramp(nullptr); - CHECK(noise_texture->get_color_ramp() == nullptr); + CHECK(noise_texture->get_color_ramp().is_null()); } TEST_CASE("[NoiseTexture2D][SceneTree] Generating a basic noise texture with mipmaps and color ramp modulation") { diff --git a/modules/noise/tests/test_noise_texture_3d.h b/modules/noise/tests/test_noise_texture_3d.h index b708eac43b..434cd20a08 100644 --- a/modules/noise/tests/test_noise_texture_3d.h +++ b/modules/noise/tests/test_noise_texture_3d.h @@ -133,7 +133,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") { noise_texture->set_noise(noise); CHECK(noise_texture->get_noise() == noise); noise_texture->set_noise(nullptr); - CHECK(noise_texture->get_noise() == nullptr); + CHECK(noise_texture->get_noise().is_null()); noise_texture->set_width(8); noise_texture->set_height(4); @@ -174,7 +174,7 @@ TEST_CASE("[NoiseTexture][SceneTree] Getter and setter") { noise_texture->set_color_ramp(gradient); CHECK(noise_texture->get_color_ramp() == gradient); noise_texture->set_color_ramp(nullptr); - CHECK(noise_texture->get_color_ramp() == nullptr); + CHECK(noise_texture->get_color_ramp().is_null()); } TEST_CASE("[NoiseTexture3D][SceneTree] Generating a basic noise texture with mipmaps and color ramp modulation") { diff --git a/modules/openxr/openxr_api.cpp b/modules/openxr/openxr_api.cpp index ecf7c05789..a19a75e722 100644 --- a/modules/openxr/openxr_api.cpp +++ b/modules/openxr/openxr_api.cpp @@ -271,17 +271,14 @@ OpenXRAPI *OpenXRAPI::singleton = nullptr; Vector<OpenXRExtensionWrapper *> OpenXRAPI::registered_extension_wrappers; bool OpenXRAPI::openxr_is_enabled(bool p_check_run_in_editor) { - // @TODO we need an overrule switch so we can force enable openxr, i.e run "godot --openxr_enabled" - - if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) { - // Disabled for now, using XR inside of the editor we'll be working on during the coming months. - return false; - } else { - if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) { - return GLOBAL_GET("xr/openxr/enabled"); + if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) { + if (Engine::get_singleton()->is_editor_hint() && p_check_run_in_editor) { + return GLOBAL_GET("xr/openxr/enabled.editor"); } else { - return XRServer::get_xr_mode() == XRServer::XRMODE_ON; + return GLOBAL_GET("xr/openxr/enabled"); } + } else { + return XRServer::get_xr_mode() == XRServer::XRMODE_ON; } } @@ -557,14 +554,11 @@ bool OpenXRAPI::create_instance() { extension_ptrs.push_back(enabled_extensions[i].get_data()); } - // Get our project name - String project_name = GLOBAL_GET("application/config/name"); - // Create our OpenXR instance XrApplicationInfo application_info{ - "", // applicationName, we'll set this down below + "Godot Engine", // applicationName, if we're running a game we'll update this down below. 1, // applicationVersion, we don't currently have this - "Godot Game Engine", // engineName + "Godot Engine", // engineName VERSION_MAJOR * 10000 + VERSION_MINOR * 100 + VERSION_PATCH, // engineVersion 4.0 -> 40000, 4.0.1 -> 40001, 4.1 -> 40100, etc. XR_API_VERSION_1_0 // apiVersion }; @@ -588,7 +582,11 @@ bool OpenXRAPI::create_instance() { extension_ptrs.ptr() // enabledExtensionNames }; - copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE); + // Get our project name + String project_name = GLOBAL_GET("application/config/name"); + if (!project_name.is_empty()) { + copy_string_to_char_buffer(project_name, instance_create_info.applicationInfo.applicationName, XR_MAX_APPLICATION_NAME_SIZE); + } XrResult result = xrCreateInstance(&instance_create_info, &instance); ERR_FAIL_COND_V_MSG(XR_FAILED(result), false, "Failed to create XR instance."); @@ -2583,7 +2581,6 @@ OpenXRAPI::OpenXRAPI() { if (Engine::get_singleton()->is_editor_hint()) { // Enabled OpenXR in the editor? Adjust our settings for the editor - } else { // Load settings from project settings int form_factor_setting = GLOBAL_GET("xr/openxr/form_factor"); diff --git a/modules/regex/regex.cpp b/modules/regex/regex.cpp index 85c0b9ecad..9c366408a0 100644 --- a/modules/regex/regex.cpp +++ b/modules/regex/regex.cpp @@ -54,8 +54,8 @@ int RegExMatch::_find(const Variant &p_name) const { return -1; } return i; - } else if (p_name.get_type() == Variant::STRING || p_name.get_type() == Variant::STRING_NAME) { - HashMap<String, int>::ConstIterator found = names.find((String)p_name); + } else if (p_name.is_string()) { + HashMap<String, int>::ConstIterator found = names.find(p_name); if (found) { return found->value; } diff --git a/modules/regex/tests/test_regex.h b/modules/regex/tests/test_regex.h index af58e2487b..7e8e456341 100644 --- a/modules/regex/tests/test_regex.h +++ b/modules/regex/tests/test_regex.h @@ -80,32 +80,32 @@ TEST_CASE("[RegEx] Searching") { REQUIRE(re.is_valid()); Ref<RegExMatch> match = re.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "ea"); match = re.search(s, 1, 2); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "e"); match = re.search(s, 2, 4); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "a"); match = re.search(s, 3, 5); - CHECK(match == nullptr); + CHECK(match.is_null()); match = re.search(s, 6, 2); - CHECK(match == nullptr); + CHECK(match.is_null()); const Array all_results = re.search_all(s); CHECK(all_results.size() == 2); match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "ea"); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == "i"); CHECK(re.compile(numerics) == OK); CHECK(re.is_valid()); - CHECK(re.search(s) == nullptr); + CHECK(re.search(s).is_null()); CHECK(re.search_all(s).size() == 0); } @@ -168,7 +168,7 @@ TEST_CASE("[RegEx] Uninitialized use") { RegEx re; ERR_PRINT_OFF; - CHECK(re.search(s) == nullptr); + CHECK(re.search(s).is_null()); CHECK(re.search_all(s).size() == 0); CHECK(re.sub(s, "") == ""); CHECK(re.get_group_count() == 0); @@ -237,10 +237,10 @@ TEST_CASE("[RegEx] Invalid end position") { const Array all_results = re.search_all(s, 0, 10); CHECK(all_results.size() == 2); match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == String("o")); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == String("o")); CHECK(re.sub(s, "", true, 0, 10) == "Gdt"); @@ -251,7 +251,7 @@ TEST_CASE("[RegEx] Get match string list") { RegEx re("(Go)(dot)"); Ref<RegExMatch> match = re.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); PackedStringArray result; result.append("Godot"); result.append("Go"); @@ -265,14 +265,14 @@ TEST_CASE("[RegEx] Match start and end positions") { RegEx re1("pattern"); REQUIRE(re1.is_valid()); Ref<RegExMatch> match = re1.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 6); CHECK(match->get_end(0) == 13); RegEx re2("(?<vowel>[aeiou])"); REQUIRE(re2.is_valid()); match = re2.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start("vowel") == 2); CHECK(match->get_end("vowel") == 3); } @@ -307,7 +307,7 @@ TEST_CASE("[RegEx] Simple lookahead") { RegEx re("o(?=t)"); REQUIRE(re.is_valid()); Ref<RegExMatch> match = re.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 3); CHECK(match->get_end(0) == 4); } @@ -325,12 +325,12 @@ TEST_CASE("[RegEx] Lookahead groups empty matches") { CHECK(all_results.size() == 2); match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == String("")); CHECK(match->get_string(1) == String("12")); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_string(0) == String("")); CHECK(match->get_string(1) == String("2")); } @@ -341,7 +341,7 @@ TEST_CASE("[RegEx] Simple lookbehind") { RegEx re("(?<=d)o"); REQUIRE(re.is_valid()); Ref<RegExMatch> match = re.search(s); - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 3); CHECK(match->get_end(0) == 4); } @@ -355,22 +355,22 @@ TEST_CASE("[RegEx] Simple lookbehind search all") { CHECK(all_results.size() == 4); Ref<RegExMatch> match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 1); CHECK(match->get_end(0) == 2); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 3); CHECK(match->get_end(0) == 4); match = all_results[2]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 7); CHECK(match->get_end(0) == 8); match = all_results[3]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 9); CHECK(match->get_end(0) == 10); } @@ -386,7 +386,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") { CHECK(all_results.size() == 3); match = all_results[0]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 2); CHECK(match->get_end(0) == 2); CHECK(match->get_start(1) == 1); @@ -395,7 +395,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") { CHECK(match->get_string(1) == String("b")); match = all_results[1]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 6); CHECK(match->get_end(0) == 6); CHECK(match->get_start(1) == 5); @@ -404,7 +404,7 @@ TEST_CASE("[RegEx] Lookbehind groups empty matches") { CHECK(match->get_string(1) == String("b")); match = all_results[2]; - REQUIRE(match != nullptr); + REQUIRE(match.is_valid()); CHECK(match->get_start(0) == 8); CHECK(match->get_end(0) == 8); CHECK(match->get_start(1) == 7); diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 4bf09d3c84..3322300dda 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -5980,7 +5980,7 @@ _FORCE_INLINE_ void TextServerAdvanced::_add_featuers(const Dictionary &p_source int32_t value = values[i]; if (value >= 0) { hb_feature_t feature; - if (keys[i].get_type() == Variant::STRING) { + if (keys[i].is_string()) { feature.tag = _name_to_tag(keys[i]); } else { feature.tag = keys[i]; diff --git a/modules/upnp/upnp.cpp b/modules/upnp/upnp.cpp index 70f0ea346b..6bdb261b50 100644 --- a/modules/upnp/upnp.cpp +++ b/modules/upnp/upnp.cpp @@ -229,14 +229,14 @@ Ref<UPNPDevice> UPNP::get_device(int index) const { } void UPNP::add_device(Ref<UPNPDevice> device) { - ERR_FAIL_NULL(device); + ERR_FAIL_COND(device.is_null()); devices.push_back(device); } void UPNP::set_device(int index, Ref<UPNPDevice> device) { ERR_FAIL_INDEX(index, devices.size()); - ERR_FAIL_NULL(device); + ERR_FAIL_COND(device.is_null()); devices.set(index, device); } @@ -257,7 +257,7 @@ Ref<UPNPDevice> UPNP::get_gateway() const { for (int i = 0; i < devices.size(); i++) { Ref<UPNPDevice> dev = get_device(i); - if (dev != nullptr && dev->is_valid_gateway()) { + if (dev.is_valid() && dev->is_valid_gateway()) { return dev; } } @@ -292,7 +292,7 @@ bool UPNP::is_discover_ipv6() const { String UPNP::query_external_address() const { Ref<UPNPDevice> dev = get_gateway(); - if (dev == nullptr) { + if (dev.is_null()) { return ""; } @@ -302,7 +302,7 @@ String UPNP::query_external_address() const { int UPNP::add_port_mapping(int port, int port_internal, String desc, String proto, int duration) const { Ref<UPNPDevice> dev = get_gateway(); - if (dev == nullptr) { + if (dev.is_null()) { return UPNP_RESULT_NO_GATEWAY; } @@ -312,7 +312,7 @@ int UPNP::add_port_mapping(int port, int port_internal, String desc, String prot int UPNP::delete_port_mapping(int port, String proto) const { Ref<UPNPDevice> dev = get_gateway(); - if (dev == nullptr) { + if (dev.is_null()) { return UPNP_RESULT_NO_GATEWAY; } diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp index e96d9da7a9..4e182f9787 100644 --- a/modules/zip/zip_packer.cpp +++ b/modules/zip/zip_packer.cpp @@ -48,7 +48,7 @@ Error ZIPPacker::close() { Error err = zipClose(zf, nullptr) == ZIP_OK ? OK : FAILED; if (err == OK) { - DEV_ASSERT(fa == nullptr); + DEV_ASSERT(fa.is_null()); zf = nullptr; } diff --git a/modules/zip/zip_reader.cpp b/modules/zip/zip_reader.cpp index 123d1e5d46..76f48edb69 100644 --- a/modules/zip/zip_reader.cpp +++ b/modules/zip/zip_reader.cpp @@ -48,7 +48,7 @@ Error ZIPReader::close() { Error err = unzClose(uzf) == UNZ_OK ? OK : FAILED; if (err == OK) { - DEV_ASSERT(fa == nullptr); + DEV_ASSERT(fa.is_null()); uzf = nullptr; } diff --git a/platform/android/api/api.cpp b/platform/android/api/api.cpp index 60c369951c..6920f801e5 100644 --- a/platform/android/api/api.cpp +++ b/platform/android/api/api.cpp @@ -49,6 +49,7 @@ void register_android_api() { #endif GDREGISTER_CLASS(JavaClass); + GDREGISTER_CLASS(JavaObject); GDREGISTER_CLASS(JavaClassWrapper); Engine::get_singleton()->add_singleton(Engine::Singleton("JavaClassWrapper", JavaClassWrapper::get_singleton())); } @@ -59,6 +60,16 @@ void unregister_android_api() { #endif } +void JavaClass::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_java_class_name"), &JavaClass::get_java_class_name); + ClassDB::bind_method(D_METHOD("get_java_method_list"), &JavaClass::get_java_method_list); + ClassDB::bind_method(D_METHOD("get_java_parent_class"), &JavaClass::get_java_parent_class); +} + +void JavaObject::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_java_class"), &JavaObject::get_java_class); +} + void JavaClassWrapper::_bind_methods() { ClassDB::bind_method(D_METHOD("wrap", "name"), &JavaClassWrapper::wrap); } @@ -69,13 +80,32 @@ Variant JavaClass::callp(const StringName &, const Variant **, int, Callable::Ca return Variant(); } +String JavaClass::get_java_class_name() const { + return ""; +} + +TypedArray<Dictionary> JavaClass::get_java_method_list() const { + return TypedArray<Dictionary>(); +} + +Ref<JavaClass> JavaClass::get_java_parent_class() const { + return Ref<JavaClass>(); +} + JavaClass::JavaClass() { } +JavaClass::~JavaClass() { +} + Variant JavaObject::callp(const StringName &, const Variant **, int, Callable::CallError &) { return Variant(); } +Ref<JavaClass> JavaObject::get_java_class() const { + return Ref<JavaClass>(); +} + JavaClassWrapper *JavaClassWrapper::singleton = nullptr; Ref<JavaClass> JavaClassWrapper::wrap(const String &) { diff --git a/platform/android/api/java_class_wrapper.h b/platform/android/api/java_class_wrapper.h index e21a331ab9..52df1644be 100644 --- a/platform/android/api/java_class_wrapper.h +++ b/platform/android/api/java_class_wrapper.h @@ -32,6 +32,7 @@ #define JAVA_CLASS_WRAPPER_H #include "core/object/ref_counted.h" +#include "core/variant/typed_array.h" #ifdef ANDROID_ENABLED #include <android/log.h> @@ -67,6 +68,7 @@ class JavaClass : public RefCounted { struct MethodInfo { bool _static = false; + bool _constructor = false; Vector<uint32_t> param_types; Vector<StringName> param_sigs; uint32_t return_type = 0; @@ -174,14 +176,29 @@ class JavaClass : public RefCounted { bool _call_method(JavaObject *p_instance, const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error, Variant &ret); friend class JavaClassWrapper; + friend class JavaObject; + String java_class_name; + String java_constructor_name; HashMap<StringName, List<MethodInfo>> methods; jclass _class; #endif +protected: + static void _bind_methods(); + public: virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; + String get_java_class_name() const; + TypedArray<Dictionary> get_java_method_list() const; + Ref<JavaClass> get_java_parent_class() const; + +#ifdef ANDROID_ENABLED + virtual String to_string() override; +#endif + JavaClass(); + ~JavaClass(); }; class JavaObject : public RefCounted { @@ -191,14 +208,24 @@ class JavaObject : public RefCounted { Ref<JavaClass> base_class; friend class JavaClass; - jobject instance; + jobject instance = nullptr; #endif +protected: + static void _bind_methods(); + public: virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; + Ref<JavaClass> get_java_class() const; + #ifdef ANDROID_ENABLED - JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance); + virtual String to_string() override; + + jobject get_instance() { return instance; } + + JavaObject(); + JavaObject(const Ref<JavaClass> &p_base, jobject p_instance); ~JavaObject(); #endif }; @@ -209,13 +236,17 @@ class JavaClassWrapper : public Object { #ifdef ANDROID_ENABLED RBMap<String, Ref<JavaClass>> class_cache; friend class JavaClass; - jmethodID getDeclaredMethods; - jmethodID getFields; - jmethodID getParameterTypes; - jmethodID getReturnType; - jmethodID getModifiers; - jmethodID getName; + jmethodID Class_getDeclaredConstructors; + jmethodID Class_getDeclaredMethods; + jmethodID Class_getFields; jmethodID Class_getName; + jmethodID Class_getSuperclass; + jmethodID Constructor_getParameterTypes; + jmethodID Constructor_getModifiers; + jmethodID Method_getParameterTypes; + jmethodID Method_getReturnType; + jmethodID Method_getModifiers; + jmethodID Method_getName; jmethodID Field_getName; jmethodID Field_getModifiers; jmethodID Field_get; @@ -242,6 +273,8 @@ public: Ref<JavaClass> wrap(const String &p_class); #ifdef ANDROID_ENABLED + Ref<JavaClass> wrap_jclass(jclass p_class); + JavaClassWrapper(jobject p_activity = nullptr); #else JavaClassWrapper(); diff --git a/platform/android/api/jni_singleton.h b/platform/android/api/jni_singleton.h index 087fd1bace..06afc4eb78 100644 --- a/platform/android/api/jni_singleton.h +++ b/platform/android/api/jni_singleton.h @@ -180,6 +180,11 @@ public: env->DeleteLocalRef(obj); } break; + case Variant::OBJECT: { + jobject obj = env->CallObjectMethodA(instance, E->get().method, v); + ret = _jobject_to_variant(env, obj); + env->DeleteLocalRef(obj); + } break; default: { env->PopLocalFrame(nullptr); ERR_FAIL_V(Variant()); diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index ae336d6f9d..59b669eabb 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -113,87 +113,6 @@ bool FileAccessAndroid::eof_reached() const { return eof; } -uint8_t FileAccessAndroid::get_8() const { - if (pos >= len) { - eof = true; - return 0; - } - - uint8_t byte; - AAsset_read(asset, &byte, 1); - pos++; - return byte; -} - -uint16_t FileAccessAndroid::get_16() const { - if (pos >= len) { - eof = true; - return 0; - } - - uint16_t bytes = 0; - int r = AAsset_read(asset, &bytes, 2); - - if (r >= 0) { - pos += r; - if (pos >= len) { - eof = true; - } - } - - if (big_endian) { - bytes = BSWAP16(bytes); - } - - return bytes; -} - -uint32_t FileAccessAndroid::get_32() const { - if (pos >= len) { - eof = true; - return 0; - } - - uint32_t bytes = 0; - int r = AAsset_read(asset, &bytes, 4); - - if (r >= 0) { - pos += r; - if (pos >= len) { - eof = true; - } - } - - if (big_endian) { - bytes = BSWAP32(bytes); - } - - return bytes; -} - -uint64_t FileAccessAndroid::get_64() const { - if (pos >= len) { - eof = true; - return 0; - } - - uint64_t bytes = 0; - int r = AAsset_read(asset, &bytes, 8); - - if (r >= 0) { - pos += r; - if (pos >= len) { - eof = true; - } - } - - if (big_endian) { - bytes = BSWAP64(bytes); - } - - return bytes; -} - uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); @@ -209,6 +128,7 @@ uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const pos = len; } } + return r; } @@ -220,19 +140,7 @@ void FileAccessAndroid::flush() { ERR_FAIL(); } -void FileAccessAndroid::store_8(uint8_t p_dest) { - ERR_FAIL(); -} - -void FileAccessAndroid::store_16(uint16_t p_dest) { - ERR_FAIL(); -} - -void FileAccessAndroid::store_32(uint32_t p_dest) { - ERR_FAIL(); -} - -void FileAccessAndroid::store_64(uint64_t p_dest) { +void FileAccessAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL(); } diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index b465a92c78..3224ab50b9 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -68,19 +68,12 @@ public: virtual bool eof_reached() const override; // reading passed EOF virtual Error resize(int64_t p_length) override { return ERR_UNAVAILABLE; } - virtual uint8_t get_8() const override; // get a byte - virtual uint16_t get_16() const override; - virtual uint32_t get_32() const override; - virtual uint64_t get_64() const override; virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; // get last error virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; // store a byte - virtual void store_16(uint16_t p_dest) override; - virtual void store_32(uint32_t p_dest) override; - virtual void store_64(uint64_t p_dest) override; + virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_path) override; // return true if a file exists diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp index 9ae48dfb10..8b52a00ed8 100644 --- a/platform/android/file_access_filesystem_jandroid.cpp +++ b/platform/android/file_access_filesystem_jandroid.cpp @@ -169,43 +169,6 @@ void FileAccessFilesystemJAndroid::_set_eof(bool eof) { } } -uint8_t FileAccessFilesystemJAndroid::get_8() const { - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - uint8_t byte; - get_buffer(&byte, 1); - return byte; -} - -uint16_t FileAccessFilesystemJAndroid::get_16() const { - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - uint16_t bytes = 0; - get_buffer(reinterpret_cast<uint8_t *>(&bytes), 2); - if (big_endian) { - bytes = BSWAP16(bytes); - } - return bytes; -} - -uint32_t FileAccessFilesystemJAndroid::get_32() const { - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - uint32_t bytes = 0; - get_buffer(reinterpret_cast<uint8_t *>(&bytes), 4); - if (big_endian) { - bytes = BSWAP32(bytes); - } - return bytes; -} - -uint64_t FileAccessFilesystemJAndroid::get_64() const { - ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); - uint64_t bytes = 0; - get_buffer(reinterpret_cast<uint8_t *>(&bytes), 8); - if (big_endian) { - bytes = BSWAP64(bytes); - } - return bytes; -} - String FileAccessFilesystemJAndroid::get_line() const { ERR_FAIL_COND_V_MSG(!is_open(), String(), "File must be opened before use."); @@ -271,31 +234,6 @@ uint64_t FileAccessFilesystemJAndroid::get_buffer(uint8_t *p_dst, uint64_t p_len } } -void FileAccessFilesystemJAndroid::store_8(uint8_t p_dest) { - store_buffer(&p_dest, 1); -} - -void FileAccessFilesystemJAndroid::store_16(uint16_t p_dest) { - if (big_endian) { - p_dest = BSWAP16(p_dest); - } - store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 2); -} - -void FileAccessFilesystemJAndroid::store_32(uint32_t p_dest) { - if (big_endian) { - p_dest = BSWAP32(p_dest); - } - store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 4); -} - -void FileAccessFilesystemJAndroid::store_64(uint64_t p_dest) { - if (big_endian) { - p_dest = BSWAP64(p_dest); - } - store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 8); -} - void FileAccessFilesystemJAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) { if (_file_write) { ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h index 2795ac02ac..1345b72fa6 100644 --- a/platform/android/file_access_filesystem_jandroid.h +++ b/platform/android/file_access_filesystem_jandroid.h @@ -78,20 +78,12 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF virtual Error resize(int64_t p_length) override; - virtual uint8_t get_8() const override; ///< get a byte - virtual uint16_t get_16() const override; - virtual uint32_t get_32() const override; - virtual uint64_t get_64() const override; virtual String get_line() const override; ///< get a line virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual void flush() override; - virtual void store_8(uint8_t p_dest) override; ///< store a byte - virtual void store_16(uint16_t p_dest) override; - virtual void store_32(uint32_t p_dest) override; - virtual void store_64(uint64_t p_dest) override; virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_path) override; ///< return true if a file exists diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle index 05b4f379b3..b9d15deec9 100644 --- a/platform/android/java/app/build.gradle +++ b/platform/android/java/app/build.gradle @@ -12,6 +12,7 @@ allprojects { mavenCentral() gradlePluginPortal() maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"} // Godot user plugins custom maven repos String[] mavenRepos = getGodotPluginsMavenRepos() diff --git a/platform/android/java/app/settings.gradle b/platform/android/java/app/settings.gradle index dcac44e393..e758d4e99a 100644 --- a/platform/android/java/app/settings.gradle +++ b/platform/android/java/app/settings.gradle @@ -11,6 +11,7 @@ pluginManagement { mavenCentral() gradlePluginPortal() maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"} } } diff --git a/platform/android/java/build.gradle b/platform/android/java/build.gradle index 771bda6948..974f072c18 100644 --- a/platform/android/java/build.gradle +++ b/platform/android/java/build.gradle @@ -18,12 +18,14 @@ allprojects { mavenCentral() gradlePluginPortal() maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"} } } ext { supportedAbis = ["arm32", "arm64", "x86_32", "x86_64"] supportedFlavors = ["editor", "template"] + supportedEditorVendors = ["google", "meta"] supportedFlavorsBuildTypes = [ "editor": ["dev", "debug", "release"], "template": ["dev", "debug", "release"] @@ -92,15 +94,20 @@ def templateExcludedBuildTask() { /** * Generates the build tasks for the given flavor * @param flavor Must be one of the supported flavors ('template' / 'editor') + * @param editorVendor Must be one of the supported editor vendors ('google' / 'meta') */ -def generateBuildTasks(String flavor = "template") { +def generateBuildTasks(String flavor = "template", String editorVendor = "google") { if (!supportedFlavors.contains(flavor)) { throw new GradleException("Invalid build flavor: $flavor") } + if (!supportedEditorVendors.contains(editorVendor)) { + throw new GradleException("Invalid editor vendor: $editorVendor") + } + String capitalizedEditorVendor = editorVendor.capitalize() def buildTasks = [] - // Only build the apks and aar files for which we have native shared libraries unless we intend + // Only build the binary files for which we have native shared libraries unless we intend // to run the scons build tasks. boolean excludeSconsBuildTasks = excludeSconsBuildTasks() boolean isTemplate = flavor == "template" @@ -163,28 +170,28 @@ def generateBuildTasks(String flavor = "template") { } } else { // Copy the generated editor apk to the bin directory. - String copyEditorApkTaskName = "copyEditor${capitalizedTarget}ApkToBin" + String copyEditorApkTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}ApkToBin" if (tasks.findByName(copyEditorApkTaskName) != null) { buildTasks += tasks.getByName(copyEditorApkTaskName) } else { buildTasks += tasks.create(name: copyEditorApkTaskName, type: Copy) { - dependsOn ":editor:assemble${capitalizedTarget}" - from("editor/build/outputs/apk/${target}") + dependsOn ":editor:assemble${capitalizedEditorVendor}${capitalizedTarget}" + from("editor/build/outputs/apk/${editorVendor}/${target}") into(androidEditorBuildsDir) - include("android_editor-${target}*.apk") + include("android_editor-${editorVendor}-${target}*.apk") } } // Copy the generated editor aab to the bin directory. - String copyEditorAabTaskName = "copyEditor${capitalizedTarget}AabToBin" + String copyEditorAabTaskName = "copyEditor${capitalizedEditorVendor}${capitalizedTarget}AabToBin" if (tasks.findByName(copyEditorAabTaskName) != null) { buildTasks += tasks.getByName(copyEditorAabTaskName) } else { buildTasks += tasks.create(name: copyEditorAabTaskName, type: Copy) { - dependsOn ":editor:bundle${capitalizedTarget}" - from("editor/build/outputs/bundle/${target}") + dependsOn ":editor:bundle${capitalizedEditorVendor}${capitalizedTarget}" + from("editor/build/outputs/bundle/${editorVendor}${capitalizedTarget}") into(androidEditorBuildsDir) - include("android_editor-${target}*.aab") + include("android_editor-${editorVendor}-${target}*.aab") } } } @@ -197,15 +204,27 @@ def generateBuildTasks(String flavor = "template") { } /** - * Generate the Godot Editor Android apk. + * Generate the Godot Editor Android binaries. * * Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries * must have been generated (via scons) prior to running this gradle task. - * The task will only build the apk(s) for which the shared libraries is available. + * The task will only build the binaries for which the shared libraries is available. */ task generateGodotEditor { gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() - dependsOn = generateBuildTasks("editor") + dependsOn = generateBuildTasks("editor", "google") +} + +/** + * Generate the Godot Editor Android binaries for Meta devices. + * + * Note: Unless the 'generateNativeLibs` argument is specified, the Godot 'tools' shared libraries + * must have been generated (via scons) prior to running this gradle task. + * The task will only build the binaries for which the shared libraries is available. + */ +task generateGodotMetaEditor { + gradle.startParameter.excludedTaskNames += templateExcludedBuildTask() + dependsOn = generateBuildTasks("editor", "meta") } /** diff --git a/platform/android/java/editor/build.gradle b/platform/android/java/editor/build.gradle index b8b4233636..54d6b9b5f3 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -5,16 +5,6 @@ plugins { id 'base' } -dependencies { - implementation "androidx.fragment:fragment:$versions.fragmentVersion" - implementation project(":lib") - - implementation "androidx.window:window:1.3.0" - implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion" - implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation "org.bouncycastle:bcprov-jdk15to18:1.77" -} - ext { // Retrieve the build number from the environment variable; default to 0 if none is specified. // The build number is added as a suffix to the version code for upload to the Google Play store. @@ -154,4 +144,37 @@ android { doNotStrip '**/*.so' } } + + flavorDimensions = ["vendor"] + productFlavors { + google { + dimension "vendor" + missingDimensionStrategy 'products', 'editor' + } + meta { + dimension "vendor" + missingDimensionStrategy 'products', 'editor' + ndk { + //noinspection ChromeOsAbiSupport + abiFilters "arm64-v8a" + } + applicationIdSuffix ".meta" + versionNameSuffix "-meta" + minSdkVersion 23 + targetSdkVersion 32 + } + } +} + +dependencies { + implementation "androidx.fragment:fragment:$versions.fragmentVersion" + implementation project(":lib") + + implementation "androidx.window:window:1.3.0" + implementation "androidx.core:core-splashscreen:$versions.splashscreenVersion" + implementation "androidx.constraintlayout:constraintlayout:2.1.4" + implementation "org.bouncycastle:bcprov-jdk15to18:1.77" + + // Meta dependencies + metaImplementation "org.godotengine:godot-openxr-vendors-meta:3.0.0-stable" } diff --git a/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt new file mode 100644 index 0000000000..f15d9f7768 --- /dev/null +++ b/platform/android/java/editor/src/google/java/org/godotengine/editor/GodotEditor.kt @@ -0,0 +1,39 @@ +/**************************************************************************/ +/* GodotEditor.kt */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +package org.godotengine.editor + +/** + * Primary window of the Godot Editor. + * + * This is the implementation of the editor used when running on regular Android devices. + */ +open class GodotEditor : BaseGodotEditor() { +} diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt index 1995a38c2a..50daf44d2a 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/BaseGodotEditor.kt @@ -1,5 +1,5 @@ /**************************************************************************/ -/* GodotEditor.kt */ +/* BaseGodotEditor.kt */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -52,6 +52,8 @@ import org.godotengine.godot.GodotLib import org.godotengine.godot.error.Error import org.godotengine.godot.utils.PermissionsUtil import org.godotengine.godot.utils.ProcessPhoenix +import org.godotengine.godot.utils.isHorizonOSDevice +import org.godotengine.godot.utils.isNativeXRDevice import java.util.* import kotlin.math.min @@ -61,13 +63,11 @@ import kotlin.math.min * This provides the basic templates for the activities making up this application. * Each derived activity runs in its own process, which enable up to have several instances of * the Godot engine up and running at the same time. - * - * It also plays the role of the primary editor window. */ -open class GodotEditor : GodotActivity() { +abstract class BaseGodotEditor : GodotActivity() { companion object { - private val TAG = GodotEditor::class.java.simpleName + private val TAG = BaseGodotEditor::class.java.simpleName private const val WAIT_FOR_DEBUGGER = false @@ -81,12 +81,13 @@ open class GodotEditor : GodotActivity() { // Command line arguments private const val FULLSCREEN_ARG = "--fullscreen" private const val FULLSCREEN_ARG_SHORT = "-f" - private const val EDITOR_ARG = "--editor" - private const val EDITOR_ARG_SHORT = "-e" - private const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager" - private const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p" - private const val BREAKPOINTS_ARG = "--breakpoints" - private const val BREAKPOINTS_ARG_SHORT = "-b" + internal const val EDITOR_ARG = "--editor" + internal const val EDITOR_ARG_SHORT = "-e" + internal const val EDITOR_PROJECT_MANAGER_ARG = "--project-manager" + internal const val EDITOR_PROJECT_MANAGER_ARG_SHORT = "-p" + internal const val BREAKPOINTS_ARG = "--breakpoints" + internal const val BREAKPOINTS_ARG_SHORT = "-b" + internal const val XR_MODE_ARG = "--xr-mode" // Info for the various classes used by the editor internal val EDITOR_MAIN_INFO = EditorWindowInfo(GodotEditor::class.java, 777, "") @@ -101,6 +102,7 @@ open class GodotEditor : GodotActivity() { private const val ANDROID_WINDOW_AUTO = 0 private const val ANDROID_WINDOW_SAME_AS_EDITOR = 1 private const val ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR = 2 + private const val ANDROID_WINDOW_SAME_AS_EDITOR_AND_LAUNCH_IN_PIP_MODE = 3 /** * Sets of constants to specify the Play window PiP mode. @@ -121,6 +123,20 @@ open class GodotEditor : GodotActivity() { internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO + /** + * Set of permissions to be excluded when requesting all permissions at startup. + * + * The permissions in this set will be requested on demand based on use cases. + */ + @CallSuper + protected open fun getExcludedPermissions(): MutableSet<String> { + return mutableSetOf( + // The RECORD_AUDIO permission is requested when the "audio/driver/enable_input" project + // setting is enabled. + Manifest.permission.RECORD_AUDIO + ) + } + override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() @@ -130,8 +146,8 @@ open class GodotEditor : GodotActivity() { } // We exclude certain permissions from the set we request at startup, as they'll be - // requested on demand based on use-cases. - PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO)) + // requested on demand based on use cases. + PermissionsUtil.requestManifestPermissions(this, getExcludedPermissions()) val params = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS) Log.d(TAG, "Starting intent $intent with parameters ${params.contentToString()}") @@ -151,8 +167,6 @@ open class GodotEditor : GodotActivity() { val longPressEnabled = enableLongPressGestures() val panScaleEnabled = enablePanAndScaleGestures() - checkForProjectPermissionsToEnable() - runOnUiThread { // Enable long press, panning and scaling gestures godotFragment?.godot?.renderView?.inputHandler?.apply { @@ -170,17 +184,6 @@ open class GodotEditor : GodotActivity() { } } - /** - * Check for project permissions to enable - */ - protected open fun checkForProjectPermissionsToEnable() { - // Check for RECORD_AUDIO permission - val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input")) - if (audioInputEnabled) { - PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this) - } - } - @CallSuper protected open fun updateCommandLineParams(args: List<String>) { // Update the list of command line params with the new args @@ -195,7 +198,7 @@ open class GodotEditor : GodotActivity() { final override fun getCommandLine() = commandLineParams - protected open fun getEditorWindowInfo(args: Array<String>): EditorWindowInfo { + protected open fun retrieveEditorWindowInfo(args: Array<String>): EditorWindowInfo { var hasEditor = false var i = 0 @@ -244,30 +247,35 @@ open class GodotEditor : GodotActivity() { val isPiPAvailable = if (editorWindowInfo.supportsPiPMode && hasPiPSystemFeature()) { val pipMode = getPlayWindowPiPMode() pipMode == PLAY_WINDOW_PIP_ENABLED || - (pipMode == PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR && launchPolicy == LaunchPolicy.SAME) + (pipMode == PLAY_WINDOW_PIP_ENABLED_FOR_SAME_AS_EDITOR && + (launchPolicy == LaunchPolicy.SAME || launchPolicy == LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE)) } else { false } newInstance.putExtra(EXTRA_PIP_AVAILABLE, isPiPAvailable) + var launchInPiP = false if (launchPolicy == LaunchPolicy.ADJACENT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { Log.v(TAG, "Adding flag for adjacent launch") newInstance.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT) } } else if (launchPolicy == LaunchPolicy.SAME) { - if (isPiPAvailable && - (updatedArgs.contains(BREAKPOINTS_ARG) || updatedArgs.contains(BREAKPOINTS_ARG_SHORT))) { - Log.v(TAG, "Launching in PiP mode because of breakpoints") - newInstance.putExtra(EXTRA_LAUNCH_IN_PIP, true) - } + launchInPiP = isPiPAvailable && + (updatedArgs.contains(BREAKPOINTS_ARG) || updatedArgs.contains(BREAKPOINTS_ARG_SHORT)) + } else if (launchPolicy == LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE) { + launchInPiP = isPiPAvailable } + if (launchInPiP) { + Log.v(TAG, "Launching in PiP mode") + newInstance.putExtra(EXTRA_LAUNCH_IN_PIP, launchInPiP) + } return newInstance } override fun onNewGodotInstanceRequested(args: Array<String>): Int { - val editorWindowInfo = getEditorWindowInfo(args) + val editorWindowInfo = retrieveEditorWindowInfo(args) // Launch a new activity val sourceView = godotFragment?.view @@ -399,19 +407,26 @@ open class GodotEditor : GodotActivity() { return when (policy) { LaunchPolicy.AUTO -> { - try { - when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) { - ANDROID_WINDOW_SAME_AS_EDITOR -> LaunchPolicy.SAME - ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> LaunchPolicy.ADJACENT - else -> { - // ANDROID_WINDOW_AUTO - defaultLaunchPolicy + if (isHorizonOSDevice()) { + // Horizon OS UX is more desktop-like and has support for launching adjacent + // windows. So we always want to launch in adjacent mode when auto is selected. + LaunchPolicy.ADJACENT + } else { + try { + when (Integer.parseInt(GodotLib.getEditorSetting("run/window_placement/android_window"))) { + ANDROID_WINDOW_SAME_AS_EDITOR -> LaunchPolicy.SAME + ANDROID_WINDOW_SIDE_BY_SIDE_WITH_EDITOR -> LaunchPolicy.ADJACENT + ANDROID_WINDOW_SAME_AS_EDITOR_AND_LAUNCH_IN_PIP_MODE -> LaunchPolicy.SAME_AND_LAUNCH_IN_PIP_MODE + else -> { + // ANDROID_WINDOW_AUTO + defaultLaunchPolicy + } } + } catch (e: NumberFormatException) { + Log.w(TAG, "Error parsing the Android window placement editor setting", e) + // Fall-back to the default launch policy + defaultLaunchPolicy } - } catch (e: NumberFormatException) { - Log.w(TAG, "Error parsing the Android window placement editor setting", e) - // Fall-back to the default launch policy - defaultLaunchPolicy } } @@ -424,8 +439,16 @@ open class GodotEditor : GodotActivity() { /** * Returns true the if the device supports picture-in-picture (PiP) */ - protected open fun hasPiPSystemFeature() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && - packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) + protected open fun hasPiPSystemFeature(): Boolean { + if (isNativeXRDevice()) { + // Known native XR devices do not support PiP. + // Will need to revisit as they update their OS. + return false + } + + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && + packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) + } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt index b16e62149a..f5a6ed7dab 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorMessageDispatcher.kt @@ -42,9 +42,9 @@ import android.util.Log import java.util.concurrent.ConcurrentHashMap /** - * Used by the [GodotEditor] classes to dispatch messages across processes. + * Used by the [BaseGodotEditor] classes to dispatch messages across processes. */ -internal class EditorMessageDispatcher(private val editor: GodotEditor) { +internal class EditorMessageDispatcher(private val editor: BaseGodotEditor) { companion object { private val TAG = EditorMessageDispatcher::class.java.simpleName @@ -173,7 +173,11 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) { // to the sender. val senderId = messengerBundle.getInt(KEY_EDITOR_ID) val senderMessenger: Messenger? = messengerBundle.getParcelable(KEY_EDITOR_MESSENGER) - registerMessenger(senderId, senderMessenger) + registerMessenger(senderId, senderMessenger) { + // Terminate current instance when parent is no longer available. + Log.d(TAG, "Terminating current editor instance because parent is no longer available") + editor.finish() + } // Register ourselves to the sender so that it can communicate with us. registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId) diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt index d3daa1dbbc..2e1de9a607 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/EditorWindowInfo.kt @@ -48,7 +48,12 @@ enum class LaunchPolicy { /** * Adjacent launches are enabled. */ - ADJACENT + ADJACENT, + + /** + * Launches happen in the same window but start in PiP mode. + */ + SAME_AND_LAUNCH_IN_PIP_MODE } /** diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt index 6b4bf255f2..e52d566347 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt @@ -30,6 +30,7 @@ package org.godotengine.editor +import android.Manifest import android.annotation.SuppressLint import android.app.PictureInPictureParams import android.content.Intent @@ -38,12 +39,15 @@ import android.os.Build import android.os.Bundle import android.util.Log import android.view.View +import androidx.annotation.CallSuper import org.godotengine.godot.GodotLib +import org.godotengine.godot.utils.PermissionsUtil +import org.godotengine.godot.utils.ProcessPhoenix /** * Drives the 'run project' window of the Godot Editor. */ -class GodotGame : GodotEditor() { +open class GodotGame : GodotEditor() { companion object { private val TAG = GodotGame::class.java.simpleName @@ -136,8 +140,53 @@ class GodotGame : GodotEditor() { override fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) - override fun checkForProjectPermissionsToEnable() { - // Nothing to do.. by the time we get here, the project permissions will have already - // been requested by the Editor window. + override fun onGodotSetupCompleted() { + super.onGodotSetupCompleted() + Log.v(TAG, "OnGodotSetupCompleted") + + // Check if we should be running in XR instead (if available) as it's possible we were + // launched from the project manager which doesn't have that information. + val launchingArgs = intent.getStringArrayExtra(EXTRA_COMMAND_LINE_PARAMS) + if (launchingArgs != null) { + val editorWindowInfo = retrieveEditorWindowInfo(launchingArgs) + if (editorWindowInfo != getEditorWindowInfo()) { + val relaunchIntent = getNewGodotInstanceIntent(editorWindowInfo, launchingArgs) + relaunchIntent.putExtra(EXTRA_NEW_LAUNCH, true) + .putExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD, intent.getBundleExtra(EditorMessageDispatcher.EXTRA_MSG_DISPATCHER_PAYLOAD)) + + Log.d(TAG, "Relaunching XR project using ${editorWindowInfo.windowClassName} with parameters ${launchingArgs.contentToString()}") + val godot = godot + if (godot != null) { + godot.destroyAndKillProcess { + ProcessPhoenix.triggerRebirth(this, relaunchIntent) + } + } else { + ProcessPhoenix.triggerRebirth(this, relaunchIntent) + } + return + } + } + + // Request project runtime permissions if necessary + val permissionsToEnable = getProjectPermissionsToEnable() + if (permissionsToEnable.isNotEmpty()) { + PermissionsUtil.requestPermissions(this, permissionsToEnable) + } + } + + /** + * Check for project permissions to enable + */ + @CallSuper + protected open fun getProjectPermissionsToEnable(): MutableList<String> { + val permissionsToEnable = mutableListOf<String>() + + // Check for RECORD_AUDIO permission + val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input")) + if (audioInputEnabled) { + permissionsToEnable.add(Manifest.permission.RECORD_AUDIO) + } + + return permissionsToEnable } } diff --git a/platform/android/java/editor/src/meta/AndroidManifest.xml b/platform/android/java/editor/src/meta/AndroidManifest.xml new file mode 100644 index 0000000000..06442ac4e6 --- /dev/null +++ b/platform/android/java/editor/src/meta/AndroidManifest.xml @@ -0,0 +1,99 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:horizonos="http://schemas.horizonos/sdk"> + + <horizonos:uses-horizonos-sdk + horizonos:minSdkVersion="69" + horizonos:targetSdkVersion="69" /> + + <uses-feature + android:name="android.hardware.vr.headtracking" + android:required="true" + android:version="1"/> + + <!-- Oculus Quest hand tracking --> + <uses-permission android:name="com.oculus.permission.HAND_TRACKING" /> + <uses-feature + android:name="oculus.software.handtracking" + android:required="false" /> + + <!-- Passthrough feature flag --> + <uses-feature android:name="com.oculus.feature.PASSTHROUGH" + android:required="false" /> + + <!-- Overlay keyboard support --> + <uses-feature android:name="oculus.software.overlay_keyboard" android:required="false"/> + + <!-- Render model --> + <uses-permission android:name="com.oculus.permission.RENDER_MODEL" /> + <uses-feature android:name="com.oculus.feature.RENDER_MODEL" android:required="false" /> + + <!-- Anchor api --> + <uses-permission android:name="com.oculus.permission.USE_ANCHOR_API" /> + + <!-- Scene api --> + <uses-permission android:name="com.oculus.permission.USE_SCENE" /> + + <application> + + <activity + android:name=".GodotEditor" + android:exported="true" + android:screenOrientation="landscape" + tools:node="merge" + tools:replace="android:screenOrientation"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + <category android:name="com.oculus.intent.category.2D" /> + </intent-filter> + + <meta-data android:name="com.oculus.vrshell.free_resizing_lock_aspect_ratio" android:value="true"/> + </activity> + + <activity + android:name=".GodotXRGame" + android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize|density|keyboard|navigation|screenLayout|uiMode" + android:process=":GodotXRGame" + android:launchMode="singleTask" + android:icon="@mipmap/ic_play_window" + android:label="@string/godot_game_activity_name" + android:exported="false" + android:screenOrientation="landscape" + android:resizeableActivity="false" + android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="com.oculus.intent.category.VR" /> + <category android:name="org.khronos.openxr.intent.category.IMMERSIVE_HMD" /> + </intent-filter> + </activity> + + <!-- Supported Meta devices --> + <meta-data + android:name="com.oculus.supportedDevices" + android:value="quest3|questpro" + tools:replace="android:value" /> + + <!-- + We remove this meta-data originating from the vendors plugin as we only need the loader for + now since the project being edited provides its own version of the vendors plugin. + + This needs to be removed once we start implementing the immersive version of the project + manager and editor windows. + --> + <meta-data + android:name="org.godotengine.plugin.v2.GodotOpenXRMeta" + android:value="org.godotengine.openxr.vendors.meta.GodotOpenXRMeta" + tools:node="remove" /> + + <!-- Enable system splash screen --> + <meta-data android:name="com.oculus.ossplash" android:value="true"/> + <!-- Enable passthrough background during the splash screen --> + <meta-data android:name="com.oculus.ossplash.background" android:value="passthrough-contextual"/> + + </application> + +</manifest> diff --git a/platform/android/java/editor/src/meta/assets/vr_splash.png b/platform/android/java/editor/src/meta/assets/vr_splash.png Binary files differnew file mode 100644 index 0000000000..7bddd4325a --- /dev/null +++ b/platform/android/java/editor/src/meta/assets/vr_splash.png diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt new file mode 100644 index 0000000000..9f0440e87d --- /dev/null +++ b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotEditor.kt @@ -0,0 +1,94 @@ +/**************************************************************************/ +/* GodotEditor.kt */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +package org.godotengine.editor + +import org.godotengine.godot.GodotLib +import org.godotengine.godot.utils.isNativeXRDevice + +/** + * Primary window of the Godot Editor. + * + * This is the implementation of the editor used when running on Meta devices. + */ +open class GodotEditor : BaseGodotEditor() { + + companion object { + private val TAG = GodotEditor::class.java.simpleName + + internal val XR_RUN_GAME_INFO = EditorWindowInfo(GodotXRGame::class.java, 1667, ":GodotXRGame") + + internal const val USE_ANCHOR_API_PERMISSION = "com.oculus.permission.USE_ANCHOR_API" + internal const val USE_SCENE_PERMISSION = "com.oculus.permission.USE_SCENE" + } + + override fun getExcludedPermissions(): MutableSet<String> { + val excludedPermissions = super.getExcludedPermissions() + // The USE_ANCHOR_API and USE_SCENE permissions are requested when the "xr/openxr/enabled" + // project setting is enabled. + excludedPermissions.add(USE_ANCHOR_API_PERMISSION) + excludedPermissions.add(USE_SCENE_PERMISSION) + return excludedPermissions + } + + override fun retrieveEditorWindowInfo(args: Array<String>): EditorWindowInfo { + var hasEditor = false + var xrModeOn = false + + var i = 0 + while (i < args.size) { + when (args[i++]) { + EDITOR_ARG, EDITOR_ARG_SHORT, EDITOR_PROJECT_MANAGER_ARG, EDITOR_PROJECT_MANAGER_ARG_SHORT -> hasEditor = true + XR_MODE_ARG -> { + val argValue = args[i++] + xrModeOn = xrModeOn || ("on" == argValue) + } + } + } + + return if (hasEditor) { + EDITOR_MAIN_INFO + } else { + val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean() + if (openxrEnabled && isNativeXRDevice()) { + XR_RUN_GAME_INFO + } else { + RUN_GAME_INFO + } + } + } + + override fun getEditorWindowInfoForInstanceId(instanceId: Int): EditorWindowInfo? { + return when (instanceId) { + XR_RUN_GAME_INFO.windowId -> XR_RUN_GAME_INFO + else -> super.getEditorWindowInfoForInstanceId(instanceId) + } + } +} diff --git a/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt new file mode 100644 index 0000000000..d71fbb53f2 --- /dev/null +++ b/platform/android/java/editor/src/meta/java/org/godotengine/editor/GodotXRGame.kt @@ -0,0 +1,71 @@ +/*************************************************************************/ +/* GodotXRGame.kt */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +package org.godotengine.editor + +import org.godotengine.godot.GodotLib +import org.godotengine.godot.utils.PermissionsUtil +import org.godotengine.godot.xr.XRMode + +/** + * Provide support for running XR apps / games from the editor window. + */ +open class GodotXRGame: GodotGame() { + + override fun overrideOrientationRequest() = true + + override fun updateCommandLineParams(args: List<String>) { + val updatedArgs = ArrayList<String>() + if (!args.contains(XRMode.OPENXR.cmdLineArg)) { + updatedArgs.add(XRMode.OPENXR.cmdLineArg) + } + if (!args.contains(XR_MODE_ARG)) { + updatedArgs.add(XR_MODE_ARG) + updatedArgs.add("on") + } + updatedArgs.addAll(args) + + super.updateCommandLineParams(updatedArgs) + } + + override fun getEditorWindowInfo() = XR_RUN_GAME_INFO + + override fun getProjectPermissionsToEnable(): MutableList<String> { + val permissionsToEnable = super.getProjectPermissionsToEnable() + + val openxrEnabled = GodotLib.getGlobal("xr/openxr/enabled").toBoolean() + if (openxrEnabled) { + permissionsToEnable.add(USE_ANCHOR_API_PERMISSION) + permissionsToEnable.add(USE_SCENE_PERMISSION) + } + + return permissionsToEnable + } +} diff --git a/platform/android/java/lib/build.gradle b/platform/android/java/lib/build.gradle index 81ab598b90..f6aee434e5 100644 --- a/platform/android/java/lib/build.gradle +++ b/platform/android/java/lib/build.gradle @@ -51,7 +51,7 @@ android { } } - flavorDimensions "products" + flavorDimensions = ["products"] productFlavors { editor {} template {} @@ -104,7 +104,7 @@ android { } boolean devBuild = buildType == "dev" - boolean debugSymbols = devBuild || isAndroidStudio() + boolean debugSymbols = devBuild boolean runTests = devBuild boolean productionBuild = !devBuild boolean storeRelease = buildType == "release" diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java index 30821eaa8e..9db9ef6080 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -31,6 +31,7 @@ package org.godotengine.godot; import org.godotengine.godot.input.GodotInputHandler; +import org.godotengine.godot.utils.DeviceUtils; import android.view.SurfaceView; @@ -63,7 +64,11 @@ public interface GodotRenderView { void setPointerIcon(int pointerType); + /** + * @return true if pointer capture is supported. + */ default boolean canCapturePointer() { - return getInputHandler().canCapturePointer(); + // Pointer capture is not supported on Horizon OS + return !DeviceUtils.isHorizonOSDevice() && getInputHandler().canCapturePointer(); } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index d5b05913d8..8b3880d32d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -68,6 +68,7 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_DEFAULT)); } setFocusableInTouchMode(true); + setClickable(false); } @Override @@ -132,17 +133,17 @@ class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderView { @Override public boolean onKeyUp(final int keyCode, KeyEvent event) { - return mInputHandler.onKeyUp(keyCode, event); + return mInputHandler.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); } @Override public boolean onKeyDown(final int keyCode, KeyEvent event) { - return mInputHandler.onKeyDown(keyCode, event); + return mInputHandler.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); } @Override public boolean onGenericMotionEvent(MotionEvent event) { - return mInputHandler.onGenericMotionEvent(event); + return mInputHandler.onGenericMotionEvent(event) || super.onGenericMotionEvent(event); } @Override diff --git a/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt new file mode 100644 index 0000000000..abe0c5f885 --- /dev/null +++ b/platform/android/java/lib/src/org/godotengine/godot/utils/DeviceUtils.kt @@ -0,0 +1,52 @@ +/**************************************************************************/ +/* DeviceUtils.kt */ +/**************************************************************************/ +/* 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. */ +/**************************************************************************/ + +/** + * Contains utility methods for detecting specific devices. + */ +@file:JvmName("DeviceUtils") + +package org.godotengine.godot.utils + +import android.os.Build + +/** + * Returns true if running on Meta's Horizon OS. + */ +fun isHorizonOSDevice(): Boolean { + return "Oculus".equals(Build.BRAND, true) +} + +/** + * Returns true if running on a native Android XR device. + */ +fun isNativeXRDevice(): Boolean { + return isHorizonOSDevice() +} diff --git a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt index 96b6dfc9f3..a7d2774db5 100644 --- a/platform/android/java/nativeSrcsConfigs/CMakeLists.txt +++ b/platform/android/java/nativeSrcsConfigs/CMakeLists.txt @@ -8,6 +8,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(GODOT_ROOT_DIR ../../../..) set(ANDROID_ROOT_DIR "${GODOT_ROOT_DIR}/platform/android" CACHE STRING "") +set(OPENXR_INCLUDE_DIR "${GODOT_ROOT_DIR}/thirdparty/openxr/include" CACHE STRING "") # Get sources file(GLOB_RECURSE SOURCES ${GODOT_ROOT_DIR}/*.c**) @@ -17,6 +18,7 @@ add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${GODOT_ROOT_DIR} - ${ANDROID_ROOT_DIR}) + ${ANDROID_ROOT_DIR} + ${OPENXR_INCLUDE_DIR}) add_definitions(-DUNIX_ENABLED -DVULKAN_ENABLED -DANDROID_ENABLED -DGLES3_ENABLED -DTOOLS_ENABLED) diff --git a/platform/android/java/settings.gradle b/platform/android/java/settings.gradle index 3137e74244..41418a4a45 100644 --- a/platform/android/java/settings.gradle +++ b/platform/android/java/settings.gradle @@ -14,6 +14,7 @@ pluginManagement { mavenCentral() gradlePluginPortal() maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/"} } } diff --git a/platform/android/java_class_wrapper.cpp b/platform/android/java_class_wrapper.cpp index a309a6ab74..c92717e922 100644 --- a/platform/android/java_class_wrapper.cpp +++ b/platform/android/java_class_wrapper.cpp @@ -44,7 +44,7 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, MethodInfo *method = nullptr; for (MethodInfo &E : M->value) { - if (!p_instance && !E._static) { + if (!p_instance && !E._static && !E._constructor) { r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; continue; } @@ -97,20 +97,24 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } } break; case ARG_TYPE_STRING: { - if (p_args[i]->get_type() != Variant::STRING) { + if (!p_args[i]->is_string()) { arg_expected = Variant::STRING; } } break; case ARG_TYPE_CLASS: { - if (p_args[i]->get_type() != Variant::OBJECT) { + if (p_args[i]->get_type() != Variant::OBJECT && p_args[i]->get_type() != Variant::NIL) { arg_expected = Variant::OBJECT; } else { Ref<RefCounted> ref = *p_args[i]; - if (!ref.is_null()) { + if (ref.is_valid()) { if (Object::cast_to<JavaObject>(ref.ptr())) { Ref<JavaObject> jo = ref; //could be faster - jclass c = env->FindClass(E.param_sigs[i].operator String().utf8().get_data()); + String cn = E.param_sigs[i].operator String(); + if (cn.begins_with("L") && cn.ends_with(";")) { + cn = cn.substr(1, cn.length() - 2); + } + jclass c = env->FindClass(cn.utf8().get_data()); if (!c || !env->IsInstanceOf(jo->instance, c)) { arg_expected = Variant::OBJECT; } else { @@ -458,7 +462,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, } break; default: { jobject obj; - if (method->_static) { + if (method->_constructor) { + obj = env->NewObject(_class, method->method, argv); + } else if (method->_static) { obj = env->CallStaticObjectMethodA(_class, method->method, argv); } else { obj = env->CallObjectMethodA(p_instance->instance, method->method, argv); @@ -487,7 +493,9 @@ bool JavaClass::_call_method(JavaObject *p_instance, const StringName &p_method, Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { Variant ret; - bool found = _call_method(nullptr, p_method, p_args, p_argcount, r_error, ret); + + String method = (p_method == java_constructor_name) ? "<init>" : p_method; + bool found = _call_method(nullptr, method, p_args, p_argcount, r_error, ret); if (found) { return ret; } @@ -495,19 +503,156 @@ Variant JavaClass::callp(const StringName &p_method, const Variant **p_args, int return RefCounted::callp(p_method, p_args, p_argcount, r_error); } +String JavaClass::get_java_class_name() const { + return java_class_name; +} + +TypedArray<Dictionary> JavaClass::get_java_method_list() const { + TypedArray<Dictionary> method_list; + + for (const KeyValue<StringName, List<MethodInfo>> &item : methods) { + for (const MethodInfo &mi : item.value) { + Dictionary method; + + method["name"] = mi._constructor ? java_constructor_name : String(item.key); + method["id"] = (uint64_t)mi.method; + method["default_args"] = Array(); + method["flags"] = METHOD_FLAGS_DEFAULT & (mi._static || mi._constructor ? METHOD_FLAG_STATIC : METHOD_FLAG_NORMAL); + + { + Array a; + + for (uint32_t argtype : mi.param_types) { + Dictionary d; + + Variant::Type t = Variant::NIL; + float likelihood = 0.0; + _convert_to_variant_type(argtype, t, likelihood); + d["type"] = t; + if (t == Variant::OBJECT) { + d["hint"] = PROPERTY_HINT_RESOURCE_TYPE; + d["hint_string"] = "JavaObject"; + } else { + d["hint"] = 0; + d["hint_string"] = ""; + } + + a.push_back(d); + } + + method["args"] = a; + } + + { + Dictionary d; + + if (mi._constructor) { + d["type"] = Variant::OBJECT; + d["hint"] = PROPERTY_HINT_RESOURCE_TYPE; + d["hint_string"] = "JavaObject"; + } else { + Variant::Type t = Variant::NIL; + float likelihood = 0.0; + _convert_to_variant_type(mi.return_type, t, likelihood); + d["type"] = t; + if (t == Variant::OBJECT) { + d["hint"] = PROPERTY_HINT_RESOURCE_TYPE; + d["hint_string"] = "JavaObject"; + } else { + d["hint"] = 0; + d["hint_string"] = ""; + } + } + + method["return_type"] = d; + } + + method_list.push_back(method); + } + } + + return method_list; +} + +Ref<JavaClass> JavaClass::get_java_parent_class() const { + ERR_FAIL_NULL_V(_class, Ref<JavaClass>()); + + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, Ref<JavaClass>()); + + jclass superclass = (jclass)env->CallObjectMethod(_class, JavaClassWrapper::singleton->Class_getSuperclass); + if (!superclass) { + return Ref<JavaClass>(); + } + + Ref<JavaClass> ret = JavaClassWrapper::singleton->wrap_jclass(superclass); + env->DeleteLocalRef(superclass); + return ret; +} + +String JavaClass::to_string() { + return "<JavaClass:" + java_class_name + ">"; +} + JavaClass::JavaClass() { } +JavaClass::~JavaClass() { + if (_class) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(_class); + } +} + ///////////////////// Variant JavaObject::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - return Variant(); + if (instance) { + Ref<JavaClass> c = base_class; + while (c.is_valid()) { + Variant ret; + bool found = c->_call_method(this, p_method, p_args, p_argcount, r_error, ret); + if (found) { + return ret; + } + c = c->get_java_parent_class(); + } + } + + return RefCounted::callp(p_method, p_args, p_argcount, r_error); +} + +Ref<JavaClass> JavaObject::get_java_class() const { + return base_class; } -JavaObject::JavaObject(const Ref<JavaClass> &p_base, jobject *p_instance) { +String JavaObject::to_string() { + if (base_class.is_valid() && instance) { + return "<JavaObject:" + base_class->java_class_name + " \"" + (String)call("toString") + "\">"; + } + return RefCounted::to_string(); +} + +JavaObject::JavaObject() { +} + +JavaObject::JavaObject(const Ref<JavaClass> &p_base, jobject p_instance) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + base_class = p_base; + instance = env->NewGlobalRef(p_instance); } JavaObject::~JavaObject() { + if (instance) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + + env->DeleteGlobalRef(instance); + } } //////////////////// @@ -649,6 +794,16 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va return true; } break; case ARG_TYPE_CLASS: { + jclass java_class = env->GetObjectClass(obj); + Ref<JavaClass> java_class_wrapped = JavaClassWrapper::singleton->wrap_jclass(java_class); + env->DeleteLocalRef(java_class); + + if (java_class_wrapped.is_valid()) { + Ref<JavaObject> ret = Ref<JavaObject>(memnew(JavaObject(java_class_wrapped, obj))); + var = ret; + return true; + } + return false; } break; case ARG_ARRAY_BIT | ARG_TYPE_VOID: { @@ -966,43 +1121,67 @@ bool JavaClass::_convert_object_to_variant(JNIEnv *env, jobject obj, Variant &va } Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { - if (class_cache.has(p_class)) { - return class_cache[p_class]; + String class_name_dots = p_class.replace("/", "."); + if (class_cache.has(class_name_dots)) { + return class_cache[class_name_dots]; } JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, Ref<JavaClass>()); - jclass bclass = env->FindClass(p_class.utf8().get_data()); + jclass bclass = env->FindClass(class_name_dots.replace(".", "/").utf8().get_data()); ERR_FAIL_NULL_V(bclass, Ref<JavaClass>()); - jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, getDeclaredMethods); + jobjectArray constructors = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredConstructors); + ERR_FAIL_NULL_V(constructors, Ref<JavaClass>()); + jobjectArray methods = (jobjectArray)env->CallObjectMethod(bclass, Class_getDeclaredMethods); ERR_FAIL_NULL_V(methods, Ref<JavaClass>()); Ref<JavaClass> java_class = memnew(JavaClass); + java_class->java_class_name = class_name_dots; + Vector<String> class_name_parts = class_name_dots.split("."); + java_class->java_constructor_name = class_name_parts[class_name_parts.size() - 1]; + java_class->_class = (jclass)env->NewGlobalRef(bclass); + class_cache[class_name_dots] = java_class; + + LocalVector<jobject> methods_and_constructors; + int constructor_count = env->GetArrayLength(constructors); + int method_count = env->GetArrayLength(methods); + methods_and_constructors.resize(method_count + constructor_count); + for (int i = 0; i < constructor_count; i++) { + methods_and_constructors[i] = env->GetObjectArrayElement(constructors, i); + } + for (int i = 0; i < method_count; i++) { + methods_and_constructors[constructor_count + i] = env->GetObjectArrayElement(methods, i); + } - int count = env->GetArrayLength(methods); - - for (int i = 0; i < count; i++) { - jobject obj = env->GetObjectArrayElement(methods, i); + for (int i = 0; i < (int)methods_and_constructors.size(); i++) { + jobject obj = methods_and_constructors[i]; ERR_CONTINUE(!obj); - jstring name = (jstring)env->CallObjectMethod(obj, getName); - String str_method = jstring_to_string(name, env); - env->DeleteLocalRef(name); + bool is_constructor = i < constructor_count; + + String str_method; + if (is_constructor) { + str_method = "<init>"; + } else { + jstring name = (jstring)env->CallObjectMethod(obj, Method_getName); + str_method = jstring_to_string(name, env); + env->DeleteLocalRef(name); + } Vector<String> params; - jint mods = env->CallIntMethod(obj, getModifiers); + jint mods = env->CallIntMethod(obj, is_constructor ? Constructor_getModifiers : Method_getModifiers); if (!(mods & 0x0001)) { env->DeleteLocalRef(obj); continue; //not public bye } - jobjectArray param_types = (jobjectArray)env->CallObjectMethod(obj, getParameterTypes); - int count2 = env->GetArrayLength(param_types); + jobjectArray param_types = (jobjectArray)env->CallObjectMethod(obj, is_constructor ? Constructor_getParameterTypes : Method_getParameterTypes); + int count = env->GetArrayLength(param_types); if (!java_class->methods.has(str_method)) { java_class->methods[str_method] = List<JavaClass::MethodInfo>(); @@ -1010,10 +1189,11 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { JavaClass::MethodInfo mi; mi._static = (mods & 0x8) != 0; + mi._constructor = is_constructor; bool valid = true; String signature = "("; - for (int j = 0; j < count2; j++) { + for (int j = 0; j < count; j++) { jobject obj2 = env->GetObjectArrayElement(param_types, j); String strsig; uint32_t sig = 0; @@ -1029,7 +1209,7 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { } if (!valid) { - print_line("Method can't be bound (unsupported arguments): " + p_class + "::" + str_method); + print_line("Method can't be bound (unsupported arguments): " + class_name_dots + "::" + str_method); env->DeleteLocalRef(obj); env->DeleteLocalRef(param_types); continue; @@ -1037,21 +1217,28 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { signature += ")"; - jobject return_type = (jobject)env->CallObjectMethod(obj, getReturnType); + if (is_constructor) { + signature += "V"; + mi.return_type = JavaClass::ARG_TYPE_CLASS; + } else { + jobject return_type = (jobject)env->CallObjectMethod(obj, Method_getReturnType); + + String strsig; + uint32_t sig = 0; + if (!_get_type_sig(env, return_type, sig, strsig)) { + print_line("Method can't be bound (unsupported return type): " + class_name_dots + "::" + str_method); + env->DeleteLocalRef(obj); + env->DeleteLocalRef(param_types); + env->DeleteLocalRef(return_type); + continue; + } + + signature += strsig; + mi.return_type = sig; - String strsig; - uint32_t sig = 0; - if (!_get_type_sig(env, return_type, sig, strsig)) { - print_line("Method can't be bound (unsupported return type): " + p_class + "::" + str_method); - env->DeleteLocalRef(obj); - env->DeleteLocalRef(param_types); env->DeleteLocalRef(return_type); - continue; } - signature += strsig; - mi.return_type = sig; - bool discard = false; for (List<JavaClass::MethodInfo>::Element *E = java_class->methods[str_method].front(); E; E = E->next()) { @@ -1103,14 +1290,14 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { env->DeleteLocalRef(obj); env->DeleteLocalRef(param_types); - env->DeleteLocalRef(return_type); } + env->DeleteLocalRef(constructors); env->DeleteLocalRef(methods); - jobjectArray fields = (jobjectArray)env->CallObjectMethod(bclass, getFields); + jobjectArray fields = (jobjectArray)env->CallObjectMethod(bclass, Class_getFields); - count = env->GetArrayLength(fields); + int count = env->GetArrayLength(fields); for (int i = 0; i < count; i++) { jobject obj = env->GetObjectArrayElement(fields, i); @@ -1146,7 +1333,18 @@ Ref<JavaClass> JavaClassWrapper::wrap(const String &p_class) { env->DeleteLocalRef(fields); - return Ref<JavaClass>(); + return java_class; +} + +Ref<JavaClass> JavaClassWrapper::wrap_jclass(jclass p_class) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, Ref<JavaClass>()); + + jstring class_name = (jstring)env->CallObjectMethod(p_class, Class_getName); + String class_name_string = jstring_to_string(class_name, env); + env->DeleteLocalRef(class_name); + + return wrap(class_name_string); } JavaClassWrapper *JavaClassWrapper::singleton = nullptr; @@ -1158,16 +1356,23 @@ JavaClassWrapper::JavaClassWrapper(jobject p_activity) { ERR_FAIL_NULL(env); jclass bclass = env->FindClass("java/lang/Class"); - getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;"); - getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;"); + Class_getDeclaredConstructors = env->GetMethodID(bclass, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;"); + Class_getDeclaredMethods = env->GetMethodID(bclass, "getDeclaredMethods", "()[Ljava/lang/reflect/Method;"); + Class_getFields = env->GetMethodID(bclass, "getFields", "()[Ljava/lang/reflect/Field;"); Class_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); + Class_getSuperclass = env->GetMethodID(bclass, "getSuperclass", "()Ljava/lang/Class;"); + env->DeleteLocalRef(bclass); + + bclass = env->FindClass("java/lang/reflect/Constructor"); + Constructor_getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;"); + Constructor_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I"); env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/reflect/Method"); - getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;"); - getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;"); - getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); - getModifiers = env->GetMethodID(bclass, "getModifiers", "()I"); + Method_getParameterTypes = env->GetMethodID(bclass, "getParameterTypes", "()[Ljava/lang/Class;"); + Method_getReturnType = env->GetMethodID(bclass, "getReturnType", "()Ljava/lang/Class;"); + Method_getName = env->GetMethodID(bclass, "getName", "()Ljava/lang/String;"); + Method_getModifiers = env->GetMethodID(bclass, "getModifiers", "()I"); env->DeleteLocalRef(bclass); bclass = env->FindClass("java/lang/reflect/Field"); diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 1114969de8..390677df22 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -51,7 +51,10 @@ #include "core/config/project_settings.h" #include "core/input/input.h" #include "main/main.h" + +#ifndef _3D_DISABLED #include "servers/xr_server.h" +#endif // _3D_DISABLED #ifdef TOOLS_ENABLED #include "editor/editor_settings.h" @@ -271,14 +274,16 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_step(JNIEnv *env, } if (step.get() == STEP_SHOW_LOGO) { - bool xr_enabled; + bool xr_enabled = false; +#ifndef _3D_DISABLED + // Unlike PCVR, there's no additional 2D screen onto which to render the boot logo, + // so we skip this step if xr is enabled. if (XRServer::get_xr_mode() == XRServer::XRMODE_DEFAULT) { xr_enabled = GLOBAL_GET("xr/shaders/enabled"); } else { xr_enabled = XRServer::get_xr_mode() == XRServer::XRMODE_ON; } - // Unlike PCVR, there's no additional 2D screen onto which to render the boot logo, - // so we skip this step if xr is enabled. +#endif // _3D_DISABLED if (!xr_enabled) { Main::setup_boot_logo(); } diff --git a/platform/android/jni_utils.cpp b/platform/android/jni_utils.cpp index fc97d6eca4..4c17d03c60 100644 --- a/platform/android/jni_utils.cpp +++ b/platform/android/jni_utils.cpp @@ -30,6 +30,8 @@ #include "jni_utils.h" +#include "api/java_class_wrapper.h" + jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_arg, bool force_jobject) { jvalret v; @@ -185,6 +187,16 @@ jvalret _variant_to_jvalue(JNIEnv *env, Variant::Type p_type, const Variant *p_a v.obj = arr; } break; + case Variant::OBJECT: { + Ref<JavaObject> generic_object = *p_arg; + if (generic_object.is_valid()) { + jobject obj = env->NewLocalRef(generic_object->get_instance()); + v.val.l = obj; + v.obj = obj; + } else { + v.val.i = 0; + } + } break; default: { v.val.i = 0; @@ -358,9 +370,11 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj) { return ret; } + Ref<JavaObject> generic_object(memnew(JavaObject(JavaClassWrapper::get_singleton()->wrap(name), obj))); + env->DeleteLocalRef(c); - return Variant(); + return generic_object; } Variant::Type get_jni_type(const String &p_type) { @@ -395,10 +409,10 @@ Variant::Type get_jni_type(const String &p_type) { idx++; } - return Variant::NIL; + return Variant::OBJECT; } -const char *get_jni_sig(const String &p_type) { +String get_jni_sig(const String &p_type) { static struct { const char *name; const char *sig; @@ -430,5 +444,5 @@ const char *get_jni_sig(const String &p_type) { idx++; } - return "Ljava/lang/Object;"; + return "L" + p_type.replace(".", "/") + ";"; } diff --git a/platform/android/jni_utils.h b/platform/android/jni_utils.h index c608f9ebaa..631acd1cef 100644 --- a/platform/android/jni_utils.h +++ b/platform/android/jni_utils.h @@ -52,6 +52,6 @@ Variant _jobject_to_variant(JNIEnv *env, jobject obj); Variant::Type get_jni_type(const String &p_type); -const char *get_jni_sig(const String &p_type); +String get_jni_sig(const String &p_type); #endif // JNI_UTILS_H diff --git a/platform/android/plugin/godot_plugin_jni.cpp b/platform/android/plugin/godot_plugin_jni.cpp index e3cde145cb..75c8dd9528 100644 --- a/platform/android/plugin/godot_plugin_jni.cpp +++ b/platform/android/plugin/godot_plugin_jni.cpp @@ -35,7 +35,6 @@ #include "string_android.h" #include "core/config/engine.h" -#include "core/config/project_settings.h" #include "core/error/error_macros.h" static HashMap<String, JNISingleton *> jni_singletons; @@ -43,7 +42,6 @@ static HashMap<String, JNISingleton *> jni_singletons; void unregister_plugins_singletons() { for (const KeyValue<String, JNISingleton *> &E : jni_singletons) { Engine::get_singleton()->remove_singleton(E.key); - ProjectSettings::get_singleton()->set(E.key, Variant()); if (E.value) { memdelete(E.value); @@ -64,7 +62,6 @@ JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_plugin_GodotPlugin_nativeR jni_singletons[singname] = s; Engine::get_singleton()->add_singleton(Engine::Singleton(singname, s)); - ProjectSettings::get_singleton()->set(singname, s); return true; } diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 93096fcdcc..2074d7228d 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -480,7 +480,7 @@ String DisplayServerWayland::clipboard_get_primary() const { for (String mime : text_mimes) { if (wayland_thread.primary_has_mime(mime)) { print_verbose(vformat("Selecting media type \"%s\" from offered types.", mime)); - wayland_thread.primary_get_mime(mime); + data = wayland_thread.primary_get_mime(mime); break; } } @@ -1349,23 +1349,39 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win rendering_driver = p_rendering_driver; + bool driver_found = false; + String executable_name = OS::get_singleton()->get_executable_path().get_file(); + #ifdef RD_ENABLED #ifdef VULKAN_ENABLED if (rendering_driver == "vulkan") { rendering_context = memnew(RenderingContextDriverVulkanWayland); } -#endif +#endif // VULKAN_ENABLED if (rendering_context) { if (rendering_context->initialize() != OK) { - ERR_PRINT(vformat("Could not initialize %s", rendering_driver)); memdelete(rendering_context); rendering_context = nullptr; r_error = ERR_CANT_CREATE; - return; + + if (p_rendering_driver == "vulkan") { + OS::get_singleton()->alert( + vformat("Your video card drivers seem not to support the required Vulkan version.\n\n" + "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n" + "You can enable the OpenGL 3 driver by starting the engine from the\n" + "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n" + "If you recently updated your video card drivers, try rebooting.", + executable_name), + "Unable to initialize Vulkan video driver"); + } + + ERR_FAIL_MSG(vformat("Could not initialize %s", rendering_driver)); } + + driver_found = true; } -#endif +#endif // RD_ENABLED #ifdef GLES3_ENABLED if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") { @@ -1429,28 +1445,56 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win if (fallback) { WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES."); rendering_driver = "opengl3_es"; + OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); } else { r_error = ERR_UNAVAILABLE; + + OS::get_singleton()->alert( + vformat("Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n" + "If possible, consider updating your video card drivers or using the Vulkan driver.\n\n" + "You can enable the Vulkan driver by starting the engine from the\n" + "command line with the command:\n\n \"%s\" --rendering-driver vulkan\n\n" + "If you recently updated your video card drivers, try rebooting.", + executable_name), + "Unable to initialize OpenGL video driver"); + ERR_FAIL_MSG("Could not initialize OpenGL."); } } else { RasterizerGLES3::make_current(true); + driver_found = true; } } if (rendering_driver == "opengl3_es") { egl_manager = memnew(EGLManagerWaylandGLES); - if (egl_manager->initialize(wayland_thread.get_wl_display()) != OK) { + if (egl_manager->initialize(wayland_thread.get_wl_display()) != OK || egl_manager->open_display(wayland_thread.get_wl_display()) != OK) { memdelete(egl_manager); egl_manager = nullptr; r_error = ERR_CANT_CREATE; - ERR_FAIL_MSG("Could not initialize GLES3."); + + OS::get_singleton()->alert( + vformat("Your video card drivers seem not to support the required OpenGL ES 3.0 version.\n\n" + "If possible, consider updating your video card drivers or using the Vulkan driver.\n\n" + "You can enable the Vulkan driver by starting the engine from the\n" + "command line with the command:\n\n \"%s\" --rendering-driver vulkan\n\n" + "If you recently updated your video card drivers, try rebooting.", + executable_name), + "Unable to initialize OpenGL ES video driver"); + + ERR_FAIL_MSG("Could not initialize OpenGL ES."); } RasterizerGLES3::make_current(false); + driver_found = true; } } + + if (!driver_found) { + r_error = ERR_UNAVAILABLE; + ERR_FAIL_MSG("Video driver not found."); + } #endif // GLES3_ENABLED cursor_set_shape(CURSOR_BUSY); @@ -1481,12 +1525,12 @@ DisplayServerWayland::DisplayServerWayland(const String &p_rendering_driver, Win RendererCompositorRD::make_current(); } -#endif +#endif // RD_ENABLED #ifdef DBUS_ENABLED portal_desktop = memnew(FreeDesktopPortalDesktop); screensaver = memnew(FreeDesktopScreenSaver); -#endif +#endif // DBUS_ENABLED screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp index 8a2f83be2d..840cadace3 100644 --- a/platform/linuxbsd/x11/display_server_x11.cpp +++ b/platform/linuxbsd/x11/display_server_x11.cpp @@ -5428,25 +5428,6 @@ Vector<String> DisplayServerX11::get_rendering_drivers_func() { DisplayServer *DisplayServerX11::create_func(const String &p_rendering_driver, WindowMode p_mode, VSyncMode p_vsync_mode, uint32_t p_flags, const Vector2i *p_position, const Vector2i &p_resolution, int p_screen, Context p_context, Error &r_error) { DisplayServer *ds = memnew(DisplayServerX11(p_rendering_driver, p_mode, p_vsync_mode, p_flags, p_position, p_resolution, p_screen, p_context, r_error)); - if (r_error != OK) { - if (p_rendering_driver == "vulkan") { - String executable_name = OS::get_singleton()->get_executable_path().get_file(); - OS::get_singleton()->alert( - vformat("Your video card drivers seem not to support the required Vulkan version.\n\n" - "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n" - "You can enable the OpenGL 3 driver by starting the engine from the\n" - "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n" - "If you recently updated your video card drivers, try rebooting.", - executable_name), - "Unable to initialize Vulkan video driver"); - } else { - OS::get_singleton()->alert( - "Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n" - "If possible, consider updating your video card drivers.\n\n" - "If you recently updated your video card drivers, try rebooting.", - "Unable to initialize OpenGL video driver"); - } - } return ds; } @@ -6160,25 +6141,40 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode rendering_driver = p_rendering_driver; bool driver_found = false; + String executable_name = OS::get_singleton()->get_executable_path().get_file(); + + // Initialize context and rendering device. + #if defined(RD_ENABLED) #if defined(VULKAN_ENABLED) if (rendering_driver == "vulkan") { rendering_context = memnew(RenderingContextDriverVulkanX11); } -#endif +#endif // VULKAN_ENABLED if (rendering_context) { if (rendering_context->initialize() != OK) { - ERR_PRINT(vformat("Could not initialize %s", rendering_driver)); memdelete(rendering_context); rendering_context = nullptr; r_error = ERR_CANT_CREATE; - return; + + if (p_rendering_driver == "vulkan") { + OS::get_singleton()->alert( + vformat("Your video card drivers seem not to support the required Vulkan version.\n\n" + "If possible, consider updating your video card drivers or using the OpenGL 3 driver.\n\n" + "You can enable the OpenGL 3 driver by starting the engine from the\n" + "command line with the command:\n\n \"%s\" --rendering-driver opengl3\n\n" + "If you recently updated your video card drivers, try rebooting.", + executable_name), + "Unable to initialize Vulkan video driver"); + } + + ERR_FAIL_MSG(vformat("Could not initialize %s", rendering_driver)); } driver_found = true; } -#endif - // Initialize context and rendering device. +#endif // RD_ENABLED + #if defined(GLES3_ENABLED) if (rendering_driver == "opengl3" || rendering_driver == "opengl3_es") { if (getenv("DRI_PRIME") == nullptr) { @@ -6231,8 +6227,19 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode if (fallback) { WARN_PRINT("Your video card drivers seem not to support the required OpenGL version, switching to OpenGLES."); rendering_driver = "opengl3_es"; + OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); } else { r_error = ERR_UNAVAILABLE; + + OS::get_singleton()->alert( + vformat("Your video card drivers seem not to support the required OpenGL 3.3 version.\n\n" + "If possible, consider updating your video card drivers or using the Vulkan driver.\n\n" + "You can enable the Vulkan driver by starting the engine from the\n" + "command line with the command:\n\n \"%s\" --rendering-driver vulkan\n\n" + "If you recently updated your video card drivers, try rebooting.", + executable_name), + "Unable to initialize OpenGL video driver"); + ERR_FAIL_MSG("Could not initialize OpenGL."); } } else { @@ -6243,20 +6250,28 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode if (rendering_driver == "opengl3_es") { gl_manager_egl = memnew(GLManagerEGL_X11); - if (gl_manager_egl->initialize() != OK) { + if (gl_manager_egl->initialize() != OK || gl_manager_egl->open_display(x11_display) != OK) { memdelete(gl_manager_egl); gl_manager_egl = nullptr; r_error = ERR_UNAVAILABLE; - ERR_FAIL_MSG("Could not initialize OpenGLES."); + + OS::get_singleton()->alert( + "Your video card drivers seem not to support the required OpenGL ES 3.0 version.\n\n" + "If possible, consider updating your video card drivers.\n\n" + "If you recently updated your video card drivers, try rebooting.", + "Unable to initialize OpenGL ES video driver"); + + ERR_FAIL_MSG("Could not initialize OpenGL ES."); } driver_found = true; RasterizerGLES3::make_current(false); } -#endif +#endif // GLES3_ENABLED + if (!driver_found) { r_error = ERR_UNAVAILABLE; - ERR_FAIL_MSG("Video driver not found"); + ERR_FAIL_MSG("Video driver not found."); } Point2i window_position; @@ -6297,7 +6312,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode RendererCompositorRD::make_current(); } -#endif +#endif // RD_ENABLED { //set all event master mask @@ -6450,7 +6465,8 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode screen_set_keep_on(GLOBAL_GET("display/window/energy_saving/keep_screen_on")); portal_desktop = memnew(FreeDesktopPortalDesktop); -#endif +#endif // DBUS_ENABLED + XSetErrorHandler(&default_window_error_handler); r_error = OK; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index 989a9dcf6c..52dc51bc96 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -3615,6 +3615,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM WARN_PRINT("Your video card drivers seem not to support GLES3 / ANGLE or ANGLE dynamic libraries (libEGL.dylib and libGLESv2.dylib) are missing, switching to native OpenGL."); #endif rendering_driver = "opengl3"; + OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); } else { r_error = ERR_UNAVAILABLE; ERR_FAIL_MSG("Could not initialize ANGLE OpenGL."); diff --git a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml index 34ad52bbf6..c261c252ba 100644 --- a/platform/macos/doc_classes/EditorExportPlatformMacOS.xml +++ b/platform/macos/doc_classes/EditorExportPlatformMacOS.xml @@ -38,8 +38,11 @@ <member name="application/icon_interpolation" type="int" setter="" getter=""> Interpolation method used to resize application icon. </member> - <member name="application/min_macos_version" type="String" setter="" getter=""> - Minimum version of macOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + <member name="application/min_macos_version_arm64" type="String" setter="" getter=""> + Minimum version of macOS required for this application to run on Apple Silicon Macs, in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + </member> + <member name="application/min_macos_version_x86_64" type="String" setter="" getter=""> + Minimum version of macOS required for this application to run on Intel Macs, in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). </member> <member name="application/short_version" type="String" setter="" getter=""> Application version visible to the user, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). Falls back to [member ProjectSettings.application/config/version] if left empty. diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 8372600ae9..770871ae19 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -451,7 +451,8 @@ void EditorExportPlatformMacOS::get_export_options(List<ExportOption> *r_options r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary())); - r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version"), "10.12")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version_x86_64"), "10.12")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version_arm64"), "11.00")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_angle", PROPERTY_HINT_ENUM, "Auto,Yes,No"), 0, true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "display/high_res"), true)); @@ -823,8 +824,12 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref<EditorExportPreset> &p_pres strnew += lines[i].replace("$app_category", cat.to_lower()) + "\n"; } else if (lines[i].contains("$copyright")) { strnew += lines[i].replace("$copyright", p_preset->get("application/copyright")) + "\n"; + } else if (lines[i].contains("$min_version_arm64")) { + strnew += lines[i].replace("$min_version_arm64", p_preset->get("application/min_macos_version_arm64")) + "\n"; + } else if (lines[i].contains("$min_version_x86_64")) { + strnew += lines[i].replace("$min_version_x86_64", p_preset->get("application/min_macos_version_x86_64")) + "\n"; } else if (lines[i].contains("$min_version")) { - strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version")) + "\n"; + strnew += lines[i].replace("$min_version", p_preset->get("application/min_macos_version_x86_64")) + "\n"; // Old template, use x86-64 version for both. } else if (lines[i].contains("$highres")) { strnew += lines[i].replace("$highres", p_preset->get("display/high_res") ? "\t<true/>" : "\t<false/>") + "\n"; } else if (lines[i].contains("$additional_plist_content")) { diff --git a/platform/web/api/api.cpp b/platform/web/api/api.cpp index a695091a04..9ddbe5d01d 100644 --- a/platform/web/api/api.cpp +++ b/platform/web/api/api.cpp @@ -99,7 +99,7 @@ Variant JavaScriptBridge::_create_object_bind(const Variant **p_args, int p_argc r_error.expected = 1; return Ref<JavaScriptObject>(); } - if (p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING; diff --git a/platform/web/javascript_bridge_singleton.cpp b/platform/web/javascript_bridge_singleton.cpp index c4dbb405a3..502e830f82 100644 --- a/platform/web/javascript_bridge_singleton.cpp +++ b/platform/web/javascript_bridge_singleton.cpp @@ -304,7 +304,7 @@ Variant JavaScriptBridge::_create_object_bind(const Variant **p_args, int p_argc r_error.expected = 1; return Ref<JavaScriptObject>(); } - if (p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING; diff --git a/platform/web/os_web.cpp b/platform/web/os_web.cpp index ef8f90421b..51facbaa84 100644 --- a/platform/web/os_web.cpp +++ b/platform/web/os_web.cpp @@ -105,7 +105,7 @@ Error OS_Web::execute(const String &p_path, const List<String> &p_arguments, Str return create_process(p_path, p_arguments); } -Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { +Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) { ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform."); } diff --git a/platform/web/os_web.h b/platform/web/os_web.h index 55a5fcc6c6..1ddb745965 100644 --- a/platform/web/os_web.h +++ b/platform/web/os_web.h @@ -80,7 +80,7 @@ public: bool main_loop_iterate(); 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) override; - Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; + Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override; Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; Error kill(const ProcessID &p_pid) override; int get_process_id() const override; diff --git a/platform/windows/detect.py b/platform/windows/detect.py index d2a9c2315f..92ac921cee 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -649,6 +649,61 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)]) +def get_ar_version(env): + ret = { + "major": -1, + "minor": -1, + "patch": -1, + "is_llvm": False, + } + try: + output = ( + subprocess.check_output([env.subst(env["AR"]), "--version"], shell=(os.name == "nt")) + .strip() + .decode("utf-8") + ) + except (subprocess.CalledProcessError, OSError): + print_warning("Couldn't check version of `ar`.") + return ret + + match = re.search(r"GNU ar \(GNU Binutils\) (\d+)\.(\d+)(:?\.(\d+))?", output) + if match: + ret["major"] = int(match[1]) + ret["minor"] = int(match[2]) + if match[3]: + ret["patch"] = int(match[3]) + else: + ret["patch"] = 0 + return ret + + match = re.search(r"LLVM version (\d+)\.(\d+)\.(\d+)", output) + if match: + ret["major"] = int(match[1]) + ret["minor"] = int(match[2]) + ret["patch"] = int(match[3]) + ret["is_llvm"] = True + return ret + + print_warning("Couldn't parse version of `ar`.") + return ret + + +def get_is_ar_thin_supported(env): + """Check whether `ar --thin` is supported. It is only supported since Binutils 2.38 or LLVM 14.""" + ar_version = get_ar_version(env) + if ar_version["major"] == -1: + return False + + if ar_version["is_llvm"]: + return ar_version["major"] >= 14 + + if ar_version["major"] == 2: + return ar_version["minor"] >= 38 + + print_warning("Unknown Binutils `ar` version.") + return False + + def configure_mingw(env: "SConsEnvironment"): # Workaround for MinGW. See: # https://www.scons.org/wiki/LongCmdLinesOnWin32 @@ -781,7 +836,8 @@ def configure_mingw(env: "SConsEnvironment"): if env["use_llvm"] and os.name == "nt" and methods._colorize: env.Append(CCFLAGS=["$(-fansi-escape-codes$)", "$(-fcolor-diagnostics$)"]) - env.Append(ARFLAGS=["--thin"]) + if get_is_ar_thin_supported(env): + env.Append(ARFLAGS=["--thin"]) env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"]) env.Append( diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 8bcd556c22..50ebe7077f 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -522,7 +522,7 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) { if (!item.has("name") || !item.has("values") || !item.has("default")) { continue; } - event_handler->add_option(pfdc, item["name"], item["values"], item["default_idx"]); + event_handler->add_option(pfdc, item["name"], item["values"], item["default"]); } event_handler->set_root(fd->root); @@ -625,62 +625,41 @@ void DisplayServerWindows::_thread_fd_monitor(void *p_ud) { } } if (fd->callback.is_valid()) { - if (fd->options_in_cb) { - Variant v_result = true; - Variant v_files = file_names; - Variant v_index = index; - Variant v_opt = options; - const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt }; - - fd->callback.call_deferredp(cb_args, 4); - } else { - Variant v_result = true; - Variant v_files = file_names; - Variant v_index = index; - const Variant *cb_args[3] = { &v_result, &v_files, &v_index }; - - fd->callback.call_deferredp(cb_args, 3); - } + MutexLock lock(ds->file_dialog_mutex); + FileDialogCallback cb; + cb.callback = fd->callback; + cb.status = true; + cb.files = file_names; + cb.index = index; + cb.options = options; + cb.opt_in_cb = fd->options_in_cb; + ds->pending_cbs.push_back(cb); } } else { if (fd->callback.is_valid()) { - if (fd->options_in_cb) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = 0; - Variant v_opt = Dictionary(); - const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt }; - - fd->callback.call_deferredp(cb_args, 4); - } else { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = 0; - const Variant *cb_args[3] = { &v_result, &v_files, &v_index }; - - fd->callback.call_deferredp(cb_args, 3); - } + MutexLock lock(ds->file_dialog_mutex); + FileDialogCallback cb; + cb.callback = fd->callback; + cb.status = false; + cb.files = Vector<String>(); + cb.index = index; + cb.options = options; + cb.opt_in_cb = fd->options_in_cb; + ds->pending_cbs.push_back(cb); } } pfd->Release(); } else { if (fd->callback.is_valid()) { - if (fd->options_in_cb) { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = 0; - Variant v_opt = Dictionary(); - const Variant *cb_args[4] = { &v_result, &v_files, &v_index, &v_opt }; - - fd->callback.call_deferredp(cb_args, 4); - } else { - Variant v_result = false; - Variant v_files = Vector<String>(); - Variant v_index = 0; - const Variant *cb_args[3] = { &v_result, &v_files, &v_index }; - - fd->callback.call_deferredp(cb_args, 3); - } + MutexLock lock(ds->file_dialog_mutex); + FileDialogCallback cb; + cb.callback = fd->callback; + cb.status = false; + cb.files = Vector<String>(); + cb.index = 0; + cb.options = Dictionary(); + cb.opt_in_cb = fd->options_in_cb; + ds->pending_cbs.push_back(cb); } } { @@ -768,6 +747,34 @@ Error DisplayServerWindows::_file_dialog_with_options_show(const String &p_title return OK; } +void DisplayServerWindows::process_file_dialog_callbacks() { + MutexLock lock(file_dialog_mutex); + while (!pending_cbs.is_empty()) { + FileDialogCallback cb = pending_cbs.front()->get(); + pending_cbs.pop_front(); + + if (cb.opt_in_cb) { + Variant ret; + Callable::CallError ce; + const Variant *args[4] = { &cb.status, &cb.files, &cb.index, &cb.options }; + + cb.callback.callp(args, 4, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 4, ce))); + } + } else { + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &cb.status, &cb.files, &cb.index }; + + cb.callback.callp(args, 3, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Failed to execute file dialog callback: %s.", Variant::get_callable_error_text(cb.callback, args, 3, ce))); + } + } + } +} + void DisplayServerWindows::mouse_set_mode(MouseMode p_mode) { _THREAD_SAFE_METHOD_ @@ -3190,6 +3197,7 @@ void DisplayServerWindows::process_events() { memdelete(E->get()); E->erase(); } + process_file_dialog_callbacks(); } void DisplayServerWindows::force_process_and_drop_events() { @@ -6150,6 +6158,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win if (rendering_context->initialize() == OK) { WARN_PRINT("Your video card drivers seem not to support Direct3D 12, switching to Vulkan."); rendering_driver = "vulkan"; + OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); failed = false; } } @@ -6163,6 +6172,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win if (rendering_context->initialize() == OK) { WARN_PRINT("Your video card drivers seem not to support Vulkan, switching to Direct3D 12."); rendering_driver = "d3d12"; + OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); failed = false; } } @@ -6241,6 +6251,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win } } rendering_driver = "opengl3_angle"; + OS::get_singleton()->set_current_rendering_driver_name(rendering_driver); } } diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h index 3deb7ac8b0..54e1c9681d 100644 --- a/platform/windows/display_server_windows.h +++ b/platform/windows/display_server_windows.h @@ -572,6 +572,16 @@ class DisplayServerWindows : public DisplayServer { Mutex file_dialog_mutex; List<FileDialogData *> file_dialogs; HashMap<HWND, FileDialogData *> file_dialog_wnd; + struct FileDialogCallback { + Callable callback; + Variant status; + Variant files; + Variant index; + Variant options; + bool opt_in_cb = false; + }; + List<FileDialogCallback> pending_cbs; + void process_file_dialog_callbacks(); static void _thread_fd_monitor(void *p_ud); diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index 47836788e1..f9c636a4a6 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -878,7 +878,7 @@ Dictionary OS_Windows::get_memory_info() const { return meminfo; } -Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { +Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) { #define CLEAN_PIPES \ if (pipe_in[0] != 0) { \ CloseHandle(pipe_in[0]); \ @@ -977,11 +977,11 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String Ref<FileAccessWindowsPipe> main_pipe; main_pipe.instantiate(); - main_pipe->open_existing(pipe_out[0], pipe_in[1]); + main_pipe->open_existing(pipe_out[0], pipe_in[1], p_blocking); Ref<FileAccessWindowsPipe> err_pipe; err_pipe.instantiate(); - err_pipe->open_existing(pipe_err[0], 0); + err_pipe->open_existing(pipe_err[0], 0, p_blocking); ret["stdio"] = main_pipe; ret["stderr"] = err_pipe; @@ -1614,16 +1614,7 @@ String OS_Windows::get_executable_path() const { } bool OS_Windows::has_environment(const String &p_var) const { -#ifdef MINGW_ENABLED - return _wgetenv((LPCWSTR)(p_var.utf16().get_data())) != nullptr; -#else - WCHAR *env; - size_t len; - _wdupenv_s(&env, &len, (LPCWSTR)(p_var.utf16().get_data())); - const bool has_env = env != nullptr; - free(env); - return has_env; -#endif + return GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), nullptr, 0) > 0; } String OS_Windows::get_environment(const String &p_var) const { diff --git a/platform/windows/os_windows.h b/platform/windows/os_windows.h index 9c7b98d7fd..4f9bc049ee 100644 --- a/platform/windows/os_windows.h +++ b/platform/windows/os_windows.h @@ -188,7 +188,7 @@ public: virtual Dictionary get_memory_info() const override; 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) override; - virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; + virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error kill(const ProcessID &p_pid) override; virtual int get_process_id() const override; diff --git a/platform/windows/windows_utils.cpp b/platform/windows/windows_utils.cpp index 9e0b9eed8a..30743c6900 100644 --- a/platform/windows/windows_utils.cpp +++ b/platform/windows/windows_utils.cpp @@ -155,7 +155,11 @@ Error WindowsUtils::copy_and_rename_pdb(const String &p_dll_path) { } else if (!FileAccess::exists(copy_pdb_path)) { copy_pdb_path = dll_base_dir.path_join(copy_pdb_path.get_file()); } - ERR_FAIL_COND_V_MSG(!FileAccess::exists(copy_pdb_path), FAILED, vformat("File '%s' does not exist.", copy_pdb_path)); + if (!FileAccess::exists(copy_pdb_path)) { + // The PDB file may be distributed separately on purpose, so we don't consider this an error. + WARN_VERBOSE(vformat("PDB file '%s' for library '%s' was not found, skipping copy/rename.", copy_pdb_path, p_dll_path)); + return ERR_SKIP; + } String new_pdb_base_name = p_dll_path.get_file().get_basename() + "_"; diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp index 014419573c..1cab29f383 100644 --- a/scene/2d/animated_sprite_2d.cpp +++ b/scene/2d/animated_sprite_2d.cpp @@ -463,7 +463,7 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool name = animation; } - ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name)); + ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name)); ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name)); if (frames->get_frame_count(name) == 0) { @@ -541,7 +541,7 @@ void AnimatedSprite2D::set_animation(const StringName &p_name) { emit_signal(SceneStringName(animation_changed)); - if (frames == nullptr) { + if (frames.is_null()) { animation = StringName(); stop(); ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name)); diff --git a/scene/2d/canvas_modulate.cpp b/scene/2d/canvas_modulate.cpp index 2c5c6a1a16..dc83775c71 100644 --- a/scene/2d/canvas_modulate.cpp +++ b/scene/2d/canvas_modulate.cpp @@ -114,7 +114,7 @@ Color CanvasModulate::get_color() const { } PackedStringArray CanvasModulate::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (is_in_canvas && is_visible_in_tree()) { List<Node *> nodes; diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp index 5ce26b3ed4..50c5873781 100644 --- a/scene/2d/light_2d.cpp +++ b/scene/2d/light_2d.cpp @@ -417,7 +417,7 @@ Vector2 PointLight2D::get_texture_offset() const { } PackedStringArray PointLight2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!texture.is_valid()) { warnings.push_back(RTR("A texture with the shape of the light must be supplied to the \"Texture\" property.")); diff --git a/scene/2d/light_occluder_2d.cpp b/scene/2d/light_occluder_2d.cpp index 092c987ac0..7c3fb61d04 100644 --- a/scene/2d/light_occluder_2d.cpp +++ b/scene/2d/light_occluder_2d.cpp @@ -263,7 +263,7 @@ int LightOccluder2D::get_occluder_light_mask() const { } PackedStringArray LightOccluder2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!occluder_polygon.is_valid()) { warnings.push_back(RTR("An occluder polygon must be set (or drawn) for this occluder to take effect.")); diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp index 111f5a7b78..4961e18dc9 100644 --- a/scene/2d/navigation_link_2d.cpp +++ b/scene/2d/navigation_link_2d.cpp @@ -328,7 +328,7 @@ void NavigationLink2D::set_travel_cost(real_t p_travel_cost) { } PackedStringArray NavigationLink2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (start_position.is_equal_approx(end_position)) { warnings.push_back(RTR("NavigationLink2D start position should be different than the end position to be useful.")); diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 9b3c7bb9ea..f65a3c0ecc 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -452,6 +452,7 @@ void NavigationRegion2D::_update_debug_mesh() { const Transform2D region_gt = get_global_transform(); rs->canvas_item_set_parent(debug_instance_rid, get_world_2d()->get_canvas()); + rs->canvas_item_set_z_index(debug_instance_rid, RS::CANVAS_ITEM_Z_MAX - 2); rs->canvas_item_set_transform(debug_instance_rid, region_gt); if (!debug_mesh_dirty) { diff --git a/scene/2d/parallax_layer.cpp b/scene/2d/parallax_layer.cpp index 023e9201fc..24f261deb6 100644 --- a/scene/2d/parallax_layer.cpp +++ b/scene/2d/parallax_layer.cpp @@ -133,7 +133,7 @@ void ParallaxLayer::set_base_offset_and_scale(const Point2 &p_offset, real_t p_s } PackedStringArray ParallaxLayer::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!Object::cast_to<ParallaxBackground>(get_parent())) { warnings.push_back(RTR("ParallaxLayer node only works when set as child of a ParallaxBackground node.")); diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index 282d14da5d..5813ab02e3 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -288,7 +288,7 @@ void PathFollow2D::_validate_property(PropertyInfo &p_property) const { } PackedStringArray PathFollow2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path2D>(get_parent())) { @@ -378,9 +378,10 @@ real_t PathFollow2D::get_progress() const { } void PathFollow2D::set_progress_ratio(real_t p_ratio) { - if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { - set_progress(p_ratio * path->get_curve()->get_baked_length()); - } + ERR_FAIL_NULL_MSG(path, "Can only set progress ratio on a PathFollow2D that is the child of a Path2D which is itself part of the scene tree."); + ERR_FAIL_COND_MSG(path->get_curve().is_null(), "Can't set progress ratio on a PathFollow2D that does not have a Curve."); + ERR_FAIL_COND_MSG(!path->get_curve()->get_baked_length(), "Can't set progress ratio on a PathFollow2D that has a 0 length curve."); + set_progress(p_ratio * path->get_curve()->get_baked_length()); } real_t PathFollow2D::get_progress_ratio() const { diff --git a/scene/2d/physics/collision_object_2d.cpp b/scene/2d/physics/collision_object_2d.cpp index 00b6085f0c..27ee6b883c 100644 --- a/scene/2d/physics/collision_object_2d.cpp +++ b/scene/2d/physics/collision_object_2d.cpp @@ -582,7 +582,7 @@ void CollisionObject2D::_update_pickable() { } PackedStringArray CollisionObject2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape2D or CollisionPolygon2D as a child to define its shape.")); diff --git a/scene/2d/physics/collision_polygon_2d.cpp b/scene/2d/physics/collision_polygon_2d.cpp index a9b47ef4d4..b49badac1f 100644 --- a/scene/2d/physics/collision_polygon_2d.cpp +++ b/scene/2d/physics/collision_polygon_2d.cpp @@ -232,7 +232,7 @@ bool CollisionPolygon2D::_edit_is_selected_on_click(const Point2 &p_point, doubl #endif PackedStringArray CollisionPolygon2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!Object::cast_to<CollisionObject2D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon2D only serves to provide a collision shape to a CollisionObject2D derived node. Please only use it as a child of Area2D, StaticBody2D, RigidBody2D, CharacterBody2D, etc. to give them a shape.")); diff --git a/scene/2d/physics/collision_shape_2d.cpp b/scene/2d/physics/collision_shape_2d.cpp index 6fc29ffe63..bdd0d06b5e 100644 --- a/scene/2d/physics/collision_shape_2d.cpp +++ b/scene/2d/physics/collision_shape_2d.cpp @@ -174,7 +174,7 @@ bool CollisionShape2D::_edit_is_selected_on_click(const Point2 &p_point, double } PackedStringArray CollisionShape2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); CollisionObject2D *col_object = Object::cast_to<CollisionObject2D>(get_parent()); if (col_object == nullptr) { diff --git a/scene/2d/physics/physical_bone_2d.cpp b/scene/2d/physics/physical_bone_2d.cpp index 77bb8c24b8..19274c8084 100644 --- a/scene/2d/physics/physical_bone_2d.cpp +++ b/scene/2d/physics/physical_bone_2d.cpp @@ -107,7 +107,7 @@ void PhysicalBone2D::_find_joint_child() { } PackedStringArray PhysicalBone2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = RigidBody2D::get_configuration_warnings(); if (!parent_skeleton) { warnings.push_back(RTR("A PhysicalBone2D only works with a Skeleton2D or another PhysicalBone2D as a parent node!")); diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp index 920f5720fa..1816a3409b 100644 --- a/scene/2d/remote_transform_2d.cpp +++ b/scene/2d/remote_transform_2d.cpp @@ -211,7 +211,7 @@ void RemoteTransform2D::force_update_cache() { } PackedStringArray RemoteTransform2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node2D>(get_node(remote_node))) { warnings.push_back(RTR("Path property must point to a valid Node2D node to work.")); diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp index f9e8c831d1..90bfb4c84c 100644 --- a/scene/2d/skeleton_2d.cpp +++ b/scene/2d/skeleton_2d.cpp @@ -412,7 +412,7 @@ int Bone2D::get_index_in_skeleton() const { } PackedStringArray Bone2D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); if (!skeleton) { if (parent_bone) { warnings.push_back(RTR("This Bone2D chain should end at a Skeleton2D node.")); diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index b10f2097da..45cfb8cf33 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -827,7 +827,7 @@ TypedArray<Vector2i> TileMap::get_surrounding_cells(const Vector2i &p_coords) { } PackedStringArray TileMap::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node2D::get_configuration_warnings(); warnings.push_back(RTR("The TileMap node is deprecated as it is superseded by the use of multiple TileMapLayer nodes.\nTo convert a TileMap to a set of TileMapLayer nodes, open the TileMap bottom panel with this node selected, click the toolbox icon in the top-right corner and choose \"Extract TileMap layers as individual TileMapLayer nodes\".")); diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index ba0958e74b..ebb03e4e73 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -443,13 +443,15 @@ void TileMapLayer::_rendering_notification(int p_what) { Transform2D tilemap_xform = get_global_transform(); for (KeyValue<Vector2i, CellData> &kv : tile_map_layer_data) { const CellData &cell_data = kv.value; - for (const RID &occluder : cell_data.occluders) { - if (occluder.is_null()) { - continue; + for (const LocalVector<RID> &polygons : cell_data.occluders) { + for (const RID &rid : polygons) { + if (rid.is_null()) { + continue; + } + Transform2D xform(0, tile_set->map_to_local(kv.key)); + rs->canvas_light_occluder_attach_to_canvas(rid, get_canvas()); + rs->canvas_light_occluder_set_transform(rid, tilemap_xform * xform); } - Transform2D xform(0, tile_set->map_to_local(kv.key)); - rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); - rs->canvas_light_occluder_set_transform(occluder, tilemap_xform * xform); } } } @@ -557,8 +559,10 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) { RenderingServer *rs = RenderingServer::get_singleton(); // Free the occluders. - for (const RID &rid : r_cell_data.occluders) { - rs->free(rid); + for (const LocalVector<RID> &polygons : r_cell_data.occluders) { + for (const RID &rid : polygons) { + rs->free(rid); + } } r_cell_data.occluders.clear(); } @@ -566,11 +570,12 @@ void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) { void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { RenderingServer *rs = RenderingServer::get_singleton(); - // Free unused occluders then resize the occluders array. + // Free unused occluders then resize the occluder array. for (uint32_t i = tile_set->get_occlusion_layers_count(); i < r_cell_data.occluders.size(); i++) { - RID occluder_id = r_cell_data.occluders[i]; - if (occluder_id.is_valid()) { - rs->free(occluder_id); + for (const RID &occluder_id : r_cell_data.occluders[i]) { + if (occluder_id.is_valid()) { + rs->free(occluder_id); + } } } r_cell_data.occluders.resize(tile_set->get_occlusion_layers_count()); @@ -598,30 +603,42 @@ void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { // Create, update or clear occluders. bool needs_set_not_interpolated = is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && !is_physics_interpolated(); for (uint32_t occlusion_layer_index = 0; occlusion_layer_index < r_cell_data.occluders.size(); occlusion_layer_index++) { - Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer_index); - - RID &occluder = r_cell_data.occluders[occlusion_layer_index]; + LocalVector<RID> &occluders = r_cell_data.occluders[occlusion_layer_index]; - if (occluder_polygon.is_valid()) { - // Create or update occluder. - Transform2D xform; - xform.set_origin(tile_set->map_to_local(r_cell_data.coords)); - if (!occluder.is_valid()) { - occluder = rs->canvas_light_occluder_create(); - if (needs_set_not_interpolated) { - rs->canvas_light_occluder_set_interpolated(occluder, false); - } + // Free unused occluders then resize the occluders array. + for (uint32_t i = tile_data->get_occluder_polygons_count(occlusion_layer_index); i < r_cell_data.occluders[occlusion_layer_index].size(); i++) { + RID occluder_id = occluders[i]; + if (occluder_id.is_valid()) { + rs->free(occluder_id); } - rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform); - rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid()); - rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); - rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index)); - rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index)); - } else { - // Clear occluder. - if (occluder.is_valid()) { - rs->free(occluder); - occluder = RID(); + } + occluders.resize(tile_data->get_occluder_polygons_count(occlusion_layer_index)); + + for (uint32_t occlusion_polygon_index = 0; occlusion_polygon_index < occluders.size(); occlusion_polygon_index++) { + RID &occluder = occluders[occlusion_polygon_index]; + Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_index); + if (occluder_polygon.is_valid()) { + // Create or update occluder. + + Transform2D xform; + xform.set_origin(tile_set->map_to_local(r_cell_data.coords)); + if (!occluder.is_valid()) { + occluder = rs->canvas_light_occluder_create(); + if (needs_set_not_interpolated) { + rs->canvas_light_occluder_set_interpolated(occluder, false); + } + } + rs->canvas_light_occluder_set_transform(occluder, get_global_transform() * xform); + rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder_polygon(occlusion_layer_index, occlusion_polygon_index, flip_h, flip_v, transpose)->get_rid()); + rs->canvas_light_occluder_attach_to_canvas(occluder, get_canvas()); + rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index)); + rs->canvas_light_occluder_set_as_sdf_collision(occluder, tile_set->get_occlusion_layer_sdf_collision(occlusion_layer_index)); + } else { + // Clear occluder. + if (occluder.is_valid()) { + rs->free(occluder); + occluder = RID(); + } } } } @@ -1709,11 +1726,13 @@ void TileMapLayer::_physics_interpolated_changed() { } for (const KeyValue<Vector2i, CellData> &E : tile_map_layer_data) { - for (const RID &occluder : E.value.occluders) { - if (occluder.is_valid()) { - rs->canvas_light_occluder_set_interpolated(occluder, interpolated); - if (needs_reset) { - rs->canvas_light_occluder_reset_physics_interpolation(occluder); + for (const LocalVector<RID> &polygons : E.value.occluders) { + for (const RID &occluder_id : polygons) { + if (occluder_id.is_valid()) { + rs->canvas_light_occluder_set_interpolated(occluder_id, interpolated); + if (needs_reset) { + rs->canvas_light_occluder_reset_physics_interpolation(occluder_id); + } } } } diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index 2a986667bd..cc0a5b49fb 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -108,7 +108,7 @@ struct CellData { // Rendering. Ref<RenderingQuadrant> rendering_quadrant; SelfList<CellData> rendering_quadrant_list_element; - LocalVector<RID> occluders; + LocalVector<LocalVector<RID>> occluders; // Physics. LocalVector<RID> bodies; diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index 03fe5e1fad..acbc443a93 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -448,6 +448,10 @@ void CPUParticles3D::set_emission_ring_inner_radius(real_t p_radius) { emission_ring_inner_radius = p_radius; } +void CPUParticles3D::set_emission_ring_cone_angle(real_t p_angle) { + emission_ring_cone_angle = p_angle; +} + void CPUParticles3D::set_scale_curve_x(Ref<Curve> p_scale_curve) { scale_curve_x = p_scale_curve; } @@ -501,6 +505,10 @@ real_t CPUParticles3D::get_emission_ring_inner_radius() const { return emission_ring_inner_radius; } +real_t CPUParticles3D::get_emission_ring_cone_angle() const { + return emission_ring_cone_angle; +} + CPUParticles3D::EmissionShape CPUParticles3D::get_emission_shape() const { return emission_shape; } @@ -878,8 +886,14 @@ void CPUParticles3D::_particles_process(double p_delta) { } } break; case EMISSION_SHAPE_RING: { + real_t radius_clamped = MAX(0.001, emission_ring_radius); + real_t top_radius = MAX(radius_clamped - Math::tan(Math::deg_to_rad(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0); + real_t y_pos = Math::randf(); + real_t skew = MAX(MIN(radius_clamped, top_radius) / MAX(radius_clamped, top_radius), 0.5); + y_pos = radius_clamped < top_radius ? Math::pow(y_pos, skew) : 1.0 - Math::pow(y_pos, skew); real_t ring_random_angle = Math::randf() * Math_TAU; - real_t ring_random_radius = Math::sqrt(Math::randf() * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius); + real_t ring_random_radius = Math::sqrt(Math::randf() * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius); + ring_random_radius = Math::lerp(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos); Vector3 axis = emission_ring_axis == Vector3(0.0, 0.0, 0.0) ? Vector3(0.0, 0.0, 1.0) : emission_ring_axis.normalized(); Vector3 ortho_axis; if (axis.abs() == Vector3(1.0, 0.0, 0.0)) { @@ -890,7 +904,7 @@ void CPUParticles3D::_particles_process(double p_delta) { ortho_axis = ortho_axis.normalized(); ortho_axis.rotate(axis, ring_random_angle); ortho_axis = ortho_axis.normalized(); - p.transform.origin = ortho_axis * ring_random_radius + (Math::randf() * emission_ring_height - emission_ring_height / 2.0) * axis; + p.transform.origin = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis; } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; @@ -1550,6 +1564,9 @@ void CPUParticles3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &CPUParticles3D::set_emission_ring_inner_radius); ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &CPUParticles3D::get_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &CPUParticles3D::set_emission_ring_cone_angle); + ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &CPUParticles3D::get_emission_ring_cone_angle); + ClassDB::bind_method(D_METHOD("get_gravity"), &CPUParticles3D::get_gravity); ClassDB::bind_method(D_METHOD("set_gravity", "accel_vec"), &CPUParticles3D::set_gravity); @@ -1577,9 +1594,10 @@ void CPUParticles3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "emission_normals"), "set_emission_normals", "get_emission_normals"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_COLOR_ARRAY, "emission_colors"), "set_emission_colors", "get_emission_colors"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle"); ADD_GROUP("Particle Flags", "particle_flag_"); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_align_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ALIGN_Y_TO_VELOCITY); ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "particle_flag_rotate_y"), "set_particle_flag", "get_particle_flag", PARTICLE_FLAG_ROTATE_Y); @@ -1716,6 +1734,7 @@ CPUParticles3D::CPUParticles3D() { set_emission_ring_height(1); set_emission_ring_radius(1); set_emission_ring_inner_radius(0); + set_emission_ring_cone_angle(90); set_gravity(Vector3(0, -9.8, 0)); diff --git a/scene/3d/cpu_particles_3d.h b/scene/3d/cpu_particles_3d.h index 82ea4bbef3..978bb64e71 100644 --- a/scene/3d/cpu_particles_3d.h +++ b/scene/3d/cpu_particles_3d.h @@ -178,6 +178,7 @@ private: real_t emission_ring_height = 0.0; real_t emission_ring_radius = 0.0; real_t emission_ring_inner_radius = 0.0; + real_t emission_ring_cone_angle = 0.0; Ref<Curve> scale_curve_x; Ref<Curve> scale_curve_y; @@ -282,6 +283,7 @@ public: void set_emission_ring_height(real_t p_height); void set_emission_ring_radius(real_t p_radius); void set_emission_ring_inner_radius(real_t p_radius); + void set_emission_ring_cone_angle(real_t p_angle); void set_scale_curve_x(Ref<Curve> p_scale_curve); void set_scale_curve_y(Ref<Curve> p_scale_curve); void set_scale_curve_z(Ref<Curve> p_scale_curve); @@ -297,6 +299,7 @@ public: real_t get_emission_ring_height() const; real_t get_emission_ring_radius() const; real_t get_emission_ring_inner_radius() const; + real_t get_emission_ring_cone_angle() const; Ref<Curve> get_scale_curve_x() const; Ref<Curve> get_scale_curve_y() const; Ref<Curve> get_scale_curve_z() const; diff --git a/scene/3d/decal.cpp b/scene/3d/decal.cpp index 485599d0fb..8702b1d3da 100644 --- a/scene/3d/decal.cpp +++ b/scene/3d/decal.cpp @@ -163,7 +163,7 @@ void Decal::_validate_property(PropertyInfo &p_property) const { } PackedStringArray Decal::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("Decals are only available when using the Forward+ or Mobile rendering backends.")); diff --git a/scene/3d/fog_volume.cpp b/scene/3d/fog_volume.cpp index 54631a8dff..195074ba2f 100644 --- a/scene/3d/fog_volume.cpp +++ b/scene/3d/fog_volume.cpp @@ -116,7 +116,7 @@ AABB FogVolume::get_aabb() const { } PackedStringArray FogVolume::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); Ref<Environment> environment = get_viewport()->find_world_3d()->get_environment(); diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp index 3a05ec9c9e..9791f23bc3 100644 --- a/scene/3d/gpu_particles_collision_3d.cpp +++ b/scene/3d/gpu_particles_collision_3d.cpp @@ -524,7 +524,7 @@ Ref<Image> GPUParticlesCollisionSDF3D::bake() { } PackedStringArray GPUParticlesCollisionSDF3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = GPUParticlesCollision3D::get_configuration_warnings(); if (bake_mask == 0) { warnings.push_back(RTR("The Bake Mask has no bits enabled, which means baking will not produce any collision for this GPUParticlesCollisionSDF3D.\nTo resolve this, enable at least one bit in the Bake Mask property.")); diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 3f8b0dfb8e..26a574cd26 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -151,6 +151,14 @@ bool LightmapGIData::is_using_spherical_harmonics() const { return uses_spherical_harmonics; } +void LightmapGIData::_set_uses_packed_directional(bool p_enable) { + _uses_packed_directional = p_enable; +} + +bool LightmapGIData::_is_using_packed_directional() const { + return _uses_packed_directional; +} + void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) { if (p_points.size()) { int pc = p_points.size(); @@ -255,6 +263,9 @@ void LightmapGIData::_bind_methods() { ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics); ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics); + ClassDB::bind_method(D_METHOD("_set_uses_packed_directional", "_uses_packed_directional"), &LightmapGIData::_set_uses_packed_directional); + ClassDB::bind_method(D_METHOD("_is_using_packed_directional"), &LightmapGIData::_is_using_packed_directional); + ClassDB::bind_method(D_METHOD("add_user", "path", "uv_scale", "slice_index", "sub_instance"), &LightmapGIData::add_user); ClassDB::bind_method(D_METHOD("get_user_count"), &LightmapGIData::get_user_count); ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &LightmapGIData::get_user_path); @@ -267,6 +278,7 @@ void LightmapGIData::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data"); ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "_uses_packed_directional", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_uses_packed_directional", "_is_using_packed_directional"); #ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_light_texture", "light_texture"), &LightmapGIData::set_light_texture); @@ -1187,6 +1199,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa } gi_data->set_lightmap_textures(textures); + gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically. gi_data->set_uses_spherical_harmonics(directional); for (int i = 0; i < lightmapper->get_bake_mesh_count(); i++) { @@ -1352,6 +1365,12 @@ void LightmapGI::_notification(int p_what) { switch (p_what) { case NOTIFICATION_POST_ENTER_TREE: { if (light_data.is_valid()) { + ERR_FAIL_COND_MSG( + light_data->is_using_spherical_harmonics() && !light_data->_is_using_packed_directional(), + vformat( + "%s (%s): The directional lightmap textures are stored in a format that isn't supported anymore. Please bake lightmaps again to make lightmaps display from this node again.", + get_light_data()->get_path(), get_name())); + _assign_lightmaps(); } } break; @@ -1581,7 +1600,7 @@ Ref<CameraAttributes> LightmapGI::get_camera_attributes() const { } PackedStringArray LightmapGI::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("Lightmap can only be baked from a device that supports the RD backends. Lightmap baking may fail.")); diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index 67480132b6..6377c420d1 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -49,6 +49,8 @@ class LightmapGIData : public Resource { bool uses_spherical_harmonics = false; bool interior = false; + bool _uses_packed_directional = false; + RID lightmap; AABB bounds; float baked_exposure = 1.0; @@ -92,6 +94,9 @@ public: void set_uses_spherical_harmonics(bool p_enable); bool is_using_spherical_harmonics() const; + void _set_uses_packed_directional(bool p_enable); + bool _is_using_packed_directional() const; + bool is_interior() const; float get_baked_exposure() const; diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 85bf8846b9..f551cb401c 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -517,12 +517,12 @@ bool MeshInstance3D::_property_get_revert(const StringName &p_name, Variant &r_p Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing) { Ref<ArrayMesh> source_mesh = get_mesh(); - ERR_FAIL_NULL_V_MSG(source_mesh, Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh."); + ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh."); Ref<ArrayMesh> bake_mesh; if (p_existing.is_valid()) { - ERR_FAIL_NULL_V_MSG(p_existing, Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh."); + ERR_FAIL_COND_V_MSG(p_existing.is_null(), Ref<ArrayMesh>(), "The existing mesh must be a valid ArrayMesh."); ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh."); bake_mesh = p_existing; @@ -671,6 +671,172 @@ Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_blend_shape_mix(Ref<ArrayM return bake_mesh; } +Ref<ArrayMesh> MeshInstance3D::bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing) { + Ref<ArrayMesh> source_mesh = get_mesh(); + ERR_FAIL_COND_V_MSG(source_mesh.is_null(), Ref<ArrayMesh>(), "The source mesh must be a valid ArrayMesh."); + + Ref<ArrayMesh> bake_mesh; + + if (p_existing.is_valid()) { + ERR_FAIL_COND_V_MSG(source_mesh == p_existing, Ref<ArrayMesh>(), "The source mesh can not be the same mesh as the existing mesh."); + + bake_mesh = p_existing; + } else { + bake_mesh.instantiate(); + } + + ERR_FAIL_COND_V_MSG(skin_ref.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin."); + ERR_FAIL_COND_V_MSG(skin_internal.is_null(), Ref<ArrayMesh>(), "The source mesh must have a valid skin."); + RID skeleton = skin_ref->get_skeleton(); + ERR_FAIL_COND_V_MSG(!skeleton.is_valid(), Ref<ArrayMesh>(), "The source mesh must have its skin registered with a valid skeleton."); + + const int bone_count = RenderingServer::get_singleton()->skeleton_get_bone_count(skeleton); + ERR_FAIL_COND_V(bone_count <= 0, Ref<ArrayMesh>()); + ERR_FAIL_COND_V(bone_count < skin_internal->get_bind_count(), Ref<ArrayMesh>()); + + LocalVector<Transform3D> bone_transforms; + bone_transforms.resize(bone_count); + for (int bone_index = 0; bone_index < bone_count; bone_index++) { + bone_transforms[bone_index] = RenderingServer::get_singleton()->skeleton_bone_get_transform(skeleton, bone_index); + } + + bake_mesh->clear_surfaces(); + + int mesh_surface_count = source_mesh->get_surface_count(); + + for (int surface_index = 0; surface_index < mesh_surface_count; surface_index++) { + ERR_CONTINUE(source_mesh->surface_get_primitive_type(surface_index) != Mesh::PRIMITIVE_TRIANGLES); + + uint32_t surface_format = source_mesh->surface_get_format(surface_index); + + ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_VERTEX)); + ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_BONES)); + ERR_CONTINUE(0 == (surface_format & Mesh::ARRAY_FORMAT_WEIGHTS)); + + unsigned int bones_per_vertex = surface_format & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS ? 8 : 4; + + surface_format &= ~Mesh::ARRAY_FORMAT_BONES; + surface_format &= ~Mesh::ARRAY_FORMAT_WEIGHTS; + + const Array &source_mesh_arrays = source_mesh->surface_get_arrays(surface_index); + + ERR_FAIL_COND_V(source_mesh_arrays.size() != RS::ARRAY_MAX, Ref<ArrayMesh>()); + + const Vector<Vector3> &source_mesh_vertex_array = source_mesh_arrays[Mesh::ARRAY_VERTEX]; + const Vector<Vector3> &source_mesh_normal_array = source_mesh_arrays[Mesh::ARRAY_NORMAL]; + const Vector<float> &source_mesh_tangent_array = source_mesh_arrays[Mesh::ARRAY_TANGENT]; + const Vector<int> &source_mesh_bones_array = source_mesh_arrays[Mesh::ARRAY_BONES]; + const Vector<float> &source_mesh_weights_array = source_mesh_arrays[Mesh::ARRAY_WEIGHTS]; + + unsigned int vertex_count = source_mesh_vertex_array.size(); + int expected_bone_array_size = vertex_count * bones_per_vertex; + ERR_CONTINUE(source_mesh_bones_array.size() != expected_bone_array_size); + ERR_CONTINUE(source_mesh_weights_array.size() != expected_bone_array_size); + + Array new_mesh_arrays; + new_mesh_arrays.resize(Mesh::ARRAY_MAX); + for (int i = 0; i < source_mesh_arrays.size(); i++) { + if (i == Mesh::ARRAY_VERTEX || i == Mesh::ARRAY_NORMAL || i == Mesh::ARRAY_TANGENT || i == Mesh::ARRAY_BONES || i == Mesh::ARRAY_WEIGHTS) { + continue; + } + new_mesh_arrays[i] = source_mesh_arrays[i]; + } + + bool use_normal_array = source_mesh_normal_array.size() == source_mesh_vertex_array.size(); + bool use_tangent_array = source_mesh_tangent_array.size() / 4 == source_mesh_vertex_array.size(); + + Vector<Vector3> lerped_vertex_array = source_mesh_vertex_array; + Vector<Vector3> lerped_normal_array = source_mesh_normal_array; + Vector<float> lerped_tangent_array = source_mesh_tangent_array; + + const Vector3 *source_vertices_ptr = source_mesh_vertex_array.ptr(); + const Vector3 *source_normals_ptr = source_mesh_normal_array.ptr(); + const float *source_tangents_ptr = source_mesh_tangent_array.ptr(); + const int *source_bones_ptr = source_mesh_bones_array.ptr(); + const float *source_weights_ptr = source_mesh_weights_array.ptr(); + + Vector3 *lerped_vertices_ptrw = lerped_vertex_array.ptrw(); + Vector3 *lerped_normals_ptrw = lerped_normal_array.ptrw(); + float *lerped_tangents_ptrw = lerped_tangent_array.ptrw(); + + for (unsigned int vertex_index = 0; vertex_index < vertex_count; vertex_index++) { + Vector3 lerped_vertex; + Vector3 lerped_normal; + Vector3 lerped_tangent; + + const Vector3 &source_vertex = source_vertices_ptr[vertex_index]; + + Vector3 source_normal; + if (use_normal_array) { + source_normal = source_normals_ptr[vertex_index]; + } + + int tangent_index = vertex_index * 4; + Vector4 source_tangent; + Vector3 source_tangent_vec3; + if (use_tangent_array) { + source_tangent = Vector4( + source_tangents_ptr[tangent_index], + source_tangents_ptr[tangent_index + 1], + source_tangents_ptr[tangent_index + 2], + source_tangents_ptr[tangent_index + 3]); + + DEV_ASSERT(source_tangent.w == 1.0 || source_tangent.w == -1.0); + + source_tangent_vec3 = Vector3(source_tangent.x, source_tangent.y, source_tangent.z); + } + + for (unsigned int weight_index = 0; weight_index < bones_per_vertex; weight_index++) { + float bone_weight = source_weights_ptr[vertex_index * bones_per_vertex + weight_index]; + if (bone_weight < FLT_EPSILON) { + continue; + } + int vertex_bone_index = source_bones_ptr[vertex_index * bones_per_vertex + weight_index]; + const Transform3D &bone_transform = bone_transforms[vertex_bone_index]; + const Basis bone_basis = bone_transform.basis.orthonormalized(); + + ERR_FAIL_INDEX_V(vertex_bone_index, static_cast<int>(bone_transforms.size()), Ref<ArrayMesh>()); + + lerped_vertex += source_vertex.lerp(bone_transform.xform(source_vertex), bone_weight) - source_vertex; + ; + + if (use_normal_array) { + lerped_normal += source_normal.lerp(bone_basis.xform(source_normal), bone_weight) - source_normal; + } + + if (use_tangent_array) { + lerped_tangent += source_tangent_vec3.lerp(bone_basis.xform(source_tangent_vec3), bone_weight) - source_tangent_vec3; + } + } + + lerped_vertices_ptrw[vertex_index] += lerped_vertex; + + if (use_normal_array) { + lerped_normals_ptrw[vertex_index] = (source_normal + lerped_normal).normalized(); + } + + if (use_tangent_array) { + lerped_tangent = (source_tangent_vec3 + lerped_tangent).normalized(); + lerped_tangents_ptrw[tangent_index] = lerped_tangent.x; + lerped_tangents_ptrw[tangent_index + 1] = lerped_tangent.y; + lerped_tangents_ptrw[tangent_index + 2] = lerped_tangent.z; + } + } + + new_mesh_arrays[Mesh::ARRAY_VERTEX] = lerped_vertex_array; + if (use_normal_array) { + new_mesh_arrays[Mesh::ARRAY_NORMAL] = lerped_normal_array; + } + if (use_tangent_array) { + new_mesh_arrays[Mesh::ARRAY_TANGENT] = lerped_tangent_array; + } + + bake_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, new_mesh_arrays, Array(), Dictionary(), surface_format); + } + + return bake_mesh; +} + void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mesh", "mesh"), &MeshInstance3D::set_mesh); ClassDB::bind_method(D_METHOD("get_mesh"), &MeshInstance3D::get_mesh); @@ -700,6 +866,7 @@ void MeshInstance3D::_bind_methods() { ClassDB::bind_method(D_METHOD("create_debug_tangents"), &MeshInstance3D::create_debug_tangents); ClassDB::bind_method(D_METHOD("bake_mesh_from_current_blend_shape_mix", "existing"), &MeshInstance3D::bake_mesh_from_current_blend_shape_mix, DEFVAL(Ref<ArrayMesh>())); + ClassDB::bind_method(D_METHOD("bake_mesh_from_current_skeleton_pose", "existing"), &MeshInstance3D::bake_mesh_from_current_skeleton_pose, DEFVAL(Ref<ArrayMesh>())); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh", PROPERTY_HINT_RESOURCE_TYPE, "Mesh"), "set_mesh", "get_mesh"); ADD_GROUP("Skeleton", ""); diff --git a/scene/3d/mesh_instance_3d.h b/scene/3d/mesh_instance_3d.h index 8a7e03c5b3..0eff12762d 100644 --- a/scene/3d/mesh_instance_3d.h +++ b/scene/3d/mesh_instance_3d.h @@ -102,6 +102,7 @@ public: virtual AABB get_aabb() const override; Ref<ArrayMesh> bake_mesh_from_current_blend_shape_mix(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>()); + Ref<ArrayMesh> bake_mesh_from_current_skeleton_pose(Ref<ArrayMesh> p_existing = Ref<ArrayMesh>()); MeshInstance3D(); ~MeshInstance3D(); diff --git a/scene/3d/navigation_link_3d.cpp b/scene/3d/navigation_link_3d.cpp index bebba9a6c0..0cce21b9d0 100644 --- a/scene/3d/navigation_link_3d.cpp +++ b/scene/3d/navigation_link_3d.cpp @@ -453,7 +453,7 @@ void NavigationLink3D::set_travel_cost(real_t p_travel_cost) { } PackedStringArray NavigationLink3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (start_position.is_equal_approx(end_position)) { warnings.push_back(RTR("NavigationLink3D start position should be different than the end position to be useful.")); diff --git a/scene/3d/navigation_region_3d.cpp b/scene/3d/navigation_region_3d.cpp index d7397a932d..c0c254e7ed 100644 --- a/scene/3d/navigation_region_3d.cpp +++ b/scene/3d/navigation_region_3d.cpp @@ -271,7 +271,7 @@ bool NavigationRegion3D::is_baking() const { } PackedStringArray NavigationRegion3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!navigation_mesh.is_valid()) { diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index 6982df12f6..6d88323c76 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -691,7 +691,7 @@ OccluderInstance3D::BakeError OccluderInstance3D::bake_scene(Node *p_from_node, } PackedStringArray OccluderInstance3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (!bool(GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"))) { warnings.push_back(RTR("Occlusion culling is disabled in the Project Settings, which means occlusion culling won't be performed in the root viewport.\nTo resolve this, open the Project Settings and enable Rendering > Occlusion Culling > Use Occlusion Culling.")); diff --git a/scene/3d/path_3d.cpp b/scene/3d/path_3d.cpp index 1f8f7cd54c..20d646fe1e 100644 --- a/scene/3d/path_3d.cpp +++ b/scene/3d/path_3d.cpp @@ -318,7 +318,7 @@ void PathFollow3D::_validate_property(PropertyInfo &p_property) const { } PackedStringArray PathFollow3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible_in_tree() && is_inside_tree()) { if (!Object::cast_to<Path3D>(get_parent())) { @@ -461,9 +461,10 @@ real_t PathFollow3D::get_progress() const { } void PathFollow3D::set_progress_ratio(real_t p_ratio) { - if (path && path->get_curve().is_valid() && path->get_curve()->get_baked_length()) { - set_progress(p_ratio * path->get_curve()->get_baked_length()); - } + ERR_FAIL_NULL_MSG(path, "Can only set progress ratio on a PathFollow3D that is the child of a Path3D which is itself part of the scene tree."); + ERR_FAIL_COND_MSG(path->get_curve().is_null(), "Can't set progress ratio on a PathFollow3D that does not have a Curve."); + ERR_FAIL_COND_MSG(!path->get_curve()->get_baked_length(), "Can't set progress ratio on a PathFollow3D that has a 0 length curve."); + set_progress(p_ratio * path->get_curve()->get_baked_length()); } real_t PathFollow3D::get_progress_ratio() const { diff --git a/scene/3d/physical_bone_simulator_3d.cpp b/scene/3d/physical_bone_simulator_3d.cpp index ffe79e0892..510d887c83 100644 --- a/scene/3d/physical_bone_simulator_3d.cpp +++ b/scene/3d/physical_bone_simulator_3d.cpp @@ -285,11 +285,11 @@ void _pb_start_simulation(const PhysicalBoneSimulator3D *p_simulator, Node *p_no } void PhysicalBoneSimulator3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) { + _pose_updated(); + simulating = true; _reset_physical_bones_state(); - _pose_updated(); - Vector<int> sim_bones; if (p_bones.size() > 0) { sim_bones.resize(p_bones.size()); @@ -357,47 +357,16 @@ void PhysicalBoneSimulator3D::_process_modification() { if (!skeleton) { return; } - if (!enabled) { - for (int i = 0; i < bones.size(); i++) { - if (bones[i].physical_bone) { - if (bones[i].physical_bone->is_simulating_physics() == false) { - bones[i].physical_bone->reset_to_rest_position(); - } - } + ERR_FAIL_COND(skeleton->get_bone_count() != bones.size()); + for (int i = 0; i < skeleton->get_bone_count(); i++) { + if (!bones[i].physical_bone) { + continue; } - } else { - ERR_FAIL_COND(skeleton->get_bone_count() != bones.size()); - for (int i = 0; i < skeleton->get_bone_count(); i++) { - if (!bones[i].physical_bone) { - continue; - } + if (bones[i].physical_bone->is_simulating_physics() == false) { + bones[i].physical_bone->reset_to_rest_position(); + } else if (simulating) { skeleton->set_bone_global_pose(i, bones[i].global_pose); } - - // TODO: - // The above method is performance heavy and needs to be improved. - // Ideally, the processing of set_bone_global_pose within Skeleton3D should be improved, - // but the workaround available now is to convert the global pose to a local pose on the SkeletonModifier side. - // However, the follow method needs recursive processing for deformations within PhysicalBoneSimulator3D to account for update order. - /* - ERR_FAIL_COND(skeleton->get_bone_count() != bones.size()); - LocalVector<Transform3D> local_poses; - for (int i = 0; i < skeleton->get_bone_count(); i++) { - Transform3D pt; - if (skeleton->get_bone_parent(i) >= 0) { - pt = get_bone_global_pose(skeleton->get_bone_parent(i)); - } - local_poses.push_back(pt.affine_inverse() * bones[i].global_pose); - } - for (int i = 0; i < skeleton->get_bone_count(); i++) { - if (!bones[i].physical_bone) { - continue; - } - skeleton->set_bone_pose_position(i, local_poses[i].origin); - skeleton->set_bone_pose_rotation(i, local_poses[i].basis.get_rotation_quaternion()); - skeleton->set_bone_pose_scale(i, local_poses[i].basis.get_scale()); - } - */ } } diff --git a/scene/3d/physical_bone_simulator_3d.h b/scene/3d/physical_bone_simulator_3d.h index ee900e0e77..ddc9cd569a 100644 --- a/scene/3d/physical_bone_simulator_3d.h +++ b/scene/3d/physical_bone_simulator_3d.h @@ -41,7 +41,6 @@ class PhysicalBoneSimulator3D : public SkeletonModifier3D { GDCLASS(PhysicalBoneSimulator3D, SkeletonModifier3D); bool simulating = false; - bool enabled = true; struct SimulatedBone { int parent; diff --git a/scene/3d/physics/collision_object_3d.cpp b/scene/3d/physics/collision_object_3d.cpp index f11aa7012a..f0a5013ca2 100644 --- a/scene/3d/physics/collision_object_3d.cpp +++ b/scene/3d/physics/collision_object_3d.cpp @@ -731,7 +731,7 @@ bool CollisionObject3D::get_capture_input_on_drag() const { } PackedStringArray CollisionObject3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (shapes.is_empty()) { warnings.push_back(RTR("This node has no shape, so it can't collide or interact with other objects.\nConsider adding a CollisionShape3D or CollisionPolygon3D as a child to define its shape.")); diff --git a/scene/3d/physics/collision_polygon_3d.cpp b/scene/3d/physics/collision_polygon_3d.cpp index 76cd4db779..bf8dec7b54 100644 --- a/scene/3d/physics/collision_polygon_3d.cpp +++ b/scene/3d/physics/collision_polygon_3d.cpp @@ -169,7 +169,7 @@ void CollisionPolygon3D::set_margin(real_t p_margin) { } PackedStringArray CollisionPolygon3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!Object::cast_to<CollisionObject3D>(get_parent())) { warnings.push_back(RTR("CollisionPolygon3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape.")); diff --git a/scene/3d/physics/collision_shape_3d.cpp b/scene/3d/physics/collision_shape_3d.cpp index f3492a3cf3..304fa74b06 100644 --- a/scene/3d/physics/collision_shape_3d.cpp +++ b/scene/3d/physics/collision_shape_3d.cpp @@ -120,7 +120,7 @@ void CollisionShape3D::resource_changed(Ref<Resource> res) { #endif PackedStringArray CollisionShape3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); CollisionObject3D *col_object = Object::cast_to<CollisionObject3D>(get_parent()); if (col_object == nullptr) { diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp index b4c321cf5f..5073705145 100644 --- a/scene/3d/physics/vehicle_body_3d.cpp +++ b/scene/3d/physics/vehicle_body_3d.cpp @@ -106,7 +106,7 @@ void VehicleWheel3D::_notification(int p_what) { } PackedStringArray VehicleWheel3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!Object::cast_to<VehicleBody3D>(get_parent())) { warnings.push_back(RTR("VehicleWheel3D serves to provide a wheel system to a VehicleBody3D. Please use it as a child of a VehicleBody3D.")); diff --git a/scene/3d/remote_transform_3d.cpp b/scene/3d/remote_transform_3d.cpp index e580882c46..f970879aa4 100644 --- a/scene/3d/remote_transform_3d.cpp +++ b/scene/3d/remote_transform_3d.cpp @@ -211,7 +211,7 @@ void RemoteTransform3D::force_update_cache() { } PackedStringArray RemoteTransform3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (!has_node(remote_node) || !Object::cast_to<Node3D>(get_node(remote_node))) { warnings.push_back(RTR("The \"Remote Path\" property must point to a valid Node3D or Node3D-derived node to work.")); diff --git a/scene/3d/soft_body_3d.cpp b/scene/3d/soft_body_3d.cpp index 4fe5dd2385..7f67bde0cf 100644 --- a/scene/3d/soft_body_3d.cpp +++ b/scene/3d/soft_body_3d.cpp @@ -383,7 +383,7 @@ void SoftBody3D::_bind_methods() { } PackedStringArray SoftBody3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = MeshInstance3D::get_configuration_warnings(); if (mesh.is_null()) { warnings.push_back(RTR("This body will be ignored until you set a mesh.")); diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp index 50218a6d86..42460eec4c 100644 --- a/scene/3d/sprite_3d.cpp +++ b/scene/3d/sprite_3d.cpp @@ -132,7 +132,7 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect, // Properly setup UVs for impostor textures (AtlasTexture). Ref<AtlasTexture> atlas_tex = p_texture; - if (atlas_tex != nullptr) { + if (atlas_tex.is_valid()) { src_tsize[0] = atlas_tex->get_atlas()->get_width(); src_tsize[1] = atlas_tex->get_atlas()->get_height(); } @@ -1324,7 +1324,7 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool name = animation; } - ERR_FAIL_NULL_MSG(frames, vformat("There is no animation with name '%s'.", name)); + ERR_FAIL_COND_MSG(frames.is_null(), vformat("There is no animation with name '%s'.", name)); ERR_FAIL_COND_MSG(!frames->get_animation_names().has(name), vformat("There is no animation with name '%s'.", name)); if (frames->get_frame_count(name) == 0) { @@ -1402,7 +1402,7 @@ void AnimatedSprite3D::set_animation(const StringName &p_name) { emit_signal(SceneStringName(animation_changed)); - if (frames == nullptr) { + if (frames.is_null()) { animation = StringName(); stop(); ERR_FAIL_MSG(vformat("There is no animation with name '%s'.", p_name)); diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 79a01450dd..a59754c8cc 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -497,7 +497,7 @@ bool GeometryInstance3D::is_ignoring_occlusion_culling() { } PackedStringArray GeometryInstance3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (!Math::is_zero_approx(visibility_range_end) && visibility_range_end <= visibility_range_begin) { warnings.push_back(RTR("The GeometryInstance3D visibility range's End distance is set to a non-zero value, but is lower than the Begin distance.\nThis means the GeometryInstance3D will never be visible.\nTo resolve this, set the End distance to 0 or to a value greater than the Begin distance.")); diff --git a/scene/3d/voxel_gi.cpp b/scene/3d/voxel_gi.cpp index ffca856fba..80ff176a98 100644 --- a/scene/3d/voxel_gi.cpp +++ b/scene/3d/voxel_gi.cpp @@ -518,7 +518,7 @@ AABB VoxelGI::get_aabb() const { } PackedStringArray VoxelGI::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = VisualInstance3D::get_configuration_warnings(); if (OS::get_singleton()->get_current_rendering_method() == "gl_compatibility") { warnings.push_back(RTR("VoxelGI nodes are not supported when using the GL Compatibility backend yet. Support will be added in a future release.")); diff --git a/scene/3d/xr_hand_modifier_3d.cpp b/scene/3d/xr_hand_modifier_3d.cpp index aa63fb623f..3b533da701 100644 --- a/scene/3d/xr_hand_modifier_3d.cpp +++ b/scene/3d/xr_hand_modifier_3d.cpp @@ -30,6 +30,7 @@ #include "xr_hand_modifier_3d.h" +#include "core/config/project_settings.h" #include "servers/xr/xr_pose.h" #include "servers/xr_server.h" @@ -283,6 +284,17 @@ void XRHandModifier3D::_skeleton_changed(Skeleton3D *p_old, Skeleton3D *p_new) { _get_joint_data(); } +PackedStringArray XRHandModifier3D::get_configuration_warnings() const { + PackedStringArray warnings = SkeletonModifier3D::get_configuration_warnings(); + + // Detect OpenXR without the Hand Tracking extension. + if (GLOBAL_GET("xr/openxr/enabled") && !GLOBAL_GET("xr/openxr/extensions/hand_tracking")) { + warnings.push_back("XRHandModifier3D requires the OpenXR Hand Tracking extension to be enabled."); + } + + return warnings; +} + void XRHandModifier3D::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { diff --git a/scene/3d/xr_hand_modifier_3d.h b/scene/3d/xr_hand_modifier_3d.h index 3d78f32b64..d58ccd0adf 100644 --- a/scene/3d/xr_hand_modifier_3d.h +++ b/scene/3d/xr_hand_modifier_3d.h @@ -55,6 +55,8 @@ public: void set_bone_update(BoneUpdate p_bone_update); BoneUpdate get_bone_update() const; + PackedStringArray get_configuration_warnings() const override; + void _notification(int p_what); protected: diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index b71f9bc0c4..214c1f77ca 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -77,7 +77,7 @@ void XRCamera3D::_pose_changed(const Ref<XRPose> &p_pose) { } PackedStringArray XRCamera3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Camera3D::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // Warn if the node has a parent which isn't an XROrigin3D! @@ -461,7 +461,7 @@ XRNode3D::~XRNode3D() { } PackedStringArray XRNode3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { // Warn if the node has a parent which isn't an XROrigin3D! @@ -644,7 +644,7 @@ Plane XRAnchor3D::get_plane() const { Vector<XROrigin3D *> XROrigin3D::origin_nodes; PackedStringArray XROrigin3D::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Node3D::get_configuration_warnings(); if (is_visible() && is_inside_tree()) { bool has_camera = false; diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 20dd12f8c3..1c5f40f56e 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -81,9 +81,8 @@ bool AnimationMixer::_set(const StringName &p_name, const Variant &p_value) { List<Variant> keys; d.get_key_list(&keys); for (const Variant &K : keys) { - StringName lib_name = K; - Ref<AnimationLibrary> lib = d[lib_name]; - add_animation_library(lib_name, lib); + Ref<AnimationLibrary> lib = d[K]; + add_animation_library(K, lib); } emit_signal(SNAME("animation_libraries_updated")); @@ -947,14 +946,6 @@ void AnimationMixer::_process_animation(double p_delta, bool p_update_only) { clear_animation_instances(); } -Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) { - Variant res; - if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) { - return res; - } - return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx); -} - Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) { #ifndef _3D_DISABLED switch (p_anim->track_get_type(p_track)) { @@ -974,6 +965,17 @@ Variant AnimationMixer::_post_process_key_value(const Ref<Animation> &p_anim, in return p_value; } +Variant AnimationMixer::post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, ObjectID p_object_id, int p_object_sub_idx) { + if (is_GDVIRTUAL_CALL_post_process_key_value) { + Variant res; + if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, p_object_id, p_object_sub_idx, res)) { + return res; + } + is_GDVIRTUAL_CALL_post_process_key_value = false; + } + return _post_process_key_value(p_anim, p_track, p_value, p_object_id, p_object_sub_idx); +} + void AnimationMixer::_blend_init() { // Check all tracks, see if they need modification. root_motion_position = Vector3(0, 0, 0); @@ -1080,22 +1082,26 @@ void AnimationMixer::_blend_calc_total_weight() { for (const AnimationInstance &ai : animation_instances) { Ref<Animation> a = ai.animation_data.animation; real_t weight = ai.playback_info.weight; - Vector<real_t> track_weights = ai.playback_info.track_weights; - Vector<Animation::TypeHash> processed_hashes; - for (int i = 0; i < a->get_track_count(); i++) { - if (!a->track_is_enabled(i)) { + const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr(); + int track_weights_count = ai.playback_info.track_weights.size(); + static LocalVector<Animation::TypeHash> processed_hashes; + processed_hashes.clear(); + const Vector<Animation::Track *> tracks = a->get_tracks(); + for (const Animation::Track *animation_track : tracks) { + if (!animation_track->enabled) { continue; } - Animation::TypeHash thash = a->track_get_type_hash(i); - if (!track_cache.has(thash) || processed_hashes.has(thash)) { + Animation::TypeHash thash = animation_track->thash; + TrackCache **track_ptr = track_cache.getptr(thash); + if (track_ptr == nullptr || processed_hashes.has(thash)) { // No path, but avoid error spamming. // Or, there is the case different track type with same path; These can be distinguished by hash. So don't add the weight doubly. continue; } - TrackCache *track = track_cache[thash]; + TrackCache *track = *track_ptr; int blend_idx = track_map[track->path]; ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); - real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight; + real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight; track->total_weight += blend; processed_hashes.push_back(thash); } @@ -1115,26 +1121,33 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { Animation::LoopedFlag looped_flag = ai.playback_info.looped_flag; bool is_external_seeking = ai.playback_info.is_external_seeking; real_t weight = ai.playback_info.weight; - Vector<real_t> track_weights = ai.playback_info.track_weights; + const real_t *track_weights_ptr = ai.playback_info.track_weights.ptr(); + int track_weights_count = ai.playback_info.track_weights.size(); bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream. bool seeked_backward = signbit(p_delta); #ifndef _3D_DISABLED bool calc_root = !seeked || is_external_seeking; #endif // _3D_DISABLED - - for (int i = 0; i < a->get_track_count(); i++) { - if (!a->track_is_enabled(i)) { + const Vector<Animation::Track *> tracks = a->get_tracks(); + Animation::Track *const *tracks_ptr = tracks.ptr(); + real_t a_length = a->get_length(); + int count = tracks.size(); + for (int i = 0; i < count; i++) { + const Animation::Track *animation_track = tracks_ptr[i]; + if (!animation_track->enabled) { continue; } - Animation::TypeHash thash = a->track_get_type_hash(i); - if (!track_cache.has(thash)) { + Animation::TypeHash thash = animation_track->thash; + TrackCache **track_ptr = track_cache.getptr(thash); + if (track_ptr == nullptr) { continue; // No path, but avoid error spamming. } - TrackCache *track = track_cache[thash]; - ERR_CONTINUE(!track_map.has(track->path)); - int blend_idx = track_map[track->path]; + TrackCache *track = *track_ptr; + int *blend_idx_ptr = track_map.getptr(track->path); + ERR_CONTINUE(blend_idx_ptr == nullptr); + int blend_idx = *blend_idx_ptr; ERR_CONTINUE(blend_idx < 0 || blend_idx >= track_count); - real_t blend = blend_idx < track_weights.size() ? track_weights[blend_idx] * weight : weight; + real_t blend = blend_idx < track_weights_count ? track_weights_ptr[blend_idx] * weight : weight; if (!deterministic) { // If non-deterministic, do normalization. // It would be better to make this if statement outside the for loop, but come here since too much code... @@ -1143,8 +1156,8 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } blend = blend / track->total_weight; } - Animation::TrackType ttype = a->track_get_type(i); - track->root_motion = root_motion_track == a->track_get_path(i); + Animation::TrackType ttype = animation_track->type; + track->root_motion = root_motion_track == animation_track->path; switch (ttype) { case Animation::TYPE_POSITION_3D: { #ifndef _3D_DISABLED @@ -1161,26 +1174,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { prev_time = 0; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; } } } else { - if (Animation::is_greater_approx(prev_time, (double)a->get_length())) { + if (Animation::is_greater_approx(prev_time, (double)a_length)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; @@ -1195,7 +1208,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } loc[0] = post_process_key_value(a, i, loc[0], t->object_id, t->bone_idx); - a->try_position_track_interpolate(i, (double)a->get_length(), &loc[1]); + a->try_position_track_interpolate(i, (double)a_length, &loc[1]); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; prev_time = 0; @@ -1210,7 +1223,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_position_track_interpolate(i, 0, &loc[1]); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } } Error err = a->try_position_track_interpolate(i, prev_time, &loc[0]); @@ -1221,7 +1234,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_position_track_interpolate(i, time, &loc[1]); loc[1] = post_process_key_value(a, i, loc[1], t->object_id, t->bone_idx); root_motion_cache.loc += (loc[1] - loc[0]) * blend; - prev_time = !backward ? 0 : (double)a->get_length(); + prev_time = !backward ? 0 : (double)a_length; } { Vector3 loc; @@ -1249,26 +1262,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { prev_time = 0; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; } } } else { - if (Animation::is_greater_approx(prev_time, (double)a->get_length())) { + if (Animation::is_greater_approx(prev_time, (double)a_length)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; @@ -1283,7 +1296,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx); - a->try_rotation_track_interpolate(i, (double)a->get_length(), &rot[1]); + a->try_rotation_track_interpolate(i, (double)a_length, &rot[1]); rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx); root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); prev_time = 0; @@ -1297,7 +1310,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { rot[0] = post_process_key_value(a, i, rot[0], t->object_id, t->bone_idx); a->try_rotation_track_interpolate(i, 0, &rot[1]); root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } } Error err = a->try_rotation_track_interpolate(i, prev_time, &rot[0]); @@ -1308,7 +1321,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_rotation_track_interpolate(i, time, &rot[1]); rot[1] = post_process_key_value(a, i, rot[1], t->object_id, t->bone_idx); root_motion_cache.rot = (root_motion_cache.rot * Quaternion().slerp(rot[0].inverse() * rot[1], blend)).normalized(); - prev_time = !backward ? 0 : (double)a->get_length(); + prev_time = !backward ? 0 : (double)a_length; } { Quaternion rot; @@ -1336,26 +1349,26 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { prev_time = 0; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; } } } else { - if (Animation::is_greater_approx(prev_time, (double)a->get_length())) { + if (Animation::is_greater_approx(prev_time, (double)a_length)) { switch (a->get_loop_mode()) { case Animation::LOOP_NONE: { - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } break; case Animation::LOOP_LINEAR: { - prev_time = Math::fposmod(prev_time, (double)a->get_length()); + prev_time = Math::fposmod(prev_time, (double)a_length); } break; case Animation::LOOP_PINGPONG: { - prev_time = Math::pingpong(prev_time, (double)a->get_length()); + prev_time = Math::pingpong(prev_time, (double)a_length); } break; default: break; @@ -1370,7 +1383,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { continue; } scale[0] = post_process_key_value(a, i, scale[0], t->object_id, t->bone_idx); - a->try_scale_track_interpolate(i, (double)a->get_length(), &scale[1]); + a->try_scale_track_interpolate(i, (double)a_length, &scale[1]); root_motion_cache.scale += (scale[1] - scale[0]) * blend; scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); prev_time = 0; @@ -1385,7 +1398,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_scale_track_interpolate(i, 0, &scale[1]); scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); root_motion_cache.scale += (scale[1] - scale[0]) * blend; - prev_time = (double)a->get_length(); + prev_time = (double)a_length; } } Error err = a->try_scale_track_interpolate(i, prev_time, &scale[0]); @@ -1396,7 +1409,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { a->try_scale_track_interpolate(i, time, &scale[1]); scale[1] = post_process_key_value(a, i, scale[1], t->object_id, t->bone_idx); root_motion_cache.scale += (scale[1] - scale[0]) * blend; - prev_time = !backward ? 0 : (double)a->get_length(); + prev_time = !backward ? 0 : (double)a_length; } { Vector3 scale; @@ -1560,7 +1573,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } PlayingAudioTrackInfo &track_info = t->playing_streams[oid]; - track_info.length = a->get_length(); + track_info.length = a_length; track_info.time = time; track_info.volume += blend; track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE; @@ -1679,7 +1692,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { at_anim_pos = Math::fposmod(time - pos, (double)anim->get_length()); // Seek to loop. } break; case Animation::LOOP_PINGPONG: { - at_anim_pos = Math::pingpong(time - pos, (double)a->get_length()); + at_anim_pos = Math::pingpong(time - pos, (double)a_length); } break; default: break; @@ -1717,6 +1730,7 @@ void AnimationMixer::_blend_process(double p_delta, bool p_update_only) { } } } + is_GDVIRTUAL_CALL_post_process_key_value = true; } void AnimationMixer::_blend_apply() { diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index c029c68ae1..5482197fbd 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -48,6 +48,7 @@ class AnimationMixer : public Node { #endif // TOOLS_ENABLED bool reset_on_save = true; + bool is_GDVIRTUAL_CALL_post_process_key_value = true; public: enum AnimationCallbackModeProcess { diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index c5a6a99d07..19080e61de 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -203,10 +203,11 @@ AnimationNode::NodeTimeInfo AnimationNode::_blend_node(Ref<AnimationNode> p_node } for (const KeyValue<NodePath, bool> &E : filter) { - if (!process_state->track_map.has(E.key)) { + const HashMap<NodePath, int> &map = *process_state->track_map; + if (!map.has(E.key)) { continue; } - int idx = process_state->track_map[E.key]; + int idx = map[E.key]; blendw[idx] = 1.0; // Filtered goes to one. } @@ -618,7 +619,7 @@ bool AnimationTree::_blend_pre_process(double p_delta, int p_track_count, const process_state.valid = true; process_state.invalid_reasons = ""; process_state.last_pass = process_pass; - process_state.track_map = p_track_map; + process_state.track_map = &p_track_map; // Init node state for root AnimationNode. root_animation_node->node_state.track_weights.resize(p_track_count); @@ -680,7 +681,7 @@ uint64_t AnimationTree::get_last_process_pass() const { } PackedStringArray AnimationTree::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = AnimationMixer::get_configuration_warnings(); if (!root_animation_node.is_valid()) { warnings.push_back(RTR("No root AnimationNode for the graph is set.")); } diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index aa497ff1d6..d4b7bf31c9 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -106,7 +106,7 @@ public: // Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree. struct ProcessState { AnimationTree *tree = nullptr; - HashMap<NodePath, int> track_map; // TODO: Is there a better way to manage filter/tracks? + const HashMap<NodePath, int> *track_map; // TODO: Is there a better way to manage filter/tracks? bool is_testing = false; bool valid = false; String invalid_reasons; diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index e8be38e680..c3287035ff 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -40,8 +40,18 @@ void CodeEdit::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: { set_gutter_width(main_gutter, get_line_height()); - set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); + _update_line_number_gutter_width(); set_gutter_width(fold_gutter, get_line_height() / 1.2); + _clear_line_number_text_cache(); + } break; + + case NOTIFICATION_TRANSLATION_CHANGED: + [[fallthrough]]; + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + [[fallthrough]]; + case NOTIFICATION_VISIBILITY_CHANGED: { + // Avoid having many hidden text editors with unused cache filling up memory. + _clear_line_number_text_cache(); } break; case NOTIFICATION_DRAW: { @@ -1287,9 +1297,9 @@ bool CodeEdit::is_drawing_executing_lines_gutter() const { } void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { + bool hovering = get_hovered_gutter() == Vector2i(main_gutter, p_line); if (draw_breakpoints && theme_cache.breakpoint_icon.is_valid()) { bool breakpointed = is_line_breakpointed(p_line); - bool hovering = p_region.has_point(get_local_mouse_pos()); bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (breakpointed || (hovering && !is_dragging_cursor() && !shift_pressed)) { @@ -1308,7 +1318,6 @@ void CodeEdit::_main_gutter_draw_callback(int p_line, int p_gutter, const Rect2 if (draw_bookmarks && theme_cache.bookmark_icon.is_valid()) { bool bookmarked = is_line_bookmarked(p_line); - bool hovering = p_region.has_point(get_local_mouse_pos()); bool shift_pressed = Input::get_singleton()->is_key_pressed(Key::SHIFT); if (bookmarked || (hovering && !is_dragging_cursor() && shift_pressed)) { @@ -1442,7 +1451,13 @@ bool CodeEdit::is_draw_line_numbers_enabled() const { } void CodeEdit::set_line_numbers_zero_padded(bool p_zero_padded) { - p_zero_padded ? line_number_padding = "0" : line_number_padding = " "; + String new_line_number_padding = p_zero_padded ? "0" : " "; + if (line_number_padding == new_line_number_padding) { + return; + } + + line_number_padding = new_line_number_padding; + _clear_line_number_text_cache(); queue_redraw(); } @@ -1451,19 +1466,55 @@ bool CodeEdit::is_line_numbers_zero_padded() const { } void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { - String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); - if (is_localizing_numeral_system()) { - fc = TS->format_number(fc); - } - Ref<TextLine> tl; - tl.instantiate(); - tl->add_string(fc, theme_cache.font, theme_cache.font_size); - int yofs = p_region.position.y + (get_line_height() - tl->get_size().y) / 2; + if (!Rect2(Vector2(0, 0), get_size()).intersects(p_region)) { + return; + } + + bool rtl = is_layout_rtl(); + HashMap<int, RID>::Iterator E = line_number_text_cache.find(p_line); + RID text_rid; + if (E) { + text_rid = E->value; + } else { + String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); + if (is_localizing_numeral_system()) { + fc = TS->format_number(fc); + } + + text_rid = TS->create_shaped_text(); + if (theme_cache.font.is_valid()) { + TS->shaped_text_add_string(text_rid, fc, theme_cache.font->get_rids(), theme_cache.font_size, theme_cache.font->get_opentype_features()); + } + line_number_text_cache.insert(p_line, text_rid); + } + + Size2 text_size = TS->shaped_text_get_size(text_rid); + Point2 ofs = p_region.get_center() - text_size / 2; + ofs.y += TS->shaped_text_get_ascent(text_rid); + + if (rtl) { + ofs.x = p_region.position.x; + } else { + ofs.x = p_region.get_end().x - text_size.width; + } + Color number_color = get_line_gutter_item_color(p_line, line_number_gutter); if (number_color == Color(1, 1, 1)) { number_color = theme_cache.line_number_color; } - tl->draw(get_canvas_item(), Point2(p_region.position.x, yofs), number_color); + + TS->shaped_text_draw(text_rid, get_canvas_item(), ofs, -1, -1, number_color); +} + +void CodeEdit::_clear_line_number_text_cache() { + for (const KeyValue<int, RID> &KV : line_number_text_cache) { + TS->free_rid(KV.value); + } + line_number_text_cache.clear(); +} + +void CodeEdit::_update_line_number_gutter_width() { + set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); } /* Fold Gutter */ @@ -1983,12 +2034,18 @@ Point2 CodeEdit::get_delimiter_end_position(int p_line, int p_column) const { /* Code hint */ void CodeEdit::set_code_hint(const String &p_hint) { + if (code_hint == p_hint) { + return; + } code_hint = p_hint; code_hint_xpos = -0xFFFF; queue_redraw(); } void CodeEdit::set_code_hint_draw_below(bool p_below) { + if (code_hint_draw_below == p_below) { + return; + } code_hint_draw_below = p_below; queue_redraw(); } @@ -3609,16 +3666,13 @@ void CodeEdit::_text_changed() { } int lc = get_line_count(); - line_number_digits = 1; - while (lc /= 10) { - line_number_digits++; - } - - if (theme_cache.font.is_valid()) { - set_gutter_width(line_number_gutter, (line_number_digits + 1) * theme_cache.font->get_char_size('0', theme_cache.font_size).width); + int new_line_number_digits = log10l(lc) + 1; + if (line_number_digits != new_line_number_digits) { + _clear_line_number_text_cache(); } + line_number_digits = new_line_number_digits; + _update_line_number_gutter_width(); - lc = get_line_count(); List<int> breakpoints; for (const KeyValue<int, bool> &E : breakpointed_lines) { breakpoints.push_back(E.key); @@ -3705,6 +3759,7 @@ CodeEdit::CodeEdit() { } CodeEdit::~CodeEdit() { + _clear_line_number_text_cache(); } // Return true if l should come before r diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 09340be035..ab443e95e1 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -113,6 +113,9 @@ private: int line_number_gutter = -1; int line_number_digits = 1; String line_number_padding = " "; + HashMap<int, RID> line_number_text_cache; + void _clear_line_number_text_cache(); + void _update_line_number_gutter_width(); void _line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region); /* Fold Gutter */ diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 15ada0021a..e030828595 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -248,7 +248,7 @@ void Control::get_argument_options(const StringName &p_function, int p_idx, List PackedStringArray Control::get_configuration_warnings() const { ERR_READ_THREAD_GUARD_V(PackedStringArray()); - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = CanvasItem::get_configuration_warnings(); if (data.mouse_filter == MOUSE_FILTER_IGNORE && !data.tooltip.is_empty()) { warnings.push_back(RTR("The Hint Tooltip won't be displayed as the control's Mouse Filter is set to \"Ignore\". To solve this, set the Mouse Filter to \"Stop\" or \"Pass\".")); @@ -2356,6 +2356,24 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons points[2] = xform.xform(c->get_size()); points[3] = xform.xform(Point2(0, c->get_size().y)); + // Tie-breaking aims to address situations where a potential focus neighbor's bounding rect + // is right next to the currently focused control (e.g. in BoxContainer with + // separation overridden to 0). This needs specific handling so that the correct + // focus neighbor is selected. + + // Calculate centers of the potential neighbor, currently focused, and closest controls. + Point2 center = xform.xform(0.5 * c->get_size()); + // We only have the points, not an actual reference. + Point2 p_center = 0.25 * (p_points[0] + p_points[1] + p_points[2] + p_points[3]); + Point2 closest_center; + bool should_tiebreak = false; + if (*r_closest != nullptr) { + should_tiebreak = true; + Control *closest = *r_closest; + Transform2D closest_xform = closest->get_global_transform(); + closest_center = closest_xform.xform(0.5 * closest->get_size()); + } + real_t min = 1e7; for (int i = 0; i < 4; i++) { @@ -2376,10 +2394,15 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons Vector2 pa, pb; real_t d = Geometry2D::get_closest_points_between_segments(la, lb, fa, fb, pa, pb); - //real_t d = Geometry2D::get_closest_distance_between_segments(Vector3(la.x,la.y,0),Vector3(lb.x,lb.y,0),Vector3(fa.x,fa.y,0),Vector3(fb.x,fb.y,0)); if (d < r_closest_dist) { r_closest_dist = d; *r_closest = c; + } else if (should_tiebreak && d == r_closest_dist) { + // Tie-break in favor of the control most aligned with p_dir. + if (p_dir.dot((center - p_center).normalized()) > p_dir.dot((closest_center - p_center).normalized())) { + r_closest_dist = d; + *r_closest = c; + } } } } diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index 1a1aa5ccb0..373488b0fc 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -124,6 +124,8 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int file_name = ProjectSettings::get_singleton()->localize_path(file_name); } } + selected_options = p_selected_options; + String f = files[0]; if (mode == FILE_MODE_OPEN_FILES) { emit_signal(SNAME("files_selected"), files); @@ -155,7 +157,6 @@ void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int } file->set_text(f); dir->set_text(f.get_base_dir()); - selected_options = p_selected_options; filter->select(p_filter); } diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 0006204ae3..c2818edd9c 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -68,10 +68,15 @@ void LineEdit::_move_caret_left(bool p_select, bool p_move_by_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < cc) { - cc = words[i]; - break; + if (words.is_empty() || cc <= words[0]) { + // Move to the start when there are no more words. + cc = 0; + } else { + for (int i = words.size() - 2; i >= 0; i = i - 2) { + if (words[i] < cc) { + cc = words[i]; + break; + } } } @@ -101,10 +106,15 @@ void LineEdit::_move_caret_right(bool p_select, bool p_move_by_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > cc) { - cc = words[i]; - break; + if (words.is_empty() || cc >= words[words.size() - 1]) { + // Move to the end when there are no more words. + cc = text.length(); + } else { + for (int i = 1; i < words.size(); i = i + 2) { + if (words[i] > cc) { + cc = words[i]; + break; + } } } @@ -159,10 +169,15 @@ void LineEdit::_backspace(bool p_word, bool p_all_to_left) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = words.size() - 2; i >= 0; i = i - 2) { - if (words[i] < cc) { - cc = words[i]; - break; + if (words.is_empty() || cc <= words[0]) { + // Delete to the start when there are no more words. + cc = 0; + } else { + for (int i = words.size() - 2; i >= 0; i = i - 2) { + if (words[i] < cc) { + cc = words[i]; + break; + } } } @@ -198,10 +213,15 @@ void LineEdit::_delete(bool p_word, bool p_all_to_right) { if (p_word) { int cc = caret_column; PackedInt32Array words = TS->shaped_text_get_word_breaks(text_rid); - for (int i = 1; i < words.size(); i = i + 2) { - if (words[i] > cc) { - cc = words[i]; - break; + if (words.is_empty() || cc >= words[words.size() - 1]) { + // Delete to the end when there are no more words. + cc = text.length(); + } else { + for (int i = 1; i < words.size(); i = i + 2) { + if (words[i] > cc) { + cc = words[i]; + break; + } } } @@ -698,13 +718,13 @@ bool LineEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const return drop_override; } - return is_editable() && p_data.get_type() == Variant::STRING; + return is_editable() && p_data.is_string(); } void LineEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Control::drop_data(p_point, p_data); - if (p_data.get_type() == Variant::STRING && is_editable()) { + if (p_data.is_string() && is_editable()) { set_caret_at_pixel_pos(p_point.x); int caret_column_tmp = caret_column; bool is_inside_sel = selection.enabled && caret_column >= selection.begin && caret_column <= selection.end; diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp index c8b022d622..46c9c7cccc 100644 --- a/scene/gui/menu_bar.cpp +++ b/scene/gui/menu_bar.cpp @@ -218,15 +218,18 @@ void MenuBar::bind_global_menu() { int global_start_idx = -1; int count = nmenu->get_item_count(main_menu); String prev_tag; - for (int i = 0; i < count; i++) { - String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1); - if (!tag.is_empty() && tag != prev_tag) { - if (i >= start_index) { - global_start_idx = i; - break; + if (start_index >= 0) { + for (int i = 0; i < count; i++) { + String tag = nmenu->get_item_tag(main_menu, i).operator String().get_slice("#", 1); + if (!tag.is_empty() && tag != prev_tag) { + MenuBar *mb = Object::cast_to<MenuBar>(ObjectDB::get_instance(ObjectID(static_cast<uint64_t>(tag.to_int())))); + if (mb && mb->get_start_index() >= start_index) { + global_start_idx = i; + break; + } } + prev_tag = tag; } - prev_tag = tag; } if (global_start_idx == -1) { global_start_idx = count; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 5de4cdaf59..3c04094526 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -2592,14 +2592,6 @@ void PopupMenu::clear_autohide_areas() { autohide_areas.clear(); } -void PopupMenu::take_mouse_focus() { - ERR_FAIL_COND(!is_inside_tree()); - - if (get_parent()) { - get_parent()->get_viewport()->pass_mouse_focus_to(this, control); - } -} - bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) { if (property_helper.property_set_value(p_name, p_value)) { return true; diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 5313dae404..b8aa51c1ad 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -369,8 +369,6 @@ public: virtual void popup(const Rect2i &p_bounds = Rect2i()) override; virtual void set_visible(bool p_visible) override; - void take_mouse_focus(); - PopupMenu(); ~PopupMenu(); }; diff --git a/scene/gui/range.cpp b/scene/gui/range.cpp index 83359653f1..d7b1a4933d 100644 --- a/scene/gui/range.cpp +++ b/scene/gui/range.cpp @@ -31,7 +31,7 @@ #include "range.h" PackedStringArray Range::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Control::get_configuration_warnings(); if (shared->exp_ratio && shared->min <= 0) { warnings.push_back(RTR("If \"Exp Edit\" is enabled, \"Min Value\" must be greater than 0.")); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 0395dffad9..715b682342 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -547,7 +547,7 @@ float RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font> if (font_size_it && font_size_it->font_size > 0) { font_size = font_size_it->font_size; } - l.text_buf->add_string("\n", font, font_size); + l.text_buf->add_string(String::chr(0x200B), font, font_size); txt += "\n"; l.char_count++; remaining_characters--; @@ -912,84 +912,6 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o double uth = TS->shaped_text_get_underline_thickness(rid); off.y += l_ascent; - // Draw inlined objects. - Array objects = TS->shaped_text_get_objects(rid); - for (int i = 0; i < objects.size(); i++) { - Item *it = items.get_or_null(objects[i]); - if (it != nullptr) { - Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]); - if (trim_chars && l.char_offset + obj_range.y > visible_characters) { - continue; - } - if (trim_glyphs_ltr || trim_glyphs_rtl) { - int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]); - if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) { - continue; - } - } - Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); - switch (it->type) { - case ITEM_IMAGE: { - ItemImage *img = static_cast<ItemImage *>(it); - if (img->pad) { - Size2 pad_size = rect.size.min(img->image->get_size()); - Vector2 pad_off = (rect.size - pad_size) / 2; - img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color); - } else { - img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); - } - } break; - case ITEM_TABLE: { - ItemTable *table = static_cast<ItemTable *>(it); - Color odd_row_bg = theme_cache.table_odd_row_bg; - Color even_row_bg = theme_cache.table_even_row_bg; - Color border = theme_cache.table_border; - float h_separation = theme_cache.table_h_separation; - float v_separation = theme_cache.table_v_separation; - - int col_count = table->columns.size(); - int row_count = table->rows.size(); - - int idx = 0; - for (Item *E : table->subitems) { - ItemFrame *frame = static_cast<ItemFrame *>(E); - - int col = idx % col_count; - int row = idx / col_count; - - if (frame->lines.size() != 0 && row < row_count) { - Vector2 coff = frame->lines[0].offset; - if (rtl) { - coff.x = rect.size.width - table->columns[col].width - coff.x; - } - if (row % 2 == 0) { - Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg; - if (c.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); - } - } else { - Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg; - if (c.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); - } - } - Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border; - if (bc.a > 0.0) { - draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false); - } - } - - for (int j = 0; j < (int)frame->lines.size(); j++) { - _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs); - } - idx++; - } - } break; - default: - break; - } - } - } const Glyph *glyphs = TS->shaped_text_get_glyphs(rid); int gl_size = TS->shaped_text_get_glyph_count(rid); @@ -1005,6 +927,86 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o int processed_glyphs_step = 0; for (int step = DRAW_STEP_BACKGROUND; step < DRAW_STEP_MAX; step++) { + if (step == DRAW_STEP_TEXT) { + // Draw inlined objects. + Array objects = TS->shaped_text_get_objects(rid); + for (int i = 0; i < objects.size(); i++) { + Item *it = items.get_or_null(objects[i]); + if (it != nullptr) { + Vector2i obj_range = TS->shaped_text_get_object_range(rid, objects[i]); + if (trim_chars && l.char_offset + obj_range.y > visible_characters) { + continue; + } + if (trim_glyphs_ltr || trim_glyphs_rtl) { + int obj_glyph = r_processed_glyphs + TS->shaped_text_get_object_glyph(rid, objects[i]); + if ((trim_glyphs_ltr && (obj_glyph >= visible_glyphs)) || (trim_glyphs_rtl && (obj_glyph < total_glyphs - visible_glyphs))) { + continue; + } + } + Rect2 rect = TS->shaped_text_get_object_rect(rid, objects[i]); + switch (it->type) { + case ITEM_IMAGE: { + ItemImage *img = static_cast<ItemImage *>(it); + if (img->pad) { + Size2 pad_size = rect.size.min(img->image->get_size()); + Vector2 pad_off = (rect.size - pad_size) / 2; + img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off + pad_off, pad_size), false, img->color); + } else { + img->image->draw_rect(ci, Rect2(p_ofs + rect.position + off, rect.size), false, img->color); + } + } break; + case ITEM_TABLE: { + ItemTable *table = static_cast<ItemTable *>(it); + Color odd_row_bg = theme_cache.table_odd_row_bg; + Color even_row_bg = theme_cache.table_even_row_bg; + Color border = theme_cache.table_border; + float h_separation = theme_cache.table_h_separation; + float v_separation = theme_cache.table_v_separation; + + int col_count = table->columns.size(); + int row_count = table->rows.size(); + + int idx = 0; + for (Item *E : table->subitems) { + ItemFrame *frame = static_cast<ItemFrame *>(E); + + int col = idx % col_count; + int row = idx / col_count; + + if (frame->lines.size() != 0 && row < row_count) { + Vector2 coff = frame->lines[0].offset; + if (rtl) { + coff.x = rect.size.width - table->columns[col].width - coff.x; + } + if (row % 2 == 0) { + Color c = frame->odd_row_bg != Color(0, 0, 0, 0) ? frame->odd_row_bg : odd_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } + } else { + Color c = frame->even_row_bg != Color(0, 0, 0, 0) ? frame->even_row_bg : even_row_bg; + if (c.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), c, true); + } + } + Color bc = frame->border != Color(0, 0, 0, 0) ? frame->border : border; + if (bc.a > 0.0) { + draw_rect(Rect2(p_ofs + rect.position + off + coff - frame->padding.position - Vector2(h_separation * 0.5, v_separation * 0.5).floor(), Size2(table->columns[col].width + h_separation + frame->padding.position.x + frame->padding.size.x, table->rows[row])), bc, false); + } + } + + for (int j = 0; j < (int)frame->lines.size(); j++) { + _draw_line(frame, j, p_ofs + rect.position + off + Vector2(0, frame->lines[j].offset.y), rect.size.x, p_base_color, p_outline_size, p_outline_color, p_font_shadow_color, p_shadow_outline_size, p_shadow_ofs, r_processed_glyphs); + } + idx++; + } + } break; + default: + break; + } + } + } + } Vector2 off_step = off; processed_glyphs_step = r_processed_glyphs; @@ -3043,7 +3045,7 @@ void RichTextLabel::add_text(const String &p_text) { int pos = 0; while (pos < p_text.length()) { - int end = p_text.find("\n", pos); + int end = p_text.find_char('\n', pos); String line; bool eol = false; if (end == -1) { @@ -4092,6 +4094,74 @@ void RichTextLabel::parse_bbcode(const String &p_bbcode) { append_text(p_bbcode); } +String RichTextLabel::_get_tag_value(const String &p_tag) { + return p_tag.substr(p_tag.find_char('=') + 1); +} + +int RichTextLabel::_find_unquoted(const String &p_src, char32_t p_chr, int p_from) { + if (p_from < 0) { + return -1; + } + + const int len = p_src.length(); + if (len == 0) { + return -1; + } + + const char32_t *src = p_src.get_data(); + bool in_single_quote = false; + bool in_double_quote = false; + for (int i = p_from; i < len; i++) { + if (in_double_quote) { + if (src[i] == '"') { + in_double_quote = false; + } + } else if (in_single_quote) { + if (src[i] == '\'') { + in_single_quote = false; + } + } else { + if (src[i] == '"') { + in_double_quote = true; + } else if (src[i] == '\'') { + in_single_quote = true; + } else if (src[i] == p_chr) { + return i; + } + } + } + + return -1; +} + +Vector<String> RichTextLabel::_split_unquoted(const String &p_src, char32_t p_splitter) { + Vector<String> ret; + + if (p_src.is_empty()) { + return ret; + } + + int from = 0; + int len = p_src.length(); + + while (true) { + int end = _find_unquoted(p_src, p_splitter, from); + if (end < 0) { + end = len; + } + if (end > from) { + ret.push_back(p_src.substr(from, end - from)); + } + if (end == len) { + break; + } + + from = end + 1; + } + + return ret; +} + void RichTextLabel::append_text(const String &p_bbcode) { _stop_thread(); MutexLock data_lock(data_mutex); @@ -4110,7 +4180,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { set_process_internal(false); while (pos <= p_bbcode.length()) { - int brk_pos = p_bbcode.find("[", pos); + int brk_pos = p_bbcode.find_char('[', pos); if (brk_pos < 0) { brk_pos = p_bbcode.length(); @@ -4135,7 +4205,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { break; //nothing else to add } - int brk_end = p_bbcode.find("]", brk_pos + 1); + int brk_end = _find_unquoted(p_bbcode, ']', brk_pos + 1); if (brk_end == -1) { //no close, add the rest @@ -4145,7 +4215,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } String tag = p_bbcode.substr(brk_pos + 1, brk_end - brk_pos - 1); - Vector<String> split_tag_block = tag.split(" ", false); + Vector<String> split_tag_block = _split_unquoted(tag, ' '); // Find optional parameters. String bbcode_name; @@ -4155,7 +4225,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { bbcode_name = split_tag_block[0]; for (int i = 1; i < split_tag_block.size(); i++) { const String &expr = split_tag_block[i]; - int value_pos = expr.find("="); + int value_pos = expr.find_char('='); if (value_pos > -1) { bbcode_options[expr.substr(0, value_pos)] = expr.substr(value_pos + 1).unquote(); } @@ -4166,7 +4236,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { // Find main parameter. String bbcode_value; - int main_value_pos = bbcode_name.find("="); + int main_value_pos = bbcode_name.find_char('='); if (main_value_pos > -1) { bbcode_value = bbcode_name.substr(main_value_pos + 1); bbcode_name = bbcode_name.substr(0, main_value_pos); @@ -4265,10 +4335,10 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("table=")) { - Vector<String> subtag = tag.substr(6, tag.length()).split(","); + Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U','); _normalize_subtags(subtag); - int columns = subtag[0].to_int(); + int columns = (subtag.is_empty()) ? 1 : subtag[0].to_int(); if (columns < 1) { columns = 1; } @@ -4315,7 +4385,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("cell=")) { - int ratio = tag.substr(5, tag.length()).to_int(); + int ratio = _get_tag_value(tag).to_int(); if (ratio < 1) { ratio = 1; } @@ -4326,54 +4396,45 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("cell"); } else if (tag.begins_with("cell ")) { - Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - _normalize_subtags(subtag); - - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "expand") { - int ratio = subtag_a[1].to_int(); - if (ratio < 1) { - ratio = 1; - } - set_table_column_expand(get_current_table_column(), true, ratio); - } + OptionMap::Iterator expand_option = bbcode_options.find("expand"); + if (expand_option) { + int ratio = expand_option->value.to_int(); + if (ratio < 1) { + ratio = 1; } + set_table_column_expand(get_current_table_column(), true, ratio); } + push_cell(); const Color fallback_color = Color(0, 0, 0, 0); - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "border") { - Color color = Color::from_string(subtag_a[1], fallback_color); - set_cell_border_color(color); - } else if (subtag_a[0] == "bg") { - Vector<String> subtag_b = subtag_a[1].split(","); - _normalize_subtags(subtag_b); - if (subtag_b.size() == 2) { - Color color1 = Color::from_string(subtag_b[0], fallback_color); - Color color2 = Color::from_string(subtag_b[1], fallback_color); - set_cell_row_background_color(color1, color2); - } - if (subtag_b.size() == 1) { - Color color1 = Color::from_string(subtag_a[1], fallback_color); - set_cell_row_background_color(color1, color1); - } - } else if (subtag_a[0] == "padding") { - Vector<String> subtag_b = subtag_a[1].split(","); - _normalize_subtags(subtag_b); + OptionMap::Iterator border_option = bbcode_options.find("border"); + if (border_option) { + Color color = Color::from_string(border_option->value, fallback_color); + set_cell_border_color(color); + } + OptionMap::Iterator bg_option = bbcode_options.find("bg"); + if (bg_option) { + Vector<String> subtag_b = _split_unquoted(bg_option->value, U','); + _normalize_subtags(subtag_b); - if (subtag_b.size() == 4) { - set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float())); - } - } + if (subtag_b.size() == 2) { + Color color1 = Color::from_string(subtag_b[0], fallback_color); + Color color2 = Color::from_string(subtag_b[1], fallback_color); + set_cell_row_background_color(color1, color2); + } + if (subtag_b.size() == 1) { + Color color1 = Color::from_string(bg_option->value, fallback_color); + set_cell_row_background_color(color1, color1); + } + } + OptionMap::Iterator padding_option = bbcode_options.find("padding"); + if (padding_option) { + Vector<String> subtag_b = _split_unquoted(padding_option->value, U','); + _normalize_subtags(subtag_b); + + if (subtag_b.size() == 4) { + set_cell_padding(Rect2(subtag_b[0].to_float(), subtag_b[1].to_float(), subtag_b[2].to_float(), subtag_b[3].to_float())); } } @@ -4390,7 +4451,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("char=")) { - int32_t char_code = tag.substr(5, tag.length()).hex_to_int(); + int32_t char_code = _get_tag_value(tag).hex_to_int(); add_text(String::chr(char_code)); pos = brk_end + 1; } else if (tag == "lb") { @@ -4469,7 +4530,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("ul bullet=")) { - String bullet = tag.substr(10, 1); + String bullet = _get_tag_value(tag); indent_level++; push_list(indent_level, LIST_DOTS, false, bullet); pos = brk_end + 1; @@ -4505,7 +4566,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front(tag); } else if (tag.begins_with("lang=")) { - String lang = tag.substr(5, tag.length()).unquote(); + String lang = _get_tag_value(tag).unquote(); push_language(lang); pos = brk_end + 1; tag_stack.push_front("lang"); @@ -4514,89 +4575,104 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag.begins_with("p ")) { - Vector<String> subtag = tag.substr(2, tag.length()).split(" "); - _normalize_subtags(subtag); - HorizontalAlignment alignment = HORIZONTAL_ALIGNMENT_LEFT; Control::TextDirection dir = Control::TEXT_DIRECTION_INHERITED; String lang = language; PackedFloat32Array tab_stops = default_tab_stops; TextServer::StructuredTextParser st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; BitField<TextServer::JustificationFlag> jst_flags = default_jst_flags; - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "justification_flags" || subtag_a[0] == "jst") { - Vector<String> subtag_b = subtag_a[1].split(","); - jst_flags = 0; // Clear flags. - for (const String &E : subtag_b) { - if (E == "kashida" || E == "k") { - jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA); - } else if (E == "word" || E == "w") { - jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND); - } else if (E == "trim" || E == "tr") { - jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); - } else if (E == "after_last_tab" || E == "lt") { - jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB); - } else if (E == "skip_last" || E == "sl") { - jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE); - } else if (E == "skip_last_with_chars" || E == "sv") { - jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS); - } else if (E == "do_not_skip_single" || E == "ns") { - jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); - } - } - } else if (subtag_a[0] == "tab_stops") { - Vector<String> splitters; - splitters.push_back(","); - splitters.push_back(";"); - tab_stops = subtag_a[1].split_floats_mk(splitters); - } else if (subtag_a[0] == "align") { - if (subtag_a[1] == "l" || subtag_a[1] == "left") { - alignment = HORIZONTAL_ALIGNMENT_LEFT; - } else if (subtag_a[1] == "c" || subtag_a[1] == "center") { - alignment = HORIZONTAL_ALIGNMENT_CENTER; - } else if (subtag_a[1] == "r" || subtag_a[1] == "right") { - alignment = HORIZONTAL_ALIGNMENT_RIGHT; - } else if (subtag_a[1] == "f" || subtag_a[1] == "fill") { - alignment = HORIZONTAL_ALIGNMENT_FILL; - } - } else if (subtag_a[0] == "dir" || subtag_a[0] == "direction") { - if (subtag_a[1] == "a" || subtag_a[1] == "auto") { - dir = Control::TEXT_DIRECTION_AUTO; - } else if (subtag_a[1] == "l" || subtag_a[1] == "ltr") { - dir = Control::TEXT_DIRECTION_LTR; - } else if (subtag_a[1] == "r" || subtag_a[1] == "rtl") { - dir = Control::TEXT_DIRECTION_RTL; - } - } else if (subtag_a[0] == "lang" || subtag_a[0] == "language") { - lang = subtag_a[1]; - } else if (subtag_a[0] == "st" || subtag_a[0] == "bidi_override") { - if (subtag_a[1] == "d" || subtag_a[1] == "default") { - st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; - } else if (subtag_a[1] == "u" || subtag_a[1] == "uri") { - st_parser_type = TextServer::STRUCTURED_TEXT_URI; - } else if (subtag_a[1] == "f" || subtag_a[1] == "file") { - st_parser_type = TextServer::STRUCTURED_TEXT_FILE; - } else if (subtag_a[1] == "e" || subtag_a[1] == "email") { - st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL; - } else if (subtag_a[1] == "l" || subtag_a[1] == "list") { - st_parser_type = TextServer::STRUCTURED_TEXT_LIST; - } else if (subtag_a[1] == "n" || subtag_a[1] == "gdscript") { - st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT; - } else if (subtag_a[1] == "c" || subtag_a[1] == "custom") { - st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM; - } + + OptionMap::Iterator justification_flags_option = bbcode_options.find("justification_flags"); + if (!justification_flags_option) { + justification_flags_option = bbcode_options.find("jst"); + } + if (justification_flags_option) { + Vector<String> subtag_b = _split_unquoted(justification_flags_option->value, U','); + jst_flags = 0; // Clear flags. + for (const String &E : subtag_b) { + if (E == "kashida" || E == "k") { + jst_flags.set_flag(TextServer::JUSTIFICATION_KASHIDA); + } else if (E == "word" || E == "w") { + jst_flags.set_flag(TextServer::JUSTIFICATION_WORD_BOUND); + } else if (E == "trim" || E == "tr") { + jst_flags.set_flag(TextServer::JUSTIFICATION_TRIM_EDGE_SPACES); + } else if (E == "after_last_tab" || E == "lt") { + jst_flags.set_flag(TextServer::JUSTIFICATION_AFTER_LAST_TAB); + } else if (E == "skip_last" || E == "sl") { + jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE); + } else if (E == "skip_last_with_chars" || E == "sv") { + jst_flags.set_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS); + } else if (E == "do_not_skip_single" || E == "ns") { + jst_flags.set_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE); } } } + OptionMap::Iterator tab_stops_option = bbcode_options.find("tab_stops"); + if (tab_stops_option) { + Vector<String> splitters; + splitters.push_back(","); + splitters.push_back(";"); + tab_stops = tab_stops_option->value.split_floats_mk(splitters); + } + OptionMap::Iterator align_option = bbcode_options.find("align"); + if (align_option) { + if (align_option->value == "l" || align_option->value == "left") { + alignment = HORIZONTAL_ALIGNMENT_LEFT; + } else if (align_option->value == "c" || align_option->value == "center") { + alignment = HORIZONTAL_ALIGNMENT_CENTER; + } else if (align_option->value == "r" || align_option->value == "right") { + alignment = HORIZONTAL_ALIGNMENT_RIGHT; + } else if (align_option->value == "f" || align_option->value == "fill") { + alignment = HORIZONTAL_ALIGNMENT_FILL; + } + } + OptionMap::Iterator direction_option = bbcode_options.find("direction"); + if (!direction_option) { + direction_option = bbcode_options.find("dir"); + } + if (direction_option) { + if (direction_option->value == "a" || direction_option->value == "auto") { + dir = Control::TEXT_DIRECTION_AUTO; + } else if (direction_option->value == "l" || direction_option->value == "ltr") { + dir = Control::TEXT_DIRECTION_LTR; + } else if (direction_option->value == "r" || direction_option->value == "rtl") { + dir = Control::TEXT_DIRECTION_RTL; + } + } + OptionMap::Iterator language_option = bbcode_options.find("language"); + if (!language_option) { + language_option = bbcode_options.find("lang"); + } + if (language_option) { + lang = language_option->value; + } + OptionMap::Iterator bidi_override_option = bbcode_options.find("bidi_override"); + if (!bidi_override_option) { + bidi_override_option = bbcode_options.find("st"); + } + if (bidi_override_option) { + if (bidi_override_option->value == "d" || bidi_override_option->value == "default") { + st_parser_type = TextServer::STRUCTURED_TEXT_DEFAULT; + } else if (bidi_override_option->value == "u" || bidi_override_option->value == "uri") { + st_parser_type = TextServer::STRUCTURED_TEXT_URI; + } else if (bidi_override_option->value == "f" || bidi_override_option->value == "file") { + st_parser_type = TextServer::STRUCTURED_TEXT_FILE; + } else if (bidi_override_option->value == "e" || bidi_override_option->value == "email") { + st_parser_type = TextServer::STRUCTURED_TEXT_EMAIL; + } else if (bidi_override_option->value == "l" || bidi_override_option->value == "list") { + st_parser_type = TextServer::STRUCTURED_TEXT_LIST; + } else if (bidi_override_option->value == "n" || bidi_override_option->value == "gdscript") { + st_parser_type = TextServer::STRUCTURED_TEXT_GDSCRIPT; + } else if (bidi_override_option->value == "c" || bidi_override_option->value == "custom") { + st_parser_type = TextServer::STRUCTURED_TEXT_CUSTOM; + } + } + push_paragraph(alignment, dir, lang, st_parser_type, jst_flags, tab_stops); pos = brk_end + 1; tag_stack.push_front("p"); } else if (tag == "url") { - int end = p_bbcode.find("[", brk_end); + int end = p_bbcode.find_char('[', brk_end); if (end == -1) { end = p_bbcode.length(); } @@ -4607,19 +4683,16 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front(tag); } else if (tag.begins_with("url=")) { - String url = tag.substr(4, tag.length()).unquote(); + String url = _get_tag_value(tag).unquote(); push_meta(url, META_UNDERLINE_ALWAYS); pos = brk_end + 1; tag_stack.push_front("url"); } else if (tag.begins_with("hint=")) { - String description = tag.substr(5, tag.length()).unquote(); + String description = _get_tag_value(tag).unquote(); push_hint(description); pos = brk_end + 1; tag_stack.push_front("hint"); } else if (tag.begins_with("dropcap")) { - Vector<String> subtag = tag.substr(5, tag.length()).split(" "); - _normalize_subtags(subtag); - int fs = theme_cache.normal_font_size * 3; Ref<Font> f = theme_cache.normal_font; Color color = theme_cache.default_color; @@ -4627,39 +4700,47 @@ void RichTextLabel::append_text(const String &p_bbcode) { int outline_size = theme_cache.outline_size; Rect2 dropcap_margins; - for (int i = 0; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("="); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "font" || subtag_a[0] == "f") { - const String &fnt = subtag_a[1]; - Ref<Font> font = ResourceLoader::load(fnt, "Font"); - if (font.is_valid()) { - f = font; - } - } else if (subtag_a[0] == "font_size") { - fs = subtag_a[1].to_int(); - } else if (subtag_a[0] == "margins") { - Vector<String> subtag_b = subtag_a[1].split(","); - _normalize_subtags(subtag_b); + OptionMap::Iterator font_option = bbcode_options.find("font"); + if (!font_option) { + font_option = bbcode_options.find("f"); + } + if (font_option) { + const String &fnt = font_option->value; + Ref<Font> font = ResourceLoader::load(fnt, "Font"); + if (font.is_valid()) { + f = font; + } + } + OptionMap::Iterator font_size_option = bbcode_options.find("font_size"); + if (font_size_option) { + fs = font_size_option->value.to_int(); + } + OptionMap::Iterator margins_option = bbcode_options.find("margins"); + if (margins_option) { + Vector<String> subtag_b = _split_unquoted(margins_option->value, U','); + _normalize_subtags(subtag_b); - if (subtag_b.size() == 4) { - dropcap_margins.position.x = subtag_b[0].to_float(); - dropcap_margins.position.y = subtag_b[1].to_float(); - dropcap_margins.size.x = subtag_b[2].to_float(); - dropcap_margins.size.y = subtag_b[3].to_float(); - } - } else if (subtag_a[0] == "outline_size") { - outline_size = subtag_a[1].to_int(); - } else if (subtag_a[0] == "color") { - color = Color::from_string(subtag_a[1], color); - } else if (subtag_a[0] == "outline_color") { - outline_color = Color::from_string(subtag_a[1], outline_color); - } + if (subtag_b.size() == 4) { + dropcap_margins.position.x = subtag_b[0].to_float(); + dropcap_margins.position.y = subtag_b[1].to_float(); + dropcap_margins.size.x = subtag_b[2].to_float(); + dropcap_margins.size.y = subtag_b[3].to_float(); } } - int end = p_bbcode.find("[", brk_end); + OptionMap::Iterator outline_size_option = bbcode_options.find("outline_size"); + if (outline_size_option) { + outline_size = outline_size_option->value.to_int(); + } + OptionMap::Iterator color_option = bbcode_options.find("color"); + if (color_option) { + color = Color::from_string(color_option->value, color); + } + OptionMap::Iterator outline_color_option = bbcode_options.find("outline_color"); + if (outline_color_option) { + outline_color = Color::from_string(outline_color_option->value, outline_color); + } + + int end = p_bbcode.find_char('[', brk_end); if (end == -1) { end = p_bbcode.length(); } @@ -4673,7 +4754,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } else if (tag.begins_with("img")) { int alignment = INLINE_ALIGNMENT_CENTER; if (tag.begins_with("img=")) { - Vector<String> subtag = tag.substr(4, tag.length()).split(","); + Vector<String> subtag = _split_unquoted(_get_tag_value(tag), U','); _normalize_subtags(subtag); if (subtag.size() > 1) { @@ -4704,7 +4785,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { } } - int end = p_bbcode.find("[", brk_end); + int end = p_bbcode.find_char('[', brk_end); if (end == -1) { end = p_bbcode.length(); } @@ -4716,7 +4797,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { Rect2 region; OptionMap::Iterator region_option = bbcode_options.find("region"); if (region_option) { - Vector<String> region_values = region_option->value.split(",", false); + Vector<String> region_values = _split_unquoted(region_option->value, U','); if (region_values.size() == 4) { region.position.x = region_values[0].to_float(); region.position.y = region_values[1].to_float(); @@ -4778,27 +4859,27 @@ void RichTextLabel::append_text(const String &p_bbcode) { pos = end; tag_stack.push_front(bbcode_name); } else if (tag.begins_with("color=")) { - String color_str = tag.substr(6, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_color(color); pos = brk_end + 1; tag_stack.push_front("color"); } else if (tag.begins_with("outline_color=")) { - String color_str = tag.substr(14, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_outline_color(color); pos = brk_end + 1; tag_stack.push_front("outline_color"); } else if (tag.begins_with("font_size=")) { - int fnt_size = tag.substr(10, tag.length()).to_int(); + int fnt_size = _get_tag_value(tag).to_int(); push_font_size(fnt_size); pos = brk_end + 1; tag_stack.push_front("font_size"); } else if (tag.begins_with("opentype_features=") || tag.begins_with("otf=")) { - int value_pos = tag.find("="); + int value_pos = tag.find_char('='); String fnt_ftr = tag.substr(value_pos + 1); Vector<String> subtag = fnt_ftr.split(","); _normalize_subtags(subtag); @@ -4842,7 +4923,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front(tag.substr(0, value_pos)); } else if (tag.begins_with("font=")) { - String fnt = tag.substr(5, tag.length()).unquote(); + String fnt = _get_tag_value(tag).unquote(); Ref<Font> fc = ResourceLoader::load(fnt, "Font"); if (fc.is_valid()) { @@ -4853,11 +4934,9 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("font"); } else if (tag.begins_with("font ")) { - Vector<String> subtag = tag.substr(2, tag.length()).split(" "); - _normalize_subtags(subtag); - Ref<Font> font = theme_cache.normal_font; DefaultFont def_font = NORMAL_FONT; + int fnt_size = -1; ItemFont *font_it = _find_font(current); if (font_it) { @@ -4870,75 +4949,122 @@ void RichTextLabel::append_text(const String &p_bbcode) { Ref<FontVariation> fc; fc.instantiate(); - int fnt_size = -1; - for (int i = 1; i < subtag.size(); i++) { - Vector<String> subtag_a = subtag[i].split("=", true, 1); - _normalize_subtags(subtag_a); - - if (subtag_a.size() == 2) { - if (subtag_a[0] == "name" || subtag_a[0] == "n") { - const String &fnt = subtag_a[1]; - Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); - if (font_data.is_valid()) { - font = font_data; - def_font = CUSTOM_FONT; - } - } else if (subtag_a[0] == "size" || subtag_a[0] == "s") { - fnt_size = subtag_a[1].to_int(); - } else if (subtag_a[0] == "glyph_spacing" || subtag_a[0] == "gl") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_GLYPH, spacing); - } else if (subtag_a[0] == "space_spacing" || subtag_a[0] == "sp") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_SPACE, spacing); - } else if (subtag_a[0] == "top_spacing" || subtag_a[0] == "top") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_TOP, spacing); - } else if (subtag_a[0] == "bottom_spacing" || subtag_a[0] == "bt") { - int spacing = subtag_a[1].to_int(); - fc->set_spacing(TextServer::SPACING_BOTTOM, spacing); - } else if (subtag_a[0] == "embolden" || subtag_a[0] == "emb") { - float emb = subtag_a[1].to_float(); - fc->set_variation_embolden(emb); - } else if (subtag_a[0] == "face_index" || subtag_a[0] == "fi") { - int fi = subtag_a[1].to_int(); - fc->set_variation_face_index(fi); - } else if (subtag_a[0] == "slant" || subtag_a[0] == "sln") { - float slant = subtag_a[1].to_float(); - fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)); - } else if (subtag_a[0] == "opentype_variation" || subtag_a[0] == "otv") { - Dictionary variations; - if (!subtag_a[1].is_empty()) { - Vector<String> variation_tags = subtag_a[1].split(","); - for (int j = 0; j < variation_tags.size(); j++) { - Vector<String> subtag_b = variation_tags[j].split("="); - _normalize_subtags(subtag_b); - - if (subtag_b.size() == 2) { - variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); - } - } - fc->set_variation_opentype(variations); + OptionMap::Iterator name_option = bbcode_options.find("name"); + if (!name_option) { + name_option = bbcode_options.find("n"); + } + if (name_option) { + const String &fnt = name_option->value; + Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); + if (font_data.is_valid()) { + font = font_data; + def_font = CUSTOM_FONT; + } + } + OptionMap::Iterator size_option = bbcode_options.find("size"); + if (!size_option) { + size_option = bbcode_options.find("s"); + } + if (size_option) { + fnt_size = size_option->value.to_int(); + } + OptionMap::Iterator glyph_spacing_option = bbcode_options.find("glyph_spacing"); + if (!glyph_spacing_option) { + glyph_spacing_option = bbcode_options.find("gl"); + } + if (glyph_spacing_option) { + int spacing = glyph_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_GLYPH, spacing); + } + OptionMap::Iterator space_spacing_option = bbcode_options.find("space_spacing"); + if (!space_spacing_option) { + space_spacing_option = bbcode_options.find("sp"); + } + if (space_spacing_option) { + int spacing = space_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_SPACE, spacing); + } + OptionMap::Iterator top_spacing_option = bbcode_options.find("top_spacing"); + if (!top_spacing_option) { + top_spacing_option = bbcode_options.find("top"); + } + if (top_spacing_option) { + int spacing = top_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_TOP, spacing); + } + OptionMap::Iterator bottom_spacing_option = bbcode_options.find("bottom_spacing"); + if (!bottom_spacing_option) { + bottom_spacing_option = bbcode_options.find("bt"); + } + if (bottom_spacing_option) { + int spacing = bottom_spacing_option->value.to_int(); + fc->set_spacing(TextServer::SPACING_BOTTOM, spacing); + } + OptionMap::Iterator embolden_option = bbcode_options.find("embolden"); + if (!embolden_option) { + embolden_option = bbcode_options.find("emb"); + } + if (embolden_option) { + float emb = embolden_option->value.to_float(); + fc->set_variation_embolden(emb); + } + OptionMap::Iterator face_index_option = bbcode_options.find("face_index"); + if (!face_index_option) { + face_index_option = bbcode_options.find("fi"); + } + if (face_index_option) { + int fi = face_index_option->value.to_int(); + fc->set_variation_face_index(fi); + } + OptionMap::Iterator slant_option = bbcode_options.find("slant"); + if (!slant_option) { + slant_option = bbcode_options.find("sln"); + } + if (slant_option) { + float slant = slant_option->value.to_float(); + fc->set_variation_transform(Transform2D(1.0, slant, 0.0, 1.0, 0.0, 0.0)); + } + OptionMap::Iterator opentype_variation_option = bbcode_options.find("opentype_variation"); + if (!opentype_variation_option) { + opentype_variation_option = bbcode_options.find("otv"); + } + if (opentype_variation_option) { + Dictionary variations; + if (!opentype_variation_option->value.is_empty()) { + Vector<String> variation_tags = opentype_variation_option->value.split(","); + for (int j = 0; j < variation_tags.size(); j++) { + Vector<String> subtag_b = variation_tags[j].split("="); + _normalize_subtags(subtag_b); + + if (subtag_b.size() == 2) { + variations[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); } - } else if (subtag_a[0] == "opentype_features" || subtag_a[0] == "otf") { - Dictionary features; - if (!subtag_a[1].is_empty()) { - Vector<String> feature_tags = subtag_a[1].split(","); - for (int j = 0; j < feature_tags.size(); j++) { - Vector<String> subtag_b = feature_tags[j].split("="); - _normalize_subtags(subtag_b); - - if (subtag_b.size() == 2) { - features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); - } else if (subtag_b.size() == 1) { - features[TS->name_to_tag(subtag_b[0])] = 1; - } - } - fc->set_opentype_features(features); + } + fc->set_variation_opentype(variations); + } + } + OptionMap::Iterator opentype_features_option = bbcode_options.find("opentype_features"); + if (!opentype_features_option) { + opentype_features_option = bbcode_options.find("otf"); + } + if (opentype_features_option) { + Dictionary features; + if (!opentype_features_option->value.is_empty()) { + Vector<String> feature_tags = opentype_features_option->value.split(","); + for (int j = 0; j < feature_tags.size(); j++) { + Vector<String> subtag_b = feature_tags[j].split("="); + _normalize_subtags(subtag_b); + + if (subtag_b.size() == 2) { + features[TS->name_to_tag(subtag_b[0])] = subtag_b[1].to_float(); + } else if (subtag_b.size() == 1) { + features[TS->name_to_tag(subtag_b[0])] = 1; } } + fc->set_opentype_features(features); } } + fc->set_base_font(font); if (def_font != CUSTOM_FONT) { @@ -4951,7 +5077,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("font"); } else if (tag.begins_with("outline_size=")) { - int fnt_size = tag.substr(13, tag.length()).to_int(); + int fnt_size = _get_tag_value(tag).to_int(); if (fnt_size > 0) { push_outline_size(fnt_size); } @@ -5090,7 +5216,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("pulse"); set_process_internal(true); } else if (tag.begins_with("bgcolor=")) { - String color_str = tag.substr(8, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_bgcolor(color); @@ -5098,7 +5224,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { tag_stack.push_front("bgcolor"); } else if (tag.begins_with("fgcolor=")) { - String color_str = tag.substr(8, tag.length()).unquote(); + String color_str = _get_tag_value(tag).unquote(); Color color = Color::from_string(color_str, theme_cache.default_color); push_fgcolor(color); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index 6da13e7b2d..a01da02b27 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -614,6 +614,10 @@ private: String _get_prefix(Item *p_item, const Vector<int> &p_list_index, const Vector<ItemList *> &p_list_items); + static int _find_unquoted(const String &p_src, char32_t p_chr, int p_from); + static Vector<String> _split_unquoted(const String &p_src, char32_t p_splitter); + static String _get_tag_value(const String &p_tag); + #ifndef DISABLE_DEPRECATED // Kept for compatibility from 3.x to 4.0. bool _set(const StringName &p_name, const Variant &p_value); diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 4212cd709f..ac81f0de56 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -287,7 +287,12 @@ inline void SpinBox::_compute_sizes() { int buttons_block_wanted_width = theme_cache.buttons_width + theme_cache.field_and_buttons_separation; int buttons_block_icon_enforced_width = _get_widest_button_icon_width() + theme_cache.field_and_buttons_separation; - int w = theme_cache.set_min_buttons_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width; +#ifndef DISABLE_DEPRECATED + const bool min_width_from_icons = theme_cache.set_min_buttons_width_from_icons || (theme_cache.buttons_width < 0); +#else + const bool min_width_from_icons = theme_cache.buttons_width < 0; +#endif + int w = min_width_from_icons != 0 ? MAX(buttons_block_icon_enforced_width, buttons_block_wanted_width) : buttons_block_wanted_width; if (w != sizing_cache.buttons_block_width) { line_edit->set_offset(SIDE_LEFT, 0); @@ -551,7 +556,9 @@ void SpinBox::_bind_methods() { BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_vertical_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, field_and_buttons_separation); BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, buttons_width); +#ifndef DISABLE_DEPRECATED BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, SpinBox, set_min_buttons_width_from_icons); +#endif BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, updown_icon, "updown"); BIND_THEME_ITEM_CUSTOM(Theme::DATA_TYPE_ICON, SpinBox, up_icon, "up"); diff --git a/scene/gui/spin_box.h b/scene/gui/spin_box.h index 7c6974f6a8..592805f43a 100644 --- a/scene/gui/spin_box.h +++ b/scene/gui/spin_box.h @@ -127,8 +127,9 @@ class SpinBox : public Range { int buttons_vertical_separation = 0; int field_and_buttons_separation = 0; int buttons_width = 0; - int set_min_buttons_width_from_icons = 0; - +#ifndef DISABLE_DEPRECATED + bool set_min_buttons_width_from_icons = false; +#endif } theme_cache; void _mouse_exited(); diff --git a/scene/gui/subviewport_container.cpp b/scene/gui/subviewport_container.cpp index c715aceb0b..a443ae9abf 100644 --- a/scene/gui/subviewport_container.cpp +++ b/scene/gui/subviewport_container.cpp @@ -259,7 +259,7 @@ void SubViewportContainer::remove_child_notify(Node *p_child) { } PackedStringArray SubViewportContainer::get_configuration_warnings() const { - PackedStringArray warnings = Node::get_configuration_warnings(); + PackedStringArray warnings = Container::get_configuration_warnings(); bool has_viewport = false; for (int i = 0; i < get_child_count(); i++) { diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index b6835541bf..ab0ad2f4b7 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -112,8 +112,34 @@ int TextEdit::Text::get_line_width(int p_line, int p_wrap_index) const { return text[p_line].data_buf->get_size().x; } +int TextEdit::Text::get_max_width() const { + if (max_line_width_dirty) { + int new_max_line_width = 0; + for (const Line &l : text) { + if (l.hidden) { + continue; + } + new_max_line_width = MAX(new_max_line_width, l.width); + } + max_line_width = new_max_line_width; + } + + return max_line_width; +} + int TextEdit::Text::get_line_height() const { - return line_height; + if (max_line_height_dirty) { + int new_max_line_height = 0; + for (const Line &l : text) { + if (l.hidden) { + continue; + } + new_max_line_height = MAX(new_max_line_height, l.height); + } + max_line_height = new_max_line_height; + } + + return max_line_height; } void TextEdit::Text::set_width(float p_width) { @@ -135,15 +161,17 @@ BitField<TextServer::LineBreakFlag> TextEdit::Text::get_brk_flags() const { int TextEdit::Text::get_line_wrap_amount(int p_line) const { ERR_FAIL_INDEX_V(p_line, text.size(), 0); - return text[p_line].data_buf->get_line_count() - 1; + return text[p_line].line_count - 1; } Vector<Vector2i> TextEdit::Text::get_line_wrap_ranges(int p_line) const { Vector<Vector2i> ret; ERR_FAIL_INDEX_V(p_line, text.size(), ret); - for (int i = 0; i < text[p_line].data_buf->get_line_count(); i++) { - ret.push_back(text[p_line].data_buf->get_line_range(i)); + Ref<TextParagraph> data_buf = text[p_line].data_buf; + int line_count = data_buf->get_line_count(); + for (int i = 0; i < line_count; i++) { + ret.push_back(data_buf->get_line_range(i)); } return ret; } @@ -153,40 +181,11 @@ const Ref<TextParagraph> TextEdit::Text::get_line_data(int p_line) const { return text[p_line].data_buf; } -_FORCE_INLINE_ const String &TextEdit::Text::operator[](int p_line) const { +_FORCE_INLINE_ String TextEdit::Text::operator[](int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), ""); return text[p_line].data; } -void TextEdit::Text::_calculate_line_height() { - int height = 0; - for (const Line &l : text) { - // Found another line with the same height...nothing to update. - if (l.height == line_height) { - height = line_height; - break; - } - height = MAX(height, l.height); - } - line_height = height; -} - -void TextEdit::Text::_calculate_max_line_width() { - int line_width = 0; - for (const Line &l : text) { - if (l.hidden) { - continue; - } - - // Found another line with the same width...nothing to update. - if (l.width == max_width) { - line_width = max_width; - break; - } - line_width = MAX(line_width, l.width); - } - max_width = line_width; -} - void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_changed, const String &p_ime_text, const Array &p_bidi_override) { ERR_FAIL_INDEX(p_line, text.size()); @@ -194,8 +193,9 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan return; // Not in tree? } + Line &text_line = text.write[p_line]; if (p_text_changed) { - text.write[p_line].data_buf->clear(); + text_line.data_buf->clear(); } BitField<TextServer::LineBreakFlag> flags = brk_flags; @@ -203,30 +203,30 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan flags.set_flag(TextServer::BREAK_TRIM_INDENT); } - text.write[p_line].data_buf->set_width(width); - text.write[p_line].data_buf->set_direction((TextServer::Direction)direction); - text.write[p_line].data_buf->set_break_flags(flags); - text.write[p_line].data_buf->set_preserve_control(draw_control_chars); - text.write[p_line].data_buf->set_custom_punctuation(get_enabled_word_separators()); + text_line.data_buf->set_width(width); + text_line.data_buf->set_direction((TextServer::Direction)direction); + text_line.data_buf->set_break_flags(flags); + text_line.data_buf->set_preserve_control(draw_control_chars); + text_line.data_buf->set_custom_punctuation(get_enabled_word_separators()); if (p_ime_text.length() > 0) { if (p_text_changed) { - text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language); + text_line.data_buf->add_string(p_ime_text, font, font_size, language); } if (!p_bidi_override.is_empty()) { - TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), p_bidi_override); + TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), p_bidi_override); } } else { if (p_text_changed) { - text.write[p_line].data_buf->add_string(text[p_line].data, font, font_size, language); + text_line.data_buf->add_string(text_line.data, font, font_size, language); } - if (!text[p_line].bidi_override.is_empty()) { - TS->shaped_text_set_bidi_override(text.write[p_line].data_buf->get_rid(), text[p_line].bidi_override); + if (!text_line.bidi_override.is_empty()) { + TS->shaped_text_set_bidi_override(text_line.data_buf->get_rid(), text_line.bidi_override); } } if (!p_text_changed) { - RID r = text.write[p_line].data_buf->get_rid(); + RID r = text_line.data_buf->get_rid(); int spans = TS->shaped_get_span_count(r); for (int i = 0; i < spans; i++) { TS->shaped_set_span_update_font(r, i, font->get_rids(), font_size, font->get_opentype_features()); @@ -237,61 +237,58 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan if (tab_size > 0) { Vector<float> tabs; tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); - text.write[p_line].data_buf->tab_align(tabs); + text_line.data_buf->tab_align(tabs); + } + + // Update wrap amount. + const int old_line_count = text_line.line_count; + text_line.line_count = text_line.data_buf->get_line_count(); + if (!text_line.hidden && text_line.line_count != old_line_count) { + total_visible_line_count += text_line.line_count - old_line_count; } // Update height. - const int old_height = text.write[p_line].height; - const int wrap_amount = get_line_wrap_amount(p_line); - int height = font_height; - for (int i = 0; i <= wrap_amount; i++) { - height = MAX(height, text[p_line].data_buf->get_line_size(i).y); + const int old_height = text_line.height; + text_line.height = font_height; + for (int i = 0; i < text_line.line_count; i++) { + text_line.height = MAX(text_line.height, text_line.data_buf->get_line_size(i).y); } - text.write[p_line].height = height; - // If this line has shrunk, this may no longer the tallest line. - if (old_height == line_height && height < line_height) { - _calculate_line_height(); - } else { - line_height = MAX(height, line_height); + // If this line has shrunk, this may no longer be the tallest line. + if (!text_line.hidden) { + if (old_height == max_line_height && text_line.height < old_height) { + max_line_height_dirty = true; + } else { + max_line_height = MAX(text_line.height, max_line_height); + } } // Update width. - const int old_width = text.write[p_line].width; - int line_width = get_line_width(p_line); - text.write[p_line].width = line_width; + const int old_width = text_line.width; + text_line.width = get_line_width(p_line); - // If this line has shrunk, this may no longer the longest line. - if (old_width == max_width && line_width < max_width) { - _calculate_max_line_width(); - } else if (!is_hidden(p_line)) { - max_width = MAX(line_width, max_width); + if (!text_line.hidden) { + // If this line has shrunk, this may no longer be the longest line. + if (old_width == max_line_width && text_line.width < old_width) { + max_line_width_dirty = true; + } else { + max_line_width = MAX(text_line.width, max_line_width); + } } } void TextEdit::Text::invalidate_all_lines() { for (int i = 0; i < text.size(); i++) { - BitField<TextServer::LineBreakFlag> flags = brk_flags; - if (indent_wrapped_lines) { - flags.set_flag(TextServer::BREAK_TRIM_INDENT); - } - text.write[i].data_buf->set_width(width); - text.write[i].data_buf->set_break_flags(flags); - text.write[i].data_buf->set_custom_punctuation(get_enabled_word_separators()); - if (tab_size_dirty) { if (tab_size > 0) { Vector<float> tabs; tabs.push_back(font->get_char_size(' ', font_size).width * tab_size); - text.write[i].data_buf->tab_align(tabs); + text[i].data_buf->tab_align(tabs); } } - text.write[i].width = get_line_width(i); + invalidate_cache(i, -1, false); } tab_size_dirty = false; - - max_width = -1; - _calculate_max_line_width(); } void TextEdit::Text::invalidate_font() { @@ -299,8 +296,8 @@ void TextEdit::Text::invalidate_font() { return; } - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); @@ -317,8 +314,8 @@ void TextEdit::Text::invalidate_all() { return; } - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; if (font.is_valid() && font_size > 0) { font_height = font->get_height(font_size); @@ -333,8 +330,8 @@ void TextEdit::Text::invalidate_all() { void TextEdit::Text::clear() { text.clear(); - max_width = -1; - line_height = -1; + max_line_width_dirty = true; + max_line_height_dirty = true; Line line; line.gutters.resize(gutter_count); @@ -343,8 +340,8 @@ void TextEdit::Text::clear() { invalidate_cache(0, -1, true); } -int TextEdit::Text::get_max_width() const { - return max_width; +int TextEdit::Text::get_total_visible_line_count() const { + return total_visible_line_count; } void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_override) { @@ -355,7 +352,37 @@ void TextEdit::Text::set(int p_line, const String &p_text, const Array &p_bidi_o invalidate_cache(p_line, -1, true); } +void TextEdit::Text::set_hidden(int p_line, bool p_hidden) { + ERR_FAIL_INDEX(p_line, text.size()); + + Line &text_line = text.write[p_line]; + if (text_line.hidden == p_hidden) { + return; + } + text_line.hidden = p_hidden; + if (p_hidden) { + total_visible_line_count -= text_line.line_count; + if (text_line.width == max_line_width) { + max_line_width_dirty = true; + } + if (text_line.height == max_line_height) { + max_line_height_dirty = true; + } + } else { + total_visible_line_count += text_line.line_count; + max_line_width = MAX(text_line.width, max_line_width); + max_line_height = MAX(text_line.height, max_line_height); + } +} + +bool TextEdit::Text::is_hidden(int p_line) const { + ERR_FAIL_INDEX_V(p_line, text.size(), true); + return text[p_line].hidden; +} + void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override) { + ERR_FAIL_INDEX(p_at, text.size() + 1); + int new_line_count = p_text.size() - 1; if (new_line_count > 0) { text.resize(text.size() + new_line_count); @@ -382,24 +409,25 @@ void TextEdit::Text::insert(int p_at, const Vector<String> &p_text, const Vector } void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { + p_from_line = MAX(p_from_line, 0); + ERR_FAIL_INDEX(p_from_line, text.size()); + + p_to_line = MIN(p_to_line, text.size()); + ERR_FAIL_COND(p_to_line < p_from_line); + if (p_from_line == p_to_line) { return; } - bool dirty_height = false; - bool dirty_width = false; for (int i = p_from_line; i < p_to_line; i++) { - if (!dirty_height && text[i].height == line_height) { - dirty_height = true; + const Line &text_line = text[i]; + if (text_line.height == max_line_height) { + max_line_height_dirty = true; } - - if (!dirty_width && text[i].width == max_width) { - dirty_width = true; - } - - if (dirty_height && dirty_width) { - break; + if (text_line.width == max_line_width) { + max_line_width_dirty = true; } + total_visible_line_count -= text_line.line_count; } int diff = (p_to_line - p_from_line); @@ -407,16 +435,6 @@ void TextEdit::Text::remove_range(int p_from_line, int p_to_line) { text.write[(i - diff) + 1] = text[i + 1]; } text.resize(text.size() - diff); - - if (dirty_height) { - line_height = -1; - _calculate_line_height(); - } - - if (dirty_width) { - max_width = -1; - _calculate_max_line_width(); - } } void TextEdit::Text::add_gutter(int p_at) { @@ -431,6 +449,8 @@ void TextEdit::Text::add_gutter(int p_at) { } void TextEdit::Text::remove_gutter(int p_gutter) { + ERR_FAIL_INDEX(p_gutter, text.size()); + for (int i = 0; i < text.size(); i++) { text.write[i].gutters.remove_at(p_gutter); } @@ -438,6 +458,9 @@ void TextEdit::Text::remove_gutter(int p_gutter) { } void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) { + ERR_FAIL_INDEX(p_from_line, text.size()); + ERR_FAIL_INDEX(p_to_line, text.size()); + text.write[p_to_line].gutters = text[p_from_line].gutters; text.write[p_from_line].gutters.clear(); text.write[p_from_line].gutters.resize(gutter_count); @@ -625,6 +648,8 @@ void TextEdit::_notification(int p_what) { brace_matching.resize(get_caret_count()); for (int caret = 0; caret < get_caret_count(); caret++) { + BraceMatchingData &brace_match = brace_matching.write[caret]; + if (get_caret_line(caret) < 0 || get_caret_line(caret) >= text.size() || get_caret_column(caret) < 0) { continue; } @@ -678,20 +703,20 @@ void TextEdit::_notification(int p_what) { } if (stack == 0) { - brace_matching.write[caret].open_match_line = i; - brace_matching.write[caret].open_match_column = j; - brace_matching.write[caret].open_matching = true; + brace_match.open_match_line = i; + brace_match.open_match_column = j; + brace_match.open_matching = true; break; } } - if (brace_matching.write[caret].open_match_line != -1) { + if (brace_match.open_match_line != -1) { break; } } - if (!brace_matching.write[caret].open_matching) { - brace_matching.write[caret].open_mismatch = true; + if (!brace_match.open_matching) { + brace_match.open_mismatch = true; } } } @@ -744,20 +769,20 @@ void TextEdit::_notification(int p_what) { } if (stack == 0) { - brace_matching.write[caret].close_match_line = i; - brace_matching.write[caret].close_match_column = j; - brace_matching.write[caret].close_matching = true; + brace_match.close_match_line = i; + brace_match.close_match_column = j; + brace_match.close_matching = true; break; } } - if (brace_matching.write[caret].close_match_line != -1) { + if (brace_match.close_match_line != -1) { break; } } - if (!brace_matching.write[caret].close_matching) { - brace_matching.write[caret].close_mismatch = true; + if (!brace_match.close_matching) { + brace_match.close_mismatch = true; } } } @@ -772,13 +797,14 @@ void TextEdit::_notification(int p_what) { // Check if highlighted words contain only whitespaces (tabs or spaces). bool only_whitespaces_highlighted = highlighted_text.strip_edges().is_empty(); - HashMap<int, HashSet<int>> caret_line_wrap_index_map; + Vector<Pair<int, int>> highlighted_lines; + highlighted_lines.resize(carets.size()); Vector<int> carets_wrap_index; carets_wrap_index.resize(carets.size()); for (int i = 0; i < carets.size(); i++) { carets.write[i].visible = false; int wrap_index = get_caret_wrap_index(i); - caret_line_wrap_index_map[get_caret_line(i)].insert(wrap_index); + highlighted_lines.write[i] = Pair<int, int>(get_caret_line(i), wrap_index); carets_wrap_index.write[i] = wrap_index; } @@ -842,7 +868,7 @@ void TextEdit::_notification(int p_what) { break; } - Dictionary color_map = _get_line_syntax_highlighting(minimap_line); + const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(minimap_line); Color line_background_color = text.get_line_background_color(minimap_line); @@ -853,12 +879,9 @@ void TextEdit::_notification(int p_what) { line_background_color.a *= 0.6; } - Color current_color = theme_cache.font_color; - if (!editable) { - current_color = theme_cache.font_readonly_color; - } + Color current_color = editable ? theme_cache.font_color : theme_cache.font_readonly_color; - Vector<String> wrap_rows = get_line_wrapped_text(minimap_line); + const Vector<String> wrap_rows = get_line_wrapped_text(minimap_line); int line_wrap_amount = get_line_wrap_count(minimap_line); int last_wrap_column = 0; @@ -881,13 +904,13 @@ void TextEdit::_notification(int p_what) { last_wrap_column += wrap_rows[line_wrap_index - 1].length(); } - if (caret_line_wrap_index_map.has(minimap_line) && caret_line_wrap_index_map[minimap_line].has(line_wrap_index) && highlight_current_line) { + if (highlight_current_line && highlighted_lines.has(Pair<int, int>(minimap_line, line_wrap_index))) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), theme_cache.current_line_color); } else { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2((xmargin_end + 2), i * 3, minimap_width, 2), theme_cache.current_line_color); } - } else if (line_background_color != Color(0, 0, 0, 0)) { + } else if (line_background_color.a > 0) { if (rtl) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - (xmargin_end + 2) - minimap_width, i * 3, minimap_width, 2), line_background_color); } else { @@ -905,13 +928,17 @@ void TextEdit::_notification(int p_what) { // Get the number of characters to draw together. for (characters = 0; j + characters < str.length(); characters++) { int next_char_index = j + characters; - const Variant *color_data = color_map.getptr(last_wrap_column + next_char_index); - if (color_data != nullptr) { - next_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color); - if (!editable) { - next_color.a = theme_cache.font_readonly_color.a; + + for (const Pair<int64_t, Color> &color_data : color_map) { + if (last_wrap_column + next_char_index >= color_data.first) { + next_color = color_data.second; + if (!editable) { + next_color.a = theme_cache.font_readonly_color.a; + } + next_color.a *= 0.6; + } else { + break; } - next_color.a *= 0.6; } if (characters == 0) { current_color = next_color; @@ -1002,7 +1029,7 @@ void TextEdit::_notification(int p_what) { LineDrawingCache cache_entry; - Dictionary color_map = _get_line_syntax_highlighting(line); + const Vector<Pair<int64_t, Color>> color_map = _get_line_syntax_highlighting(line); // Ensure we at least use the font color. Color current_color = !editable ? theme_cache.font_readonly_color : theme_cache.font_color; @@ -1012,7 +1039,7 @@ void TextEdit::_notification(int p_what) { const Ref<TextParagraph> ldata = draw_placeholder ? placeholder_data_buf : text.get_line_data(line); - Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line); + const Vector<String> wrap_rows = draw_placeholder ? placeholder_wraped_rows : get_line_wrapped_text(line); int line_wrap_amount = draw_placeholder ? placeholder_wraped_rows.size() - 1 : get_line_wrap_count(line); for (int line_wrap_index = 0; line_wrap_index <= line_wrap_amount; line_wrap_index++) { @@ -1053,20 +1080,20 @@ void TextEdit::_notification(int p_what) { break; } - if (text.get_line_background_color(line) != Color(0, 0, 0, 0)) { + if (text.get_line_background_color(line).a > 0.0) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, xmargin_end - xmargin_beg, row_height), text.get_line_background_color(line)); } } // Draw current line highlight. - if (highlight_current_line && caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index)) { + if (highlight_current_line && highlighted_lines.has(Pair<int, int>(line, line_wrap_index))) { if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - ofs_x - xmargin_end, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); + RS::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); } } @@ -1077,7 +1104,7 @@ void TextEdit::_notification(int p_what) { int gutter_offset = theme_cache.style_normal->get_margin(SIDE_LEFT); for (int g = 0; g < gutters.size(); g++) { - const GutterInfo gutter = gutters[g]; + const GutterInfo &gutter = gutters[g]; if (!gutter.draw || gutter.width <= 0) { continue; @@ -1184,7 +1211,7 @@ void TextEdit::_notification(int p_what) { if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.selection_color, true); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.selection_color); } } } @@ -1194,7 +1221,7 @@ void TextEdit::_notification(int p_what) { int search_text_col = _get_column_pos_of_word(search_text, str, search_flags, 0); int search_text_len = search_text.length(); while (search_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, search_text_col + start, search_text_col + search_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1206,7 +1233,7 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.search_result_color, true); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.search_result_color); draw_rect(rect, theme_cache.search_result_border_color, false); } @@ -1218,7 +1245,7 @@ void TextEdit::_notification(int p_what) { int highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); int highlighted_text_len = highlighted_text.length(); while (highlighted_text_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_text_col + start, highlighted_text_col + highlighted_text_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1230,7 +1257,7 @@ void TextEdit::_notification(int p_what) { } else if (rect.position.x + rect.size.x > xmargin_end) { rect.size.x = xmargin_end - rect.position.x; } - draw_rect(rect, theme_cache.word_highlighted_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, theme_cache.word_highlighted_color); } highlighted_text_col = _get_column_pos_of_word(highlighted_text, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, highlighted_text_col + highlighted_text_len); @@ -1243,7 +1270,7 @@ void TextEdit::_notification(int p_what) { int lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0); int lookup_symbol_word_len = lookup_symbol_word.length(); while (lookup_symbol_word_col != -1) { - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, lookup_symbol_word_col + start, lookup_symbol_word_col + lookup_symbol_word_len + start); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y + (theme_cache.line_spacing / 2), sel[j].y - sel[j].x, row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1257,7 +1284,7 @@ void TextEdit::_notification(int p_what) { } rect.position.y += ceil(TS->shaped_text_get_ascent(rid)) + ceil(theme_cache.font->get_underline_position(theme_cache.font_size)); rect.size.y = MAX(1, theme_cache.font->get_underline_thickness(theme_cache.font_size)); - draw_rect(rect, highlight_underline_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, highlight_underline_color); } lookup_symbol_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, lookup_symbol_word_col + lookup_symbol_word_len); @@ -1293,21 +1320,16 @@ void TextEdit::_notification(int p_what) { char_ofs = 0; } for (int j = 0; j < gl_size; j++) { - int64_t color_start = -1; - for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key)) { - if (int64_t(*key) <= glyphs[j].start) { - color_start = *key; + for (const Pair<int64_t, Color> &color_data : color_map) { + if (color_data.first <= glyphs[j].start) { + current_color = color_data.second; + if (!editable && current_color.a > theme_cache.font_readonly_color.a) { + current_color.a = theme_cache.font_readonly_color.a; + } } else { break; } } - const Variant *color_data = (color_start >= 0) ? color_map.getptr(color_start) : nullptr; - if (color_data != nullptr) { - current_color = (color_data->operator Dictionary()).get("color", theme_cache.font_color); - if (!editable && current_color.a > theme_cache.font_readonly_color.a) { - current_color.a = theme_cache.font_readonly_color.a; - } - } Color gl_color = current_color; for (int c = 0; c < get_caret_count(); c++) { @@ -1325,22 +1347,23 @@ void TextEdit::_notification(int p_what) { if (char_pos >= xmargin_beg) { if (highlight_matching_braces_enabled) { for (int c = 0; c < get_caret_count(); c++) { - if ((brace_matching[c].open_match_line == line && brace_matching[c].open_match_column == glyphs[j].start) || - (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].open_matching || brace_matching[c].open_mismatch))) { - if (brace_matching[c].open_mismatch) { + const BraceMatchingData &brace_match = brace_matching[c]; + if ((brace_match.open_match_line == line && brace_match.open_match_column == glyphs[j].start) || + (get_caret_column(c) == glyphs[j].start && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_match.open_matching || brace_match.open_mismatch))) { + if (brace_match.open_mismatch) { gl_color = _get_brace_mismatch_color(); } Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1)); - draw_rect(rect, gl_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_color); } - if ((brace_matching[c].close_match_line == line && brace_matching[c].close_match_column == glyphs[j].start) || - (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_matching[c].close_matching || brace_matching[c].close_mismatch))) { - if (brace_matching[c].close_mismatch) { + if ((brace_match.close_match_line == line && brace_match.close_match_column == glyphs[j].start) || + (get_caret_column(c) == glyphs[j].start + 1 && get_caret_line(c) == line && carets_wrap_index[c] == line_wrap_index && (brace_match.close_matching || brace_match.close_mismatch))) { + if (brace_match.close_mismatch) { gl_color = _get_brace_mismatch_color(); } Rect2 rect = Rect2(char_pos, ofs_y + theme_cache.font->get_underline_position(theme_cache.font_size), glyphs[j].advance * glyphs[j].repeat, MAX(theme_cache.font->get_underline_thickness(theme_cache.font_size) * theme_cache.base_scale, 1)); - draw_rect(rect, gl_color); + RS::get_singleton()->canvas_item_add_rect(ci, rect, gl_color); } } } @@ -1451,11 +1474,11 @@ void TextEdit::_notification(int p_what) { // Draw split caret (leading part). ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color); // Draw extra direction marker on top of split caret. float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } } else { // End of the line. if (gl_size > 0) { @@ -1488,28 +1511,28 @@ void TextEdit::_notification(int p_what) { // Draw extra marker on top of mid caret. Rect2 trect = Rect2(ts_caret.l_caret.position.x - 2.5 * caret_width, ts_caret.l_caret.position.y, 6 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } else if (ts_caret.l_caret != Rect2() && ts_caret.t_caret != Rect2() && ts_caret.l_dir != ts_caret.t_dir) { // Draw extra direction marker on top of split caret. float d = (ts_caret.l_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; Rect2 trect = Rect2(ts_caret.l_caret.position.x + d * caret_width, ts_caret.l_caret.position.y + ts_caret.l_caret.size.y - caret_width, 3 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); d = (ts_caret.t_dir == TextServer::DIRECTION_LTR) ? 0.5 : -3; trect = Rect2(ts_caret.t_caret.position.x + d * caret_width, ts_caret.t_caret.position.y, 3 * caret_width, caret_width); trect.position += Vector2(char_margin + ofs_x, ofs_y); - RenderingServer::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, trect, theme_cache.caret_color); } ts_caret.l_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.l_caret.size.x = caret_width; - draw_rect(ts_caret.l_caret, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.l_caret, theme_cache.caret_color); ts_caret.t_caret.position += Vector2(char_margin + ofs_x, ofs_y); ts_caret.t_caret.size.x = caret_width; - draw_rect(ts_caret.t_caret, theme_cache.caret_color); + RS::get_singleton()->canvas_item_add_rect(ci, ts_caret.t_caret, theme_cache.caret_color); } } } @@ -1517,7 +1540,7 @@ void TextEdit::_notification(int p_what) { if (!ime_text.is_empty()) { { // IME Intermediate text range. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length()); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c), get_caret_column(c) + ime_text.length()); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1536,7 +1559,7 @@ void TextEdit::_notification(int p_what) { } { // IME caret. - Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y); + const Vector<Vector2> sel = TS->shaped_text_get_selection(rid, get_caret_column(c) + ime_selection.x, get_caret_column(c) + ime_selection.x + ime_selection.y); for (int j = 0; j < sel.size(); j++) { Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, text_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { @@ -1575,26 +1598,7 @@ void TextEdit::_notification(int p_what) { draw_caret = true; } - _update_ime_window_position(); - - if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) { - int caret_start = -1; - int caret_end = -1; - - if (!has_selection(0)) { - String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column()); - - caret_start = full_text.length(); - } else { - String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column()); - String post_text = get_selected_text(0); - - caret_start = pre_text.length(); - caret_end = caret_start + post_text.length(); - } - - DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end); - } + _show_virtual_keyboard(); } break; case NOTIFICATION_FOCUS_EXIT: { @@ -1943,8 +1947,7 @@ void TextEdit::gui_input(const Ref<InputEvent> &p_gui_input) { } } - // Notify to show soft keyboard. - notification(NOTIFICATION_FOCUS_ENTER); + _show_virtual_keyboard(); } } @@ -2393,7 +2396,7 @@ void TextEdit::_move_caret_left(bool p_select, bool p_move_by_word) { } else { PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); if (words.is_empty() || cc <= words[0]) { - // This solves the scenario where there are no words but glyfs that can be ignored. + // Move to the start when there are no more words. cc = 0; } else { for (int j = words.size() - 2; j >= 0; j = j - 2) { @@ -2450,7 +2453,7 @@ void TextEdit::_move_caret_right(bool p_select, bool p_move_by_word) { } else { PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(i))->get_rid()); if (words.is_empty() || cc >= words[words.size() - 1]) { - // This solves the scenario where there are no words but glyfs that can be ignored. + // Move to the end when there are no more words. cc = text[get_caret_line(i)].length(); } else { for (int j = 1; j < words.size(); j = j + 2) { @@ -2666,7 +2669,7 @@ void TextEdit::_do_backspace(bool p_word, bool p_all_to_left) { // Get a list with the indices of the word bounds of the given text line. const PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(get_caret_line(caret_index))->get_rid()); if (words.is_empty() || column <= words[0]) { - // If "words" is empty, meaning no words are left, we can remove everything until the beginning of the line. + // Delete to the start when there are no more words. column = 0; } else { // Otherwise search for the first word break that is smaller than the index from we're currently deleting. @@ -2731,10 +2734,15 @@ void TextEdit::_delete(bool p_word, bool p_all_to_right) { int column = get_caret_column(caret_index); PackedInt32Array words = TS->shaped_text_get_word_breaks(text.get_line_data(line)->get_rid()); - for (int j = 1; j < words.size(); j = j + 2) { - if (words[j] > column) { - column = words[j]; - break; + if (words.is_empty() || column >= words[words.size() - 1]) { + // Delete to the end when there are no more words. + column = text[get_caret_line(i)].length(); + } else { + for (int j = 1; j < words.size(); j = j + 2) { + if (words[j] > column) { + column = words[j]; + break; + } } } @@ -2924,6 +2932,29 @@ void TextEdit::_update_ime_text() { queue_redraw(); } +void TextEdit::_show_virtual_keyboard() { + _update_ime_window_position(); + + if (virtual_keyboard_enabled && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) { + int caret_start = -1; + int caret_end = -1; + + if (!has_selection(0)) { + String full_text = _base_get_text(0, 0, get_caret_line(), get_caret_column()); + + caret_start = full_text.length(); + } else { + String pre_text = _base_get_text(0, 0, get_selection_from_line(), get_selection_from_column()); + String post_text = get_selected_text(0); + + caret_start = pre_text.length(); + caret_end = caret_start + post_text.length(); + } + + DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), DisplayServer::KEYBOARD_TYPE_MULTILINE, -1, caret_start, caret_end); + } +} + /* General overrides. */ Size2 TextEdit::get_minimum_size() const { Size2 size = theme_cache.style_normal->get_minimum_size(); @@ -2963,13 +2994,13 @@ bool TextEdit::can_drop_data(const Point2 &p_point, const Variant &p_data) const return drop_override; } - return is_editable() && p_data.get_type() == Variant::STRING; + return is_editable() && p_data.is_string(); } void TextEdit::drop_data(const Point2 &p_point, const Variant &p_data) { Control::drop_data(p_point, p_data); - if (p_data.get_type() == Variant::STRING && is_editable()) { + if (p_data.is_string() && is_editable()) { Point2i pos = get_line_column_at_pos(get_local_mouse_pos()); int drop_at_line = pos.y; int drop_at_column = pos.x; @@ -5883,7 +5914,7 @@ int TextEdit::get_visible_line_count_in_range(int p_from_line, int p_to_line) co } int TextEdit::get_total_visible_line_count() const { - return get_visible_line_count_in_range(0, text.size() - 1); + return text.get_total_visible_line_count(); } // Auto adjust. @@ -8157,8 +8188,36 @@ void TextEdit::_update_gutter_width() { } /* Syntax highlighting. */ -Dictionary TextEdit::_get_line_syntax_highlighting(int p_line) { - return (syntax_highlighter.is_null() || setting_text) ? Dictionary() : syntax_highlighter->get_line_syntax_highlighting(p_line); +Vector<Pair<int64_t, Color>> TextEdit::_get_line_syntax_highlighting(int p_line) { + if (syntax_highlighter.is_null() || setting_text) { + return Vector<Pair<int64_t, Color>>(); + } + + HashMap<int, Vector<Pair<int64_t, Color>>>::Iterator E = syntax_highlighting_cache.find(p_line); + if (E) { + return E->value; + } + + Dictionary color_map = syntax_highlighter->get_line_syntax_highlighting(p_line); + Vector<Pair<int64_t, Color>> result; + result.resize(color_map.size()); + int i = 0; + for (const Variant *key = color_map.next(nullptr); key; key = color_map.next(key), i++) { + int64_t key_data = *key; + const Variant *color_data = color_map.getptr(*key); + Color color_value = editable ? theme_cache.font_color : theme_cache.font_readonly_color; + if (color_data != nullptr) { + color_value = (color_data->operator Dictionary()).get("color", color_value); + } + result.write[i] = Pair<int64_t, Color>(key_data, color_value); + } + syntax_highlighting_cache.insert(p_line, result); + + return result; +} + +void TextEdit::_clear_syntax_highlighting_cache() { + syntax_highlighting_cache.clear(); } /* Deprecated. */ @@ -8184,6 +8243,7 @@ int TextEdit::get_selection_column(int p_caret) const { /*** Super internal Core API. Everything builds on it. ***/ void TextEdit::_text_changed() { + _clear_syntax_highlighting_cache(); _cancel_drag_and_drop_text(); queue_redraw(); diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index 1f2fd6619a..a448e185b1 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -153,6 +153,7 @@ private: Color background_color = Color(0, 0, 0, 0); bool hidden = false; + int line_count = 0; int height = 0; int width = 0; @@ -178,16 +179,19 @@ private: bool use_default_word_separators = true; bool use_custom_word_separators = false; - int line_height = -1; - int max_width = -1; + mutable bool max_line_width_dirty = true; + mutable bool max_line_height_dirty = true; + mutable int max_line_width = 0; + mutable int max_line_height = 0; + mutable int total_visible_line_count = 0; int width = -1; int tab_size = 4; int gutter_count = 0; bool indent_wrapped_lines = false; - void _calculate_line_height(); - void _calculate_max_line_width(); + void _calculate_line_height() const; + void _calculate_max_line_width() const; public: void set_tab_size(int p_tab_size); @@ -203,6 +207,7 @@ private: int get_line_height() const; int get_line_width(int p_line, int p_wrap_index = -1) const; int get_max_width() const; + int get_total_visible_line_count() const; void set_use_default_word_separators(bool p_enabled); bool is_default_word_separators_enabled() const; @@ -226,18 +231,8 @@ private: const Ref<TextParagraph> get_line_data(int p_line) const; void set(int p_line, const String &p_text, const Array &p_bidi_override); - void set_hidden(int p_line, bool p_hidden) { - if (text[p_line].hidden == p_hidden) { - return; - } - text.write[p_line].hidden = p_hidden; - if (!p_hidden && text[p_line].width > max_width) { - max_width = text[p_line].width; - } else if (p_hidden && text[p_line].width == max_width) { - _calculate_max_line_width(); - } - } - bool is_hidden(int p_line) const { return text[p_line].hidden; } + void set_hidden(int p_line, bool p_hidden); + bool is_hidden(int p_line) const; void insert(int p_at, const Vector<String> &p_text, const Vector<Array> &p_bidi_override); void remove_range(int p_from_line, int p_to_line); int size() const { return text.size(); } @@ -248,7 +243,7 @@ private: void invalidate_all(); void invalidate_all_lines(); - _FORCE_INLINE_ const String &operator[](int p_line) const; + _FORCE_INLINE_ String operator[](int p_line) const; /* Gutters. */ void add_gutter(int p_at); @@ -453,6 +448,7 @@ private: void _caret_changed(int p_caret = -1); void _emit_caret_changed(); + void _show_virtual_keyboard(); void _reset_caret_blink_timer(); void _toggle_draw_caret(); @@ -568,8 +564,10 @@ private: /* Syntax highlighting. */ Ref<SyntaxHighlighter> syntax_highlighter; + HashMap<int, Vector<Pair<int64_t, Color>>> syntax_highlighting_cache; - Dictionary _get_line_syntax_highlighting(int p_line); + Vector<Pair<int64_t, Color>> _get_line_syntax_highlighting(int p_line); + void _clear_syntax_highlighting_cache(); /* Visual. */ struct ThemeCache { @@ -1023,6 +1021,7 @@ public: void add_gutter(int p_at = -1); void remove_gutter(int p_gutter); int get_gutter_count() const; + Vector2i get_hovered_gutter() const { return hovered_gutter; } void set_gutter_name(int p_gutter, const String &p_name); String get_gutter_name(int p_gutter) const; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index 5830bea258..8871af23cb 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -1564,7 +1564,7 @@ void TreeItem::_call_recursive_bind(const Variant **p_args, int p_argcount, Call return; } - if (p_args[0]->get_type() != Variant::STRING && p_args[0]->get_type() != Variant::STRING_NAME) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -3274,12 +3274,10 @@ void Tree::value_editor_changed(double p_value) { return; } - TreeItem::Cell &c = popup_edited_item->cells.write[popup_edited_item_col]; - c.val = p_value; + const TreeItem::Cell &c = popup_edited_item->cells[popup_edited_item_col]; - line_editor->set_text(String::num(c.val, Math::range_step_decimals(c.step))); + line_editor->set_text(String::num(p_value, Math::range_step_decimals(c.step))); - item_edited(popup_edited_item_col, popup_edited_item); queue_redraw(); } @@ -3468,29 +3466,37 @@ void Tree::gui_input(const Ref<InputEvent> &p_event) { accept_event(); } - if (!selected_item || select_mode == SELECT_ROW || selected_col > (columns.size() - 1)) { + if (!selected_item || selected_col > (columns.size() - 1)) { return; } + if (k.is_valid() && k->is_shift_pressed()) { selected_item->set_collapsed_recursive(false); - } else { + } else if (select_mode != SELECT_ROW) { _go_right(); + } else if (selected_item->get_first_child() != nullptr && selected_item->is_collapsed()) { + selected_item->set_collapsed(false); + } else { + _go_down(); } } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { if (!cursor_can_exit_tree) { accept_event(); } - if (!selected_item || select_mode == SELECT_ROW || selected_col < 0) { + if (!selected_item || selected_col < 0) { return; } if (k.is_valid() && k->is_shift_pressed()) { selected_item->set_collapsed_recursive(true); - } else { + } else if (select_mode != SELECT_ROW) { _go_left(); + } else if (selected_item->get_first_child() != nullptr && !selected_item->is_collapsed()) { + selected_item->set_collapsed(true); + } else { + _go_up(); } - } else if (p_event->is_action("ui_up") && p_event->is_pressed() && !is_command) { if (!cursor_can_exit_tree) { accept_event(); @@ -4490,9 +4496,16 @@ void Tree::item_edited(int p_column, TreeItem *p_item, MouseButton p_custom_mous } void Tree::item_changed(int p_column, TreeItem *p_item) { - if (p_item != nullptr && p_column >= 0 && p_column < p_item->cells.size()) { - p_item->cells.write[p_column].dirty = true; - columns.write[p_column].cached_minimum_width_dirty = true; + if (p_item != nullptr) { + if (p_column >= 0 && p_column < p_item->cells.size()) { + p_item->cells.write[p_column].dirty = true; + columns.write[p_column].cached_minimum_width_dirty = true; + } else if (p_column == -1) { + for (int i = 0; i < p_item->cells.size(); i++) { + p_item->cells.write[i].dirty = true; + columns.write[i].cached_minimum_width_dirty = true; + } + } } queue_redraw(); } diff --git a/scene/main/node.cpp b/scene/main/node.cpp index de6d49761b..50f5923e3c 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -770,8 +770,7 @@ Error Node::_rpc_bind(const Variant **p_args, int p_argcount, Callable::CallErro return ERR_INVALID_PARAMETER; } - Variant::Type type = p_args[0]->get_type(); - if (type != Variant::STRING_NAME && type != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -799,8 +798,7 @@ Error Node::_rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallE return ERR_INVALID_PARAMETER; } - Variant::Type type = p_args[1]->get_type(); - if (type != Variant::STRING_NAME && type != Variant::STRING) { + if (!p_args[1]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 1; r_error.expected = Variant::STRING_NAME; @@ -3436,7 +3434,7 @@ Variant Node::_call_deferred_thread_group_bind(const Variant **p_args, int p_arg return Variant(); } - if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; @@ -3459,7 +3457,7 @@ Variant Node::_call_thread_safe_bind(const Variant **p_args, int p_argcount, Cal return Variant(); } - if (p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING) { + if (!p_args[0]->is_string()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 0; r_error.expected = Variant::STRING_NAME; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index f0c9e8a866..106130872d 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -1315,8 +1315,8 @@ void SceneTree::_call_group_flags(const Variant **p_args, int p_argcount, Callab ERR_FAIL_COND(p_argcount < 3); ERR_FAIL_COND(!p_args[0]->is_num()); - ERR_FAIL_COND(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING); - ERR_FAIL_COND(p_args[2]->get_type() != Variant::STRING_NAME && p_args[2]->get_type() != Variant::STRING); + ERR_FAIL_COND(!p_args[1]->is_string()); + ERR_FAIL_COND(!p_args[2]->is_string()); int flags = *p_args[0]; StringName group = *p_args[1]; @@ -1329,8 +1329,8 @@ void SceneTree::_call_group(const Variant **p_args, int p_argcount, Callable::Ca r_error.error = Callable::CallError::CALL_OK; ERR_FAIL_COND(p_argcount < 2); - ERR_FAIL_COND(p_args[0]->get_type() != Variant::STRING_NAME && p_args[0]->get_type() != Variant::STRING); - ERR_FAIL_COND(p_args[1]->get_type() != Variant::STRING_NAME && p_args[1]->get_type() != Variant::STRING); + ERR_FAIL_COND(!p_args[0]->is_string()); + ERR_FAIL_COND(!p_args[1]->is_string()); StringName group = *p_args[0]; StringName method = *p_args[1]; diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index a370ef2d18..de2332125f 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -655,9 +655,7 @@ void Viewport::_notification(int p_what) { case NOTIFICATION_WM_WINDOW_FOCUS_OUT: { _gui_cancel_tooltip(); _drop_physics_mouseover(); - if (gui.mouse_focus && !gui.forced_mouse_focus) { - _drop_mouse_focus(); - } + _drop_mouse_focus(); // When the window focus changes, we want to end mouse_focus, but // not the mouse_over. Note: The OS will trigger a separate mouse // exit event if the change in focus results in the mouse exiting @@ -1835,7 +1833,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { // as the release will never be received otherwise. if (gui.mouse_focus_mask.is_empty()) { gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; } bool stopped = mouse_focus && mouse_focus->can_process() && _gui_call_input(mouse_focus, mb); @@ -1864,7 +1861,6 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) { gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos - gui.drag_accum)); if (gui.drag_data.get_type() != Variant::NIL) { gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; gui.mouse_focus_mask.clear(); break; } else { @@ -2407,7 +2403,6 @@ void Viewport::_gui_hide_control(Control *p_control) { void Viewport::_gui_remove_control(Control *p_control) { if (gui.mouse_focus == p_control) { gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; gui.mouse_focus_mask.clear(); } if (gui.key_focus == p_control) { @@ -2573,9 +2568,12 @@ void Viewport::_drop_mouse_focus() { Control *c = gui.mouse_focus; BitField<MouseButtonMask> mask = gui.mouse_focus_mask; gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; gui.mouse_focus_mask.clear(); + if (!c) { + return; + } + for (int i = 0; i < 3; i++) { if ((int)mask & (1 << i)) { Ref<InputEventMouseButton> mb; @@ -3902,23 +3900,6 @@ Rect2i Viewport::subwindow_get_popup_safe_rect(Window *p_window) const { return gui.sub_windows[index].parent_safe_rect; } -void Viewport::pass_mouse_focus_to(Viewport *p_viewport, Control *p_control) { - ERR_MAIN_THREAD_GUARD; - ERR_FAIL_NULL(p_viewport); - ERR_FAIL_NULL(p_control); - - if (gui.mouse_focus) { - p_viewport->gui.mouse_focus = p_control; - p_viewport->gui.mouse_focus_mask = gui.mouse_focus_mask; - p_viewport->gui.key_focus = p_control; - p_viewport->gui.forced_mouse_focus = true; - - gui.mouse_focus = nullptr; - gui.forced_mouse_focus = false; - gui.mouse_focus_mask.clear(); - } -} - void Viewport::set_sdf_oversize(SDFOversize p_sdf_oversize) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_INDEX(p_sdf_oversize, SDF_OVERSIZE_MAX); @@ -4714,6 +4695,7 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Viewport::warp_mouse); ClassDB::bind_method(D_METHOD("update_mouse_cursor_state"), &Viewport::update_mouse_cursor_state); + ClassDB::bind_method(D_METHOD("gui_cancel_drag"), &Viewport::gui_cancel_drag); ClassDB::bind_method(D_METHOD("gui_get_drag_data"), &Viewport::gui_get_drag_data); ClassDB::bind_method(D_METHOD("gui_is_dragging"), &Viewport::gui_is_dragging); ClassDB::bind_method(D_METHOD("gui_is_drag_successful"), &Viewport::gui_is_drag_successful); @@ -5048,6 +5030,9 @@ Viewport::~Viewport() { for (ViewportTexture *E : viewport_textures) { E->vp = nullptr; } + if (world_2d.is_valid()) { + world_2d->remove_viewport(this); + } ERR_FAIL_NULL(RenderingServer::get_singleton()); RenderingServer::get_singleton()->free(viewport); } diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 0d31c07e57..faa36851e9 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -349,7 +349,6 @@ private: Ref<Texture2D> vrs_texture; struct GUI { - bool forced_mouse_focus = false; //used for menu buttons bool mouse_in_viewport = false; bool key_event_accepted = false; HashMap<int, ObjectID> touch_focus; @@ -662,8 +661,6 @@ public: Viewport *get_parent_viewport() const; Window *get_base_window() const; - void pass_mouse_focus_to(Viewport *p_viewport, Control *p_control); - void set_canvas_cull_mask(uint32_t p_layers); uint32_t get_canvas_cull_mask() const; diff --git a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp index ee6fc61af3..e3f14539a8 100644 --- a/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp +++ b/scene/resources/2d/navigation_mesh_source_geometry_data_2d.cpp @@ -176,7 +176,7 @@ void NavigationMeshSourceGeometryData2D::add_obstruction_outline(const PackedVec } void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry) { - ERR_FAIL_NULL(p_other_geometry); + ERR_FAIL_COND(p_other_geometry.is_null()); Vector<Vector<Vector2>> other_traversable_outlines; Vector<Vector<Vector2>> other_obstruction_outlines; diff --git a/scene/resources/2d/tile_set.compat.inc b/scene/resources/2d/tile_set.compat.inc index 873ae3aa93..58e33f7b35 100644 --- a/scene/resources/2d/tile_set.compat.inc +++ b/scene/resources/2d/tile_set.compat.inc @@ -37,7 +37,7 @@ Ref<NavigationPolygon> TileData::_get_navigation_polygon_bind_compat_84660(int p } Ref<OccluderPolygon2D> TileData::_get_occluder_bind_compat_84660(int p_layer_id) const { - return get_occluder(p_layer_id, false, false, false); + return get_occluder_polygon(p_layer_id, 0, false, false, false); } void TileData::_bind_compatibility_methods() { diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp index dd6ae5096a..229e18be23 100644 --- a/scene/resources/2d/tile_set.cpp +++ b/scene/resources/2d/tile_set.cpp @@ -3444,7 +3444,8 @@ void TileSet::_compatibility_conversion() { polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0); } occluder->set_polygon(polygon); - tile_data->set_occluder(0, occluder); + tile_data->add_occluder_polygon(0); + tile_data->set_occluder_polygon(0, 0, occluder); } if (ctd->navigation.is_valid()) { if (get_navigation_layers_count() < 1) { @@ -3558,7 +3559,8 @@ void TileSet::_compatibility_conversion() { polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0); } occluder->set_polygon(polygon); - tile_data->set_occluder(0, occluder); + tile_data->add_occluder_polygon(0); + tile_data->set_occluder_polygon(0, 0, occluder); } if (ctd->autotile_navpoly_map.has(coords)) { if (get_navigation_layers_count() < 1) { @@ -3920,7 +3922,7 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { int terrain_index = components[1].trim_prefix("terrain_").to_int(); ERR_FAIL_COND_V(terrain_index < 0, false); if (components[2] == "name") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); + ERR_FAIL_COND_V(!p_value.is_string(), false); while (terrain_set_index >= terrain_sets.size()) { add_terrain_set(); } @@ -3958,7 +3960,7 @@ bool TileSet::_set(const StringName &p_name, const Variant &p_value) { int index = components[0].trim_prefix("custom_data_layer_").to_int(); ERR_FAIL_COND_V(index < 0, false); if (components[1] == "name") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); + ERR_FAIL_COND_V(!p_value.is_string(), false); while (index >= custom_data_layers.size()) { add_custom_data_layer(); } @@ -6220,33 +6222,86 @@ int TileData::get_y_sort_origin() const { return y_sort_origin; } +#ifndef DISABLE_DEPRECATED void TileData::set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon) { ERR_FAIL_INDEX(p_layer_id, occluders.size()); - occluders.write[p_layer_id].occluder = p_occluder_polygon; - occluders.write[p_layer_id].transformed_occluders.clear(); + if (get_occluder_polygons_count(p_layer_id) == 0) { + add_occluder_polygon(p_layer_id); + } + set_occluder_polygon(p_layer_id, 0, p_occluder_polygon); emit_signal(CoreStringName(changed)); } Ref<OccluderPolygon2D> TileData::get_occluder(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const { ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>()); + if (get_occluder_polygons_count(p_layer_id) == 0) { + return Ref<OccluderPolygon2D>(); + } + return get_occluder_polygon(p_layer_id, 0, p_flip_h, p_flip_v, p_transpose); +} +#endif // DISABLE_DEPRECATED + +void TileData::set_occluder_polygons_count(int p_layer_id, int p_polygons_count) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + ERR_FAIL_COND(p_polygons_count < 0); + if (p_polygons_count == occluders.write[p_layer_id].polygons.size()) { + return; + } + occluders.write[p_layer_id].polygons.resize(p_polygons_count); + notify_property_list_changed(); + emit_signal(CoreStringName(changed)); +} + +int TileData::get_occluder_polygons_count(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), 0); + return occluders[p_layer_id].polygons.size(); +} + +void TileData::add_occluder_polygon(int p_layer_id) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + occluders.write[p_layer_id].polygons.push_back(OcclusionLayerTileData::PolygonOccluderTileData()); + emit_signal(CoreStringName(changed)); +} + +void TileData::remove_occluder_polygon(int p_layer_id, int p_polygon_index) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size()); + occluders.write[p_layer_id].polygons.remove_at(p_polygon_index); + emit_signal(CoreStringName(changed)); +} + +void TileData::set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + ERR_FAIL_INDEX(p_polygon_index, occluders[p_layer_id].polygons.size()); + + OcclusionLayerTileData::PolygonOccluderTileData &polygon_occluder_tile_data = occluders.write[p_layer_id].polygons.write[p_polygon_index]; + polygon_occluder_tile_data.occluder_polygon = p_occluder_polygon; + polygon_occluder_tile_data.transformed_polygon_occluders.clear(); + emit_signal(CoreStringName(changed)); +} + +Ref<OccluderPolygon2D> TileData::get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h, bool p_flip_v, bool p_transpose) const { + ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref<OccluderPolygon2D>()); + ERR_FAIL_INDEX_V(p_polygon_index, occluders[p_layer_id].polygons.size(), Ref<OccluderPolygon2D>()); const OcclusionLayerTileData &layer_tile_data = occluders[p_layer_id]; + const Ref<OccluderPolygon2D> &occluder_polygon = layer_tile_data.polygons[p_polygon_index].occluder_polygon; int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2; if (key == 0) { - return layer_tile_data.occluder; + return occluder_polygon; } - if (layer_tile_data.occluder.is_null()) { + if (occluder_polygon.is_null()) { return Ref<OccluderPolygon2D>(); } - HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.transformed_occluders.find(key); + HashMap<int, Ref<OccluderPolygon2D>>::Iterator I = layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders.find(key); if (!I) { Ref<OccluderPolygon2D> transformed_polygon; transformed_polygon.instantiate(); - transformed_polygon->set_polygon(get_transformed_vertices(layer_tile_data.occluder->get_polygon(), p_flip_h, p_flip_v, p_transpose)); - layer_tile_data.transformed_occluders[key] = transformed_polygon; + transformed_polygon->set_polygon(get_transformed_vertices(occluder_polygon->get_polygon(), p_flip_h, p_flip_v, p_transpose)); + layer_tile_data.polygons[p_polygon_index].transformed_polygon_occluders[key] = transformed_polygon; return transformed_polygon; } else { return I->value; @@ -6594,13 +6649,37 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { #endif Vector<String> components = String(p_name).split("/", true, 2); - - if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + if (components.size() >= 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { // Occlusion layers. int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); ERR_FAIL_COND_V(layer_index < 0, false); - if (components[1] == "polygon") { - Ref<OccluderPolygon2D> polygon = p_value; + if (components.size() == 2) { + if (components[1] == "polygon") { + // Kept for compatibility. + Ref<OccluderPolygon2D> polygon = p_value; + if (layer_index >= occluders.size()) { + if (tile_set) { + return false; + } else { + occluders.resize(layer_index + 1); + } + } + if (get_occluder_polygons_count(layer_index) == 0) { + add_occluder_polygon(layer_index); + } + set_occluder_polygon(layer_index, 0, polygon); + return true; + } else if (components[1] == "polygons_count") { + if (p_value.get_type() != Variant::INT) { + return false; + } + set_occluder_polygons_count(layer_index, p_value); + return true; + } + } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + // Polygons. + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); if (layer_index >= occluders.size()) { if (tile_set) { @@ -6609,8 +6688,16 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { occluders.resize(layer_index + 1); } } - set_occluder(layer_index, polygon); - return true; + + if (polygon_index >= occluders[layer_index].polygons.size()) { + occluders.write[layer_index].polygons.resize(polygon_index + 1); + } + + if (components[2] == "polygon") { + Ref<OccluderPolygon2D> polygon = p_value; + set_occluder_polygon(layer_index, polygon_index, polygon); + return true; + } } } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { // Physics layers. @@ -6638,6 +6725,7 @@ bool TileData::_set(const StringName &p_name, const Variant &p_value) { return true; } } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + // Polygons. int polygon_index = components[1].trim_prefix("polygon_").to_int(); ERR_FAIL_COND_V(polygon_index < 0, false); @@ -6724,16 +6812,36 @@ bool TileData::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (tile_set) { - if (components.size() == 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + if (components.size() >= 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { // Occlusion layers. int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); ERR_FAIL_COND_V(layer_index < 0, false); if (layer_index >= occluders.size()) { return false; } - if (components[1] == "polygon") { - r_ret = get_occluder(layer_index); - return true; + if (components.size() == 2) { + if (components[1] == "polygon") { + // Kept for compatibility. + if (occluders[layer_index].polygons.is_empty()) { + return false; + } + r_ret = get_occluder_polygon(layer_index, 0); + return true; + } else if (components[1] == "polygons_count") { + r_ret = get_occluder_polygons_count(layer_index); + return true; + } + } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + // Polygons. + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); + if (polygon_index >= occluders[layer_index].polygons.size()) { + return false; + } + if (components[2] == "polygon") { + r_ret = get_occluder_polygon(layer_index, polygon_index); + return true; + } } } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { // Physics layers. @@ -6813,12 +6921,15 @@ void TileData::_get_property_list(List<PropertyInfo> *p_list) const { // Occlusion layers. p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Rendering", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); for (int i = 0; i < occluders.size(); i++) { - // occlusion_layer_%d/polygon - property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT); - if (occluders[i].occluder.is_null()) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; + p_list->push_back(PropertyInfo(Variant::INT, vformat("occlusion_layer_%d/%s", i, PNAME("polygons_count")), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + for (int j = 0; j < occluders[i].polygons.size(); j++) { + // occlusion_layer_%d/polygon_%d/polygon + property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/polygon_%d/%s", i, j, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT); + if (occluders[i].polygons[j].occluder_polygon.is_null()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); } - p_list->push_back(property_info); } // Physics layers. @@ -6923,8 +7034,17 @@ void TileData::_bind_methods() { ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileData::set_y_sort_origin); ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin); + ClassDB::bind_method(D_METHOD("set_occluder_polygons_count", "layer_id", "polygons_count"), &TileData::set_occluder_polygons_count); + ClassDB::bind_method(D_METHOD("get_occluder_polygons_count", "layer_id"), &TileData::get_occluder_polygons_count); + ClassDB::bind_method(D_METHOD("add_occluder_polygon", "layer_id"), &TileData::add_occluder_polygon); + ClassDB::bind_method(D_METHOD("remove_occluder_polygon", "layer_id", "polygon_index"), &TileData::remove_occluder_polygon); + ClassDB::bind_method(D_METHOD("set_occluder_polygon", "layer_id", "polygon_index", "polygon"), &TileData::set_occluder_polygon); + ClassDB::bind_method(D_METHOD("get_occluder_polygon", "layer_id", "polygon_index", "flip_h", "flip_v", "transpose"), &TileData::get_occluder_polygon, DEFVAL(false), DEFVAL(false), DEFVAL(false)); + +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder); ClassDB::bind_method(D_METHOD("get_occluder", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_occluder, DEFVAL(false), DEFVAL(false), DEFVAL(false)); +#endif // DISABLE_DEPRECATED // Physics. ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity); diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h index 51df972c8d..931495d020 100644 --- a/scene/resources/2d/tile_set.h +++ b/scene/resources/2d/tile_set.h @@ -841,8 +841,11 @@ private: int z_index = 0; int y_sort_origin = 0; struct OcclusionLayerTileData { - Ref<OccluderPolygon2D> occluder; - mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_occluders; + struct PolygonOccluderTileData { + Ref<OccluderPolygon2D> occluder_polygon; + mutable HashMap<int, Ref<OccluderPolygon2D>> transformed_polygon_occluders; + }; + Vector<PolygonOccluderTileData> polygons; }; Vector<OcclusionLayerTileData> occluders; @@ -941,8 +944,17 @@ public: void set_y_sort_origin(int p_y_sort_origin); int get_y_sort_origin() const; +#ifndef DISABLE_DEPRECATED void set_occluder(int p_layer_id, Ref<OccluderPolygon2D> p_occluder_polygon); Ref<OccluderPolygon2D> get_occluder(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; +#endif // DISABLE_DEPRECATED + + void set_occluder_polygons_count(int p_layer_id, int p_polygons_count); + int get_occluder_polygons_count(int p_layer_id) const; + void add_occluder_polygon(int p_layer_id); + void remove_occluder_polygon(int p_layer_id, int p_polygon_index); + void set_occluder_polygon(int p_layer_id, int p_polygon_index, const Ref<OccluderPolygon2D> &p_occluder_polygon); + Ref<OccluderPolygon2D> get_occluder_polygon(int p_layer_id, int p_polygon_index, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; // Physics void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity); diff --git a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp index c55e25fcae..59366592ce 100644 --- a/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp +++ b/scene/resources/3d/navigation_mesh_source_geometry_data_3d.cpp @@ -223,7 +223,7 @@ void NavigationMeshSourceGeometryData3D::add_faces(const PackedVector3Array &p_f } void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry) { - ERR_FAIL_NULL(p_other_geometry); + ERR_FAIL_COND(p_other_geometry.is_null()); Vector<float> other_vertices; Vector<int> other_indices; diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index e00bf56939..a2ed6af23c 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -1717,7 +1717,7 @@ int Animation::track_insert_key(int p_track, double p_time, const Variant &p_key ERR_FAIL_COND_V(p_key.get_type() != Variant::DICTIONARY, -1); Dictionary d = p_key; - ERR_FAIL_COND_V(!d.has("method") || (d["method"].get_type() != Variant::STRING_NAME && d["method"].get_type() != Variant::STRING), -1); + ERR_FAIL_COND_V(!d.has("method") || !d["method"].is_string(), -1); ERR_FAIL_COND_V(!d.has("args") || !d["args"].is_array(), -1); MethodKey k; diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 9e6d34959a..0c29790ea4 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -45,7 +45,7 @@ public: static inline String PARAMETERS_BASE_PATH = "parameters/"; - enum TrackType { + enum TrackType : uint8_t { TYPE_VALUE, // Set a value in a property, can be interpolated. TYPE_POSITION_3D, // Position 3D track, can be compressed. TYPE_ROTATION_3D, // Rotation 3D track, can be compressed. @@ -57,7 +57,7 @@ public: TYPE_ANIMATION, }; - enum InterpolationType { + enum InterpolationType : uint8_t { INTERPOLATION_NEAREST, INTERPOLATION_LINEAR, INTERPOLATION_CUBIC, @@ -65,26 +65,26 @@ public: INTERPOLATION_CUBIC_ANGLE, }; - enum UpdateMode { + enum UpdateMode : uint8_t { UPDATE_CONTINUOUS, UPDATE_DISCRETE, UPDATE_CAPTURE, }; - enum LoopMode { + enum LoopMode : uint8_t { LOOP_NONE, LOOP_LINEAR, LOOP_PINGPONG, }; // LoopedFlag is used in Animataion to "process the keys at both ends correct". - enum LoopedFlag { + enum LoopedFlag : uint8_t { LOOPED_FLAG_NONE, LOOPED_FLAG_END, LOOPED_FLAG_START, }; - enum FindMode { + enum FindMode : uint8_t { FIND_MODE_NEAREST, FIND_MODE_APPROX, FIND_MODE_EXACT, @@ -104,7 +104,6 @@ public: }; #endif // TOOLS_ENABLED -private: struct Track { TrackType type = TrackType::TYPE_ANIMATION; InterpolationType interpolation = INTERPOLATION_LINEAR; @@ -117,6 +116,7 @@ private: virtual ~Track() {} }; +private: struct Key { real_t transition = 1.0; double time = 0.0; // Time in secs. @@ -396,6 +396,10 @@ public: int add_track(TrackType p_type, int p_at_pos = -1); void remove_track(int p_track); + _FORCE_INLINE_ const Vector<Track *> get_tracks() { + return tracks; + } + bool is_capture_included() const; int get_track_count() const; diff --git a/scene/resources/audio_stream_wav.cpp b/scene/resources/audio_stream_wav.cpp index 08ebacc2b3..d0e0f0eef3 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -123,10 +123,8 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, int16_t nibble, diff, step; p_ima_adpcm[i].last_nibble++; - const uint8_t *src_ptr = (const uint8_t *)base->data; - src_ptr += AudioStreamWAV::DATA_PAD; - uint8_t nbb = src_ptr[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i]; + uint8_t nbb = p_src[(p_ima_adpcm[i].last_nibble >> 1) * (is_stereo ? 2 : 1) + i]; nibble = (p_ima_adpcm[i].last_nibble & 1) ? (nbb >> 4) : (nbb & 0xF); step = _ima_adpcm_step_table[p_ima_adpcm[i].step_index]; @@ -184,9 +182,8 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, if (p_qoa->data_ofs != new_data_ofs) { p_qoa->data_ofs = new_data_ofs; - const uint8_t *src_ptr = (const uint8_t *)base->data; - src_ptr += p_qoa->data_ofs + AudioStreamWAV::DATA_PAD; - qoa_decode_frame(src_ptr, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec, &p_qoa->dec_len); + const uint8_t *ofs_src = (uint8_t *)p_src + p_qoa->data_ofs; + qoa_decode_frame(ofs_src, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec.ptr(), &p_qoa->dec_len); } uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc.channels; @@ -267,7 +264,7 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, } int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_frames) { - if (!base->data || !active) { + if (base->data.is_empty() || !active) { for (int i = 0; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } @@ -324,8 +321,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ /* audio data */ - uint8_t *dataptr = (uint8_t *)base->data; - const void *data = dataptr + AudioStreamWAV::DATA_PAD; + const uint8_t *data = base->data.ptr() + AudioStreamWAV::DATA_PAD; AudioFrame *dst_buff = p_buffer; if (format == AudioStreamWAV::FORMAT_IMA_ADPCM) { @@ -483,11 +479,7 @@ void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback> AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {} -AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() { - if (qoa.dec) { - memfree(qoa.dec); - } -} +AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() {} ///////////////////// @@ -554,7 +546,7 @@ double AudioStreamWAV::get_length() const { break; case AudioStreamWAV::FORMAT_QOA: qoa_desc desc = {}; - qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc); + qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &desc); len = desc.samples * desc.channels; break; } @@ -572,22 +564,16 @@ bool AudioStreamWAV::is_monophonic() const { void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) { AudioServer::get_singleton()->lock(); - if (data) { - memfree(data); - data = nullptr; - data_bytes = 0; - } - int datalen = p_data.size(); - if (datalen) { - const uint8_t *r = p_data.ptr(); - int alloc_len = datalen + DATA_PAD * 2; - data = memalloc(alloc_len); //alloc with some padding for interpolation - memset(data, 0, alloc_len); - uint8_t *dataptr = (uint8_t *)data; - memcpy(dataptr + DATA_PAD, r, datalen); - data_bytes = datalen; - } + int src_data_len = p_data.size(); + + data.clear(); + + int alloc_len = src_data_len + DATA_PAD * 2; + data.resize(alloc_len); + memset(data.ptr(), 0, alloc_len); + memcpy(data.ptr() + DATA_PAD, p_data.ptr(), src_data_len); + data_bytes = src_data_len; AudioServer::get_singleton()->unlock(); } @@ -595,13 +581,9 @@ void AudioStreamWAV::set_data(const Vector<uint8_t> &p_data) { Vector<uint8_t> AudioStreamWAV::get_data() const { Vector<uint8_t> pv; - if (data) { + if (!data.is_empty()) { pv.resize(data_bytes); - { - uint8_t *w = pv.ptrw(); - uint8_t *dataptr = (uint8_t *)data; - memcpy(w, dataptr + DATA_PAD, data_bytes); - } + memcpy(pv.ptrw(), data.ptr() + DATA_PAD, data_bytes); } return pv; @@ -693,12 +675,12 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() { sample->base = Ref<AudioStreamWAV>(this); if (format == AudioStreamWAV::FORMAT_QOA) { - uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &sample->qoa.desc); + uint32_t ffp = qoa_decode_header(data.ptr() + DATA_PAD, data_bytes, &sample->qoa.desc); ERR_FAIL_COND_V(ffp != 8, Ref<AudioStreamPlaybackWAV>()); sample->qoa.frame_len = qoa_max_frame_size(&sample->qoa.desc); int samples_len = (sample->qoa.desc.samples > QOA_FRAME_LEN ? QOA_FRAME_LEN : sample->qoa.desc.samples); - int alloc_len = sample->qoa.desc.channels * samples_len * sizeof(int16_t); - sample->qoa.dec = (int16_t *)memalloc(alloc_len); + int dec_len = sample->qoa.desc.channels * samples_len; + sample->qoa.dec.resize(dec_len); } return sample; @@ -780,10 +762,4 @@ void AudioStreamWAV::_bind_methods() { AudioStreamWAV::AudioStreamWAV() {} -AudioStreamWAV::~AudioStreamWAV() { - if (data) { - memfree(data); - data = nullptr; - data_bytes = 0; - } -} +AudioStreamWAV::~AudioStreamWAV() {} diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h index 47aa10e790..bc62e8883a 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -62,7 +62,7 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback { qoa_desc desc = {}; uint32_t data_ofs = 0; uint32_t frame_len = 0; - int16_t *dec = nullptr; + LocalVector<int16_t> dec; uint32_t dec_len = 0; int64_t cache_pos = -1; int16_t cache[2] = { 0, 0 }; @@ -137,7 +137,7 @@ private: int loop_begin = 0; int loop_end = 0; int mix_rate = 44100; - void *data = nullptr; + LocalVector<uint8_t> data; uint32_t data_bytes = 0; protected: diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index d07dee6674..927e76e4b2 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -37,7 +37,7 @@ #include "scene/main/scene_tree.h" void Material::set_next_pass(const Ref<Material> &p_pass) { - for (Ref<Material> pass_child = p_pass; pass_child != nullptr; pass_child = pass_child->get_next_pass()) { + for (Ref<Material> pass_child = p_pass; pass_child.is_valid(); pass_child = pass_child->get_next_pass()) { ERR_FAIL_COND_MSG(pass_child == this, "Can't set as next_pass one of its parents to prevent crashes due to recursive loop."); } diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index 8b5e438aea..22e2e9138f 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -2251,6 +2251,7 @@ Error ArrayMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, flo } void ArrayMesh::set_shadow_mesh(const Ref<ArrayMesh> &p_mesh) { + ERR_FAIL_COND_MSG(p_mesh == this, "Cannot set a mesh as its own shadow mesh."); shadow_mesh = p_mesh; if (shadow_mesh.is_valid()) { RS::get_singleton()->mesh_set_shadow_mesh(mesh, shadow_mesh->get_rid()); diff --git a/scene/resources/packed_scene.cpp b/scene/resources/packed_scene.cpp index 27db65bb1a..69dc71e414 100644 --- a/scene/resources/packed_scene.cpp +++ b/scene/resources/packed_scene.cpp @@ -369,6 +369,7 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { value = make_local_resource(value, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); } } + if (value.get_type() == Variant::ARRAY) { Array set_array = value; value = setup_resources_in_array(set_array, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); @@ -383,25 +384,20 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { } } } - if (value.get_type() == Variant::DICTIONARY) { - Dictionary dictionary = value; - const Array keys = dictionary.keys(); - const Array values = dictionary.values(); - - if (has_local_resource(values) || has_local_resource(keys)) { - Array duplicated_keys = keys.duplicate(true); - Array duplicated_values = values.duplicate(true); - duplicated_keys = setup_resources_in_array(duplicated_keys, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); - duplicated_values = setup_resources_in_array(duplicated_values, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + value = setup_resources_in_dictionary(set_dict, n, resources_local_to_sub_scene, node, snames[nprops[j].name], resources_local_to_scene, i, ret_nodes, p_edit_state); - dictionary.clear(); + bool is_get_valid = false; + Variant get_value = node->get(snames[nprops[j].name], &is_get_valid); - for (int dictionary_index = 0; dictionary_index < keys.size(); dictionary_index++) { - dictionary[duplicated_keys[dictionary_index]] = duplicated_values[dictionary_index]; + 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()); } - - value = dictionary; } } @@ -539,6 +535,30 @@ Node *SceneState::instantiate(GenEditState p_edit_state) const { array.set(i, dnp.base->get_node_or_null(paths[i])); } dnp.base->set(dnp.property, array); + } else if (dnp.value.get_type() == Variant::DICTIONARY) { + Dictionary paths = dnp.value; + + bool valid; + Dictionary dict = dnp.base->get(dnp.property, &valid); + ERR_CONTINUE(!valid); + dict = dict.duplicate(); + bool convert_key = dict.get_typed_key_builtin() == Variant::OBJECT && + ClassDB::is_parent_class(dict.get_typed_key_class_name(), "Node"); + bool convert_value = dict.get_typed_value_builtin() == Variant::OBJECT && + ClassDB::is_parent_class(dict.get_typed_value_class_name(), "Node"); + + for (int i = 0; i < paths.size(); i++) { + Variant key = paths.get_key_at_index(i); + if (convert_key) { + key = dnp.base->get_node_or_null(key); + } + Variant value = paths.get_value_at_index(i); + if (convert_value) { + value = dnp.base->get_node_or_null(value); + } + dict[key] = value; + } + dnp.base->set(dnp.property, dict); } else { dnp.base->set(dnp.property, dnp.base->get_node_or_null(dnp.value)); } @@ -641,6 +661,26 @@ Array SceneState::setup_resources_in_array(Array &p_array_to_scan, const SceneSt return p_array_to_scan; } +Dictionary SceneState::setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const { + const Array keys = p_dictionary_to_scan.keys(); + const Array values = p_dictionary_to_scan.values(); + + if (has_local_resource(values) || has_local_resource(keys)) { + Array duplicated_keys = keys.duplicate(true); + Array duplicated_values = values.duplicate(true); + + duplicated_keys = setup_resources_in_array(duplicated_keys, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state); + duplicated_values = setup_resources_in_array(duplicated_values, p_n, p_resources_local_to_sub_scene, p_node, p_sname, p_resources_local_to_scene, p_i, p_ret_nodes, p_edit_state); + p_dictionary_to_scan.clear(); + + for (int i = 0; i < keys.size(); i++) { + p_dictionary_to_scan[duplicated_keys[i]] = duplicated_values[i]; + } + } + + return p_dictionary_to_scan; +} + bool SceneState::has_local_resource(const Array &p_array) const { for (int i = 0; i < p_array.size(); i++) { Ref<Resource> res = p_array[i]; @@ -810,6 +850,53 @@ Error SceneState::_parse_node(Node *p_owner, Node *p_node, int p_parent_idx, Has value = new_array; } } + } else if (E.type == Variant::DICTIONARY && E.hint == PROPERTY_HINT_TYPE_STRING) { + int key_value_separator = E.hint_string.find(";"); + if (key_value_separator >= 0) { + int key_subtype_separator = E.hint_string.find(":"); + String key_subtype_string = E.hint_string.substr(0, key_subtype_separator); + int key_slash_pos = key_subtype_string.find("/"); + PropertyHint key_subtype_hint = PropertyHint::PROPERTY_HINT_NONE; + if (key_slash_pos >= 0) { + key_subtype_hint = PropertyHint(key_subtype_string.get_slice("/", 1).to_int()); + key_subtype_string = key_subtype_string.substr(0, key_slash_pos); + } + Variant::Type key_subtype = Variant::Type(key_subtype_string.to_int()); + bool convert_key = key_subtype == Variant::OBJECT && key_subtype_hint == PROPERTY_HINT_NODE_TYPE; + + int value_subtype_separator = E.hint_string.find(":", key_value_separator) - (key_value_separator + 1); + String value_subtype_string = E.hint_string.substr(key_value_separator + 1, value_subtype_separator); + int value_slash_pos = value_subtype_string.find("/"); + PropertyHint value_subtype_hint = PropertyHint::PROPERTY_HINT_NONE; + if (value_slash_pos >= 0) { + value_subtype_hint = PropertyHint(value_subtype_string.get_slice("/", 1).to_int()); + value_subtype_string = value_subtype_string.substr(0, value_slash_pos); + } + Variant::Type value_subtype = Variant::Type(value_subtype_string.to_int()); + bool convert_value = value_subtype == Variant::OBJECT && value_subtype_hint == PROPERTY_HINT_NODE_TYPE; + + if (convert_key || convert_value) { + use_deferred_node_path_bit = true; + Dictionary dict = value; + Dictionary new_dict; + for (int i = 0; i < dict.size(); i++) { + Variant new_key = dict.get_key_at_index(i); + if (convert_key && new_key.get_type() == Variant::OBJECT) { + if (Node *n = Object::cast_to<Node>(new_key)) { + new_key = p_node->get_path_to(n); + } + } + Variant new_value = dict.get_value_at_index(i); + if (convert_value && new_value.get_type() == Variant::OBJECT) { + if (Node *n = Object::cast_to<Node>(new_value)) { + new_value = p_node->get_path_to(n); + } + } + new_dict[new_key] = new_value; + } + value = new_dict; + } + } } if (!pinned_props.has(name)) { @@ -1267,25 +1354,6 @@ Ref<SceneState> SceneState::get_base_scene_state() const { return Ref<SceneState>(); } -void SceneState::update_instance_resource(String p_path, Ref<PackedScene> p_packed_scene) { - ERR_FAIL_COND(p_packed_scene.is_null()); - - for (const NodeData &nd : nodes) { - if (nd.instance >= 0) { - if (!(nd.instance & FLAG_INSTANCE_IS_PLACEHOLDER)) { - int instance_id = nd.instance & FLAG_MASK; - Ref<PackedScene> original_packed_scene = variants[instance_id]; - if (original_packed_scene.is_valid()) { - if (original_packed_scene->get_path() == p_path) { - variants.remove_at(instance_id); - variants.insert(instance_id, p_packed_scene); - } - } - } - } - } -} - int SceneState::find_node_by_path(const NodePath &p_node) const { ERR_FAIL_COND_V_MSG(node_path_cache.is_empty(), -1, "This operation requires the node cache to have been built."); diff --git a/scene/resources/packed_scene.h b/scene/resources/packed_scene.h index d27def1760..9f8088910f 100644 --- a/scene/resources/packed_scene.h +++ b/scene/resources/packed_scene.h @@ -158,6 +158,7 @@ public: Node *instantiate(GenEditState p_edit_state) const; Array setup_resources_in_array(Array &array_to_scan, const SceneState::NodeData &n, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_sub_scene, Node *node, const StringName sname, HashMap<Ref<Resource>, Ref<Resource>> &resources_local_to_scene, int i, Node **ret_nodes, SceneState::GenEditState p_edit_state) const; + Dictionary setup_resources_in_dictionary(Dictionary &p_dictionary_to_scan, const SceneState::NodeData &p_n, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const; Variant make_local_resource(Variant &value, const SceneState::NodeData &p_node_data, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_sub_scene, Node *p_node, const StringName p_sname, HashMap<Ref<Resource>, Ref<Resource>> &p_resources_local_to_scene, int p_i, Node **p_ret_nodes, SceneState::GenEditState p_edit_state) const; bool has_local_resource(const Array &p_array) const; diff --git a/scene/resources/particle_process_material.cpp b/scene/resources/particle_process_material.cpp index ee986f5820..8cfe4c92b7 100644 --- a/scene/resources/particle_process_material.cpp +++ b/scene/resources/particle_process_material.cpp @@ -110,6 +110,7 @@ void ParticleProcessMaterial::init_shaders() { shader_names->emission_ring_height = "emission_ring_height"; shader_names->emission_ring_radius = "emission_ring_radius"; shader_names->emission_ring_inner_radius = "emission_ring_inner_radius"; + shader_names->emission_ring_cone_angle = "emission_ring_cone_angle"; shader_names->emission_shape_offset = "emission_shape_offset"; shader_names->emission_shape_scale = "emission_shape_scale"; @@ -269,6 +270,7 @@ void ParticleProcessMaterial::_update_shader() { code += "uniform float " + shader_names->emission_ring_height + ";\n"; code += "uniform float " + shader_names->emission_ring_radius + ";\n"; code += "uniform float " + shader_names->emission_ring_inner_radius + ";\n"; + code += "uniform float " + shader_names->emission_ring_cone_angle + ";\n"; } break; case EMISSION_SHAPE_MAX: { // Max value for validity check. break; @@ -643,8 +645,14 @@ void ParticleProcessMaterial::_update_shader() { code += " pos = texelFetch(emission_texture_points, emission_tex_ofs, 0).xyz;\n"; } if (emission_shape == EMISSION_SHAPE_RING) { + code += " float radius_clamped = max(0.001, emission_ring_radius);\n"; + code += " float top_radius = max(radius_clamped - tan(radians(90.0 - emission_ring_cone_angle)) * emission_ring_height, 0.0);\n"; + code += " float y_pos = rand_from_seed(alt_seed);\n"; + code += " float skew = max(min(radius_clamped, top_radius) / max(radius_clamped, top_radius), 0.5);\n"; + code += " y_pos = radius_clamped < top_radius ? pow(y_pos, skew) : 1.0 - pow(y_pos, skew);\n"; code += " float ring_spawn_angle = rand_from_seed(alt_seed) * 2.0 * pi;\n"; - code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (emission_ring_radius * emission_ring_radius - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n"; + code += " float ring_random_radius = sqrt(rand_from_seed(alt_seed) * (radius_clamped * radius_clamped - emission_ring_inner_radius * emission_ring_inner_radius) + emission_ring_inner_radius * emission_ring_inner_radius);\n"; + code += " ring_random_radius = mix(ring_random_radius, ring_random_radius * (top_radius / radius_clamped), y_pos);\n"; code += " vec3 axis = emission_ring_axis == vec3(0.0) ? vec3(0.0, 0.0, 1.0) : normalize(emission_ring_axis);\n"; code += " vec3 ortho_axis = vec3(0.0);\n"; code += " if (abs(axis) == vec3(1.0, 0.0, 0.0)) {\n"; @@ -662,7 +670,7 @@ void ParticleProcessMaterial::_update_shader() { code += " vec3(axis.z * axis.x * oc - axis.y * s, axis.z * axis.y * oc + axis.x * s, c + axis.z * axis.z * oc)\n"; code += " ) * ortho_axis;\n"; code += " ortho_axis = normalize(ortho_axis);\n"; - code += " pos = ortho_axis * ring_random_radius + (rand_from_seed(alt_seed) * emission_ring_height - emission_ring_height / 2.0) * axis;\n"; + code += " pos = ortho_axis * ring_random_radius + (y_pos * emission_ring_height - emission_ring_height / 2.0) * axis;\n"; } code += " }\n"; code += " return pos * emission_shape_scale + emission_shape_offset;\n"; @@ -1615,6 +1623,11 @@ void ParticleProcessMaterial::set_emission_ring_inner_radius(real_t p_radius) { RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_inner_radius, p_radius); } +void ParticleProcessMaterial::set_emission_ring_cone_angle(real_t p_angle) { + emission_ring_cone_angle = p_angle; + RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_ring_cone_angle, p_angle); +} + void ParticleProcessMaterial::set_inherit_velocity_ratio(double p_ratio) { inherit_emitter_velocity_ratio = p_ratio; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->inherit_emitter_velocity_ratio, p_ratio); @@ -1664,6 +1677,10 @@ real_t ParticleProcessMaterial::get_emission_ring_inner_radius() const { return emission_ring_inner_radius; } +real_t ParticleProcessMaterial::get_emission_ring_cone_angle() const { + return emission_ring_cone_angle; +} + void ParticleProcessMaterial::set_emission_shape_offset(const Vector3 &p_emission_shape_offset) { emission_shape_offset = p_emission_shape_offset; RenderingServer::get_singleton()->material_set_param(_get_material(), shader_names->emission_shape_offset, p_emission_shape_offset); @@ -2015,6 +2032,9 @@ void ParticleProcessMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("set_emission_ring_inner_radius", "inner_radius"), &ParticleProcessMaterial::set_emission_ring_inner_radius); ClassDB::bind_method(D_METHOD("get_emission_ring_inner_radius"), &ParticleProcessMaterial::get_emission_ring_inner_radius); + ClassDB::bind_method(D_METHOD("set_emission_ring_cone_angle", "cone_angle"), &ParticleProcessMaterial::set_emission_ring_cone_angle); + ClassDB::bind_method(D_METHOD("get_emission_ring_cone_angle"), &ParticleProcessMaterial::get_emission_ring_cone_angle); + ClassDB::bind_method(D_METHOD("set_emission_shape_offset", "emission_shape_offset"), &ParticleProcessMaterial::set_emission_shape_offset); ClassDB::bind_method(D_METHOD("get_emission_shape_offset"), &ParticleProcessMaterial::get_emission_shape_offset); @@ -2096,9 +2116,10 @@ void ParticleProcessMaterial::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "emission_color_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_emission_color_texture", "get_emission_color_texture"); ADD_PROPERTY(PropertyInfo(Variant::INT, "emission_point_count", PROPERTY_HINT_RANGE, "0,1000000,1"), "set_emission_point_count", "get_emission_point_count"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "emission_ring_axis"), "set_emission_ring_axis", "get_emission_ring_axis"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height"), "set_emission_ring_height", "get_emission_ring_height"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius"), "set_emission_ring_radius", "get_emission_ring_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_height", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_height", "get_emission_ring_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_radius", "get_emission_ring_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_inner_radius", PROPERTY_HINT_RANGE, "0,1000,0.01,or_greater"), "set_emission_ring_inner_radius", "get_emission_ring_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "emission_ring_cone_angle", PROPERTY_HINT_RANGE, "0,90,0.01,degrees"), "set_emission_ring_cone_angle", "get_emission_ring_cone_angle"); ADD_SUBGROUP("Angle", ""); ADD_MIN_MAX_PROPERTY("angle", "-720,720,0.1,or_less,or_greater,degrees", PARAM_ANGLE); ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "angle_curve", PROPERTY_HINT_RESOURCE_TYPE, "CurveTexture"), "set_param_texture", "get_param_texture", PARAM_ANGLE); @@ -2276,6 +2297,7 @@ ParticleProcessMaterial::ParticleProcessMaterial() : set_emission_ring_height(1); set_emission_ring_radius(1); set_emission_ring_inner_radius(0); + set_emission_ring_cone_angle(90); set_emission_shape_offset(Vector3(0.0, 0.0, 0.0)); set_emission_shape_scale(Vector3(1.0, 1.0, 1.0)); diff --git a/scene/resources/particle_process_material.h b/scene/resources/particle_process_material.h index 25046b51cd..12e3fbb64e 100644 --- a/scene/resources/particle_process_material.h +++ b/scene/resources/particle_process_material.h @@ -259,6 +259,7 @@ private: StringName emission_ring_height; StringName emission_ring_radius; StringName emission_ring_inner_radius; + StringName emission_ring_cone_angle; StringName emission_shape_offset; StringName emission_shape_scale; @@ -325,6 +326,7 @@ private: real_t emission_ring_height = 0.0f; real_t emission_ring_radius = 0.0f; real_t emission_ring_inner_radius = 0.0f; + real_t emission_ring_cone_angle = 0.0f; int emission_point_count = 1; Vector3 emission_shape_offset; Vector3 emission_shape_scale; @@ -417,6 +419,7 @@ public: void set_emission_ring_height(real_t p_height); void set_emission_ring_radius(real_t p_radius); void set_emission_ring_inner_radius(real_t p_radius); + void set_emission_ring_cone_angle(real_t p_angle); void set_emission_point_count(int p_count); EmissionShape get_emission_shape() const; @@ -429,6 +432,7 @@ public: real_t get_emission_ring_height() const; real_t get_emission_ring_radius() const; real_t get_emission_ring_inner_radius() const; + real_t get_emission_ring_cone_angle() const; int get_emission_point_count() const; void set_turbulence_enabled(bool p_turbulence_enabled); diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 90102e44e4..29f9835ba9 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -35,17 +35,12 @@ #include "core/io/missing_resource.h" #include "core/object/script_language.h" -// Version 2: Changed names for Basis, AABB, Vectors, etc. -// Version 3: New string ID for ext/subresources, breaks forward compat. -// Version 4: PackedByteArray can be base64 encoded, and PackedVector4Array was added. -#define FORMAT_VERSION 4 -// For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray. -#define FORMAT_VERSION_COMPAT 3 - -#define _printerr() ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data()); - /// +void ResourceLoaderText::_printerr() { + ERR_PRINT(String(res_path + ":" + itos(lines) + " - Parse Error: " + error_text).utf8().get_data()); +} + Ref<Resource> ResourceLoaderText::get_resource() { return resource; } @@ -629,6 +624,19 @@ Error ResourceLoaderText::load() { } } + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = res->get(assign, &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()); + } + } + } + if (set_valid) { res->set(assign, value); } @@ -756,6 +764,19 @@ Error ResourceLoaderText::load() { } } + if (value.get_type() == Variant::DICTIONARY) { + Dictionary set_dict = value; + bool is_get_valid = false; + Variant get_value = resource->get(assign, &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()); + } + } + } + if (set_valid) { resource->set(assign, value); } @@ -1647,6 +1668,8 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant, } break; 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) { @@ -1734,7 +1757,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso if (load_steps > 1) { title += "load_steps=" + itos(load_steps) + " "; } - title += "format=" + itos(use_compat ? FORMAT_VERSION_COMPAT : FORMAT_VERSION) + ""; + title += "format=" + itos(use_compat ? ResourceLoaderText::FORMAT_VERSION_COMPAT : ResourceLoaderText::FORMAT_VERSION) + ""; ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(local_path, true); diff --git a/scene/resources/resource_format_text.h b/scene/resources/resource_format_text.h index b5542f77ba..8397bc985f 100644 --- a/scene/resources/resource_format_text.h +++ b/scene/resources/resource_format_text.h @@ -38,6 +38,17 @@ #include "scene/resources/packed_scene.h" class ResourceLoaderText { +public: + enum { + // Version 2: Changed names for Basis, AABB, Vectors, etc. + // Version 3: New string ID for ext/subresources, breaks forward compat. + // Version 4: PackedByteArray can be base64 encoded, and PackedVector4Array was added. + FORMAT_VERSION = 4, + // For compat, save as version 3 if not using PackedVector4Array or no big PackedByteArray. + FORMAT_VERSION_COMPAT = 3, + }; + +private: bool translation_remapped = false; String local_path; String res_path; @@ -100,6 +111,7 @@ class ResourceLoaderText { static Error _parse_sub_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str); static Error _parse_ext_resource_dummy(DummyReadData *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str); + void _printerr(); VariantParser::ResourceParser rp; diff --git a/scene/resources/shader.cpp b/scene/resources/shader.cpp index f343229cd8..46d38146a6 100644 --- a/scene/resources/shader.cpp +++ b/scene/resources/shader.cpp @@ -158,7 +158,7 @@ void Shader::get_shader_uniform_list(List<PropertyInfo> *p_params, bool p_get_gr #ifdef MODULE_REGEX_ENABLED const RegEx pattern("/\\*\\*\\s([^*]|[\\r\\n]|(\\*+([^*/]|[\\r\\n])))*\\*+/\\s*uniform\\s+\\w+\\s+" + pi.name + "(?=[\\s:;=])"); Ref<RegExMatch> pattern_ref = pattern.search(code); - if (pattern_ref != nullptr) { + if (pattern_ref.is_valid()) { RegExMatch *match = pattern_ref.ptr(); const RegEx pattern_tip("\\/\\*\\*([\\s\\S]*?)\\*/"); Ref<RegExMatch> pattern_tip_ref = pattern_tip.search(match->get_string(0)); diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index 6e43ea9b17..dac0ceaa78 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -106,6 +106,12 @@ bool SpriteFrames::has_animation(const StringName &p_anim) const { return animations.has(p_anim); } +void SpriteFrames::duplicate_animation(const StringName &p_from, const StringName &p_to) { + ERR_FAIL_COND_MSG(!animations.has(p_from), vformat("SpriteFrames doesn't have animation '%s'.", p_from)); + ERR_FAIL_COND_MSG(animations.has(p_to), vformat("Animation '%s' already exists.", p_to)); + animations[p_to] = animations[p_from]; +} + void SpriteFrames::remove_animation(const StringName &p_anim) { animations.erase(p_anim); } @@ -246,6 +252,7 @@ void SpriteFrames::get_argument_options(const StringName &p_function, int p_idx, void SpriteFrames::_bind_methods() { ClassDB::bind_method(D_METHOD("add_animation", "anim"), &SpriteFrames::add_animation); ClassDB::bind_method(D_METHOD("has_animation", "anim"), &SpriteFrames::has_animation); + ClassDB::bind_method(D_METHOD("duplicate_animation", "anim_from", "anim_to"), &SpriteFrames::duplicate_animation); ClassDB::bind_method(D_METHOD("remove_animation", "anim"), &SpriteFrames::remove_animation); ClassDB::bind_method(D_METHOD("rename_animation", "anim", "newname"), &SpriteFrames::rename_animation); diff --git a/scene/resources/sprite_frames.h b/scene/resources/sprite_frames.h index 0e0bb28d71..8d5b4232cf 100644 --- a/scene/resources/sprite_frames.h +++ b/scene/resources/sprite_frames.h @@ -60,6 +60,7 @@ protected: public: void add_animation(const StringName &p_anim); bool has_animation(const StringName &p_anim) const; + void duplicate_animation(const StringName &p_from, const StringName &p_to); void remove_animation(const StringName &p_anim); void rename_animation(const StringName &p_prev, const StringName &p_next); diff --git a/scene/resources/style_box_flat.cpp b/scene/resources/style_box_flat.cpp index 52d02e92cb..60b91ef0cb 100644 --- a/scene/resources/style_box_flat.cpp +++ b/scene/resources/style_box_flat.cpp @@ -300,8 +300,8 @@ inline void draw_rounded_rectangle(Vector<Vector2> &verts, Vector<int> &indices, const real_t x = radius * (real_t)cos((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.x; const real_t y = radius * (real_t)sin((corner_index + detail / (double)adapted_corner_detail) * (Math_TAU / 4.0) + Math_PI) + corner_point.y; - const float x_skew = -skew.x * (y - ring_rect.get_center().y); - const float y_skew = -skew.y * (x - ring_rect.get_center().x); + const float x_skew = -skew.x * (y - style_rect.get_center().y); + const float y_skew = -skew.y * (x - style_rect.get_center().x); verts.push_back(Vector2(x + x_skew, y + y_skew)); colors.push_back(color); } diff --git a/scene/resources/surface_tool.cpp b/scene/resources/surface_tool.cpp index 3273bc3c00..6921885ee0 100644 --- a/scene/resources/surface_tool.cpp +++ b/scene/resources/surface_tool.cpp @@ -792,7 +792,7 @@ void SurfaceTool::deindex() { } void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, LocalVector<Vertex> *r_vertex, LocalVector<int> *r_index, uint64_t &lformat) { - ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::_create_list() must be a valid object of type Mesh"); + ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::_create_list() must be a valid object of type Mesh"); Array arr = p_existing->surface_get_arrays(p_surface); ERR_FAIL_COND(arr.size() != RS::ARRAY_MAX); @@ -968,7 +968,7 @@ void SurfaceTool::create_from_triangle_arrays(const Array &p_arrays) { } void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { - ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from() must be a valid object of type Mesh"); + ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::create_from() must be a valid object of type Mesh"); clear(); primitive = p_existing->surface_get_primitive_type(p_surface); @@ -983,7 +983,7 @@ void SurfaceTool::create_from(const Ref<Mesh> &p_existing, int p_surface) { } void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_surface, const String &p_blend_shape_name) { - ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::create_from_blend_shape() must be a valid object of type Mesh"); + ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::create_from_blend_shape() must be a valid object of type Mesh"); clear(); primitive = p_existing->surface_get_primitive_type(p_surface); @@ -1023,7 +1023,7 @@ void SurfaceTool::create_from_blend_shape(const Ref<Mesh> &p_existing, int p_sur } void SurfaceTool::append_from(const Ref<Mesh> &p_existing, int p_surface, const Transform3D &p_xform) { - ERR_FAIL_NULL_MSG(p_existing, "First argument in SurfaceTool::append_from() must be a valid object of type Mesh"); + ERR_FAIL_COND_MSG(p_existing.is_null(), "First argument in SurfaceTool::append_from() must be a valid object of type Mesh"); if (vertex_array.size() == 0) { primitive = p_existing->surface_get_primitive_type(p_surface); diff --git a/scene/resources/text_paragraph.cpp b/scene/resources/text_paragraph.cpp index 5da47966dd..29a8541cb0 100644 --- a/scene/resources/text_paragraph.cpp +++ b/scene/resources/text_paragraph.cpp @@ -173,6 +173,7 @@ void TextParagraph::_shape_lines() { v_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; } + Size2i range = TS->shaped_text_get_range(rid); if (h_offset > 0) { // Dropcap, flow around. PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width - h_offset, 0, brk_flags); @@ -182,7 +183,7 @@ void TextParagraph::_shape_lines() { if (!tab_stops.is_empty()) { TS->shaped_text_tab_align(line, tab_stops); } - start = line_breaks[i + 1]; + start = (i < line_breaks.size() - 2) ? line_breaks[i + 2] : range.y; lines_rid.push_back(line); if (v_offset < h) { break; @@ -192,13 +193,15 @@ void TextParagraph::_shape_lines() { } } // Use fixed for the rest of lines. - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags); - for (int i = 0; i < line_breaks.size(); i = i + 2) { - RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); - if (!tab_stops.is_empty()) { - TS->shaped_text_tab_align(line, tab_stops); + if (start == 0 || start < range.y) { + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(rid, width, start, brk_flags); + for (int i = 0; i < line_breaks.size(); i = i + 2) { + RID line = TS->shaped_text_substr(rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); + if (!tab_stops.is_empty()) { + TS->shaped_text_tab_align(line, tab_stops); + } + lines_rid.push_back(line); } - lines_rid.push_back(line); } BitField<TextServer::TextOverrunFlag> overrun_flags = TextServer::OVERRUN_NO_TRIM; @@ -550,18 +553,42 @@ Size2 TextParagraph::get_size() const { _THREAD_SAFE_METHOD_ const_cast<TextParagraph *>(this)->_shape_lines(); + + float h_offset = 0.f; + float v_offset = 0.f; + if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) { + h_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; + v_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y; + } else { + h_offset = TS->shaped_text_get_size(dropcap_rid).y + dropcap_margins.size.y + dropcap_margins.position.y; + v_offset = TS->shaped_text_get_size(dropcap_rid).x + dropcap_margins.size.x + dropcap_margins.position.x; + } + Size2 size; int visible_lines = (max_lines_visible >= 0) ? MIN(max_lines_visible, (int)lines_rid.size()) : (int)lines_rid.size(); for (int i = 0; i < visible_lines; i++) { Size2 lsize = TS->shaped_text_get_size(lines_rid[i]); if (TS->shaped_text_get_orientation(lines_rid[i]) == TextServer::ORIENTATION_HORIZONTAL) { + if (h_offset > 0 && i <= dropcap_lines) { + lsize.x += h_offset; + } size.x = MAX(size.x, lsize.x); size.y += lsize.y; } else { + if (h_offset > 0 && i <= dropcap_lines) { + lsize.y += h_offset; + } size.x += lsize.x; size.y = MAX(size.y, lsize.y); } } + if (h_offset > 0) { + if (TS->shaped_text_get_orientation(dropcap_rid) == TextServer::ORIENTATION_HORIZONTAL) { + size.y = MAX(size.y, v_offset); + } else { + size.x = MAX(size.x, v_offset); + } + } return size; } @@ -624,7 +651,7 @@ Rect2 TextParagraph::get_line_object_rect(int p_line, Variant p_key) const { ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { - ofs.x -= h_offset; + ofs.y -= h_offset; } l_width -= h_offset; } @@ -793,7 +820,7 @@ void TextParagraph::draw(RID p_canvas, const Vector2 &p_pos, const Color &p_colo ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { - ofs.x -= h_offset; + ofs.y -= h_offset; } l_width -= h_offset; } @@ -895,7 +922,7 @@ void TextParagraph::draw_outline(RID p_canvas, const Vector2 &p_pos, int p_outli ofs.x += TS->shaped_text_get_ascent(lines_rid[i]); if (i <= dropcap_lines) { if (TS->shaped_text_get_inferred_direction(dropcap_rid) == TextServer::DIRECTION_LTR) { - ofs.x -= h_offset; + ofs.y -= h_offset; } l_width -= h_offset; } diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index 8a9e784c47..749d4e3530 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -648,7 +648,9 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("buttons_vertical_separation", "SpinBox", 0); theme->set_constant("field_and_buttons_separation", "SpinBox", 2); theme->set_constant("buttons_width", "SpinBox", 16); +#ifndef DISABLE_DEPRECATED theme->set_constant("set_min_buttons_width_from_icons", "SpinBox", 1); +#endif // ScrollContainer diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp index e2c8686911..ece088a694 100644 --- a/servers/audio/audio_stream.cpp +++ b/servers/audio/audio_stream.cpp @@ -173,12 +173,12 @@ int AudioStreamPlaybackResampled::mix(AudioFrame *p_buffer, float p_rate_scale, } float mu2 = mu * mu; - AudioFrame a0 = 3 * y1 - 3 * y2 + y3 - y0; - AudioFrame a1 = 2 * y0 - 5 * y1 + 4 * y2 - y3; - AudioFrame a2 = y2 - y0; - AudioFrame a3 = 2 * y1; + float h11 = mu2 * (mu - 1); + float z = mu2 - h11; + float h01 = z - h11; + float h10 = mu - z; - p_buffer[i] = (a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3) / 2; + p_buffer[i] = y1 + (y2 - y1) * h01 + ((y2 - y0) * h10 + (y3 - y1) * h11) * 0.5; mix_offset += mix_increment; diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp index 54af4738b1..6f358d54bb 100644 --- a/servers/audio/effects/audio_effect_record.cpp +++ b/servers/audio/effects/audio_effect_record.cpp @@ -149,7 +149,7 @@ Ref<AudioEffectInstance> AudioEffectRecord::instantiate() { ensure_thread_stopped(); bool is_currently_recording = false; - if (current_instance != nullptr) { + if (current_instance.is_valid()) { is_currently_recording = current_instance->is_recording; } if (is_currently_recording) { @@ -161,28 +161,28 @@ Ref<AudioEffectInstance> AudioEffectRecord::instantiate() { } void AudioEffectRecord::ensure_thread_stopped() { - if (current_instance != nullptr) { + if (current_instance.is_valid()) { current_instance->finish(); } } void AudioEffectRecord::set_recording_active(bool p_record) { if (p_record) { - if (current_instance == nullptr) { + if (current_instance.is_null()) { WARN_PRINT("Recording should not be set as active before Godot has initialized."); return; } ensure_thread_stopped(); current_instance->init(); } else { - if (current_instance != nullptr) { + if (current_instance.is_valid()) { current_instance->is_recording = false; } } } bool AudioEffectRecord::is_recording_active() const { - if (current_instance == nullptr) { + if (current_instance.is_null()) { return false; } else { return current_instance->is_recording; diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 8cb3e560ac..12456fc828 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -41,6 +41,9 @@ #if defined(D3D12_ENABLED) #include "drivers/d3d12/rendering_context_driver_d3d12.h" #endif +#if defined(METAL_ENABLED) +#include "drivers/metal/rendering_context_driver_metal.h" +#endif DisplayServer *DisplayServer::singleton = nullptr; @@ -1232,6 +1235,15 @@ bool DisplayServer::can_create_rendering_device() { rcd = memnew(RenderingContextDriverD3D12); } #endif +#ifdef METAL_ENABLED + if (rcd == nullptr) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" + // Eliminate "RenderingContextDriverMetal is only available on iOS 14.0 or newer". + rcd = memnew(RenderingContextDriverMetal); +#pragma clang diagnostic pop + } +#endif if (rcd != nullptr) { err = rcd->initialize(); diff --git a/servers/rendering/renderer_rd/effects/taa.cpp b/servers/rendering/renderer_rd/effects/taa.cpp index c1037ec11a..4ac5552aa6 100644 --- a/servers/rendering/renderer_rd/effects/taa.cpp +++ b/servers/rendering/renderer_rd/effects/taa.cpp @@ -62,8 +62,8 @@ void TAA::resolve(RID p_frame, RID p_temp, RID p_depth, RID p_velocity, RID p_pr memset(&push_constant, 0, sizeof(TAAResolvePushConstant)); push_constant.resolution_width = p_resolution.width; push_constant.resolution_height = p_resolution.height; - push_constant.disocclusion_threshold = 0.025f; - push_constant.disocclusion_scale = 10.0f; + push_constant.disocclusion_threshold = 2.5f; // If velocity changes by less than this amount of texels we can retain the accumulation buffer. + push_constant.disocclusion_scale = 0.01f; // Scale the weight of this pixel calculated as (change in velocity - threshold) * scale. RD::ComputeListID compute_list = RD::get_singleton()->compute_list_begin(); RD::get_singleton()->compute_list_bind_compute_pipeline(compute_list, pipeline); diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 5dc67725f1..2087989102 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -545,7 +545,6 @@ void SkyRD::Sky::free() { } if (material.is_valid()) { - RSG::material_storage->material_free(material); material = RID(); } } @@ -1251,7 +1250,7 @@ void SkyRD::update_radiance_buffers(Ref<RenderSceneBuffersRD> p_render_buffers, RS::SkyMode sky_mode = sky->mode; if (sky_mode == RS::SKY_MODE_AUTOMATIC) { - if (shader_data->uses_time || shader_data->uses_position) { + if ((shader_data->uses_time || shader_data->uses_position) && sky->radiance_size == 256) { update_single_frame = true; sky_mode = RS::SKY_MODE_REALTIME; } else if (shader_data->uses_light || shader_data->ubo_size > 0) { diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp index b97e38da4d..36bd22b723 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -2231,7 +2231,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co } RID alpha_framebuffer = rb_data.is_valid() ? rb_data->get_color_pass_fb(transparent_color_pass_flags) : color_only_framebuffer; - RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), false, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags); + RenderListParameters render_list_params(render_list[RENDER_LIST_ALPHA].elements.ptr(), render_list[RENDER_LIST_ALPHA].element_info.ptr(), render_list[RENDER_LIST_ALPHA].elements.size(), reverse_cull, PASS_MODE_COLOR, transparent_color_pass_flags, rb_data.is_null(), p_render_data->directional_light_soft_shadows, rp_uniform_set, get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME, Vector2(), p_render_data->scene_data->lod_distance_multiplier, p_render_data->scene_data->screen_mesh_lod_threshold, p_render_data->scene_data->view_count, 0, spec_constant_base_flags); _render_list_with_draw_list(&render_list_params, alpha_framebuffer, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE, RD::INITIAL_ACTION_LOAD, RD::FINAL_ACTION_STORE); } diff --git a/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl b/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl index e50397cc5d..00ac6a7919 100644 --- a/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/taa_resolve.glsl @@ -307,6 +307,8 @@ float luminance(vec3 color) { return max(dot(color, lumCoeff), 0.0001f); } +// This is "velocity disocclusion" as described by https://www.elopezr.com/temporal-aa-and-the-quest-for-the-holy-trail/. +// We use texel space, so our scale and threshold differ. float get_factor_disocclusion(vec2 uv_reprojected, vec2 velocity) { vec2 velocity_previous = imageLoad(last_velocity_buffer, ivec2(uv_reprojected * params.resolution)).xy; vec2 velocity_texels = velocity * params.resolution; @@ -336,7 +338,7 @@ vec3 temporal_antialiasing(uvec2 pos_group_top_left, uvec2 pos_group, uvec2 pos_ // Compute blend factor float blend_factor = RPC_16; // We want to be able to accumulate as many jitter samples as we generated, that is, 16. { - // If re-projected UV is out of screen, converge to current color immediatel + // If re-projected UV is out of screen, converge to current color immediately. float factor_screen = any(lessThan(uv_reprojected, vec2(0.0))) || any(greaterThan(uv_reprojected, vec2(1.0))) ? 1.0 : 0.0; // Increase blend factor when there is disocclusion (fixes a lot of the remaining ghosting). diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl index 35d29fed6a..cfde97af95 100644 --- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl @@ -1513,7 +1513,6 @@ void fragment_shader(in SceneData scene_data) { if (uses_sh) { uvw.z *= 4.0; //SH textures use 4 times more data - vec3 lm_light_l0; vec3 lm_light_l1n1; vec3 lm_light_l1_0; @@ -1521,23 +1520,23 @@ void fragment_shader(in SceneData scene_data) { if (sc_use_lightmap_bicubic_filter) { lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb; - lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb; - lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb; - lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb; + lm_light_l1n1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0; + lm_light_l1_0 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0; + lm_light_l1p1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0; } else { lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb; - lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb; - lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb; - lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb; + lm_light_l1n1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb - vec3(0.5)) * 2.0; + lm_light_l1_0 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb - vec3(0.5)) * 2.0; + lm_light_l1p1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb - vec3(0.5)) * 2.0; } vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal); float en = lightmaps.data[ofs].exposure_normalization; ambient_light += lm_light_l0 * en; - ambient_light += lm_light_l1n1 * n.y * en; - ambient_light += lm_light_l1_0 * n.z * en; - ambient_light += lm_light_l1p1 * n.x * en; + ambient_light += lm_light_l1n1 * n.y * (lm_light_l0 * en * 4.0); + ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * en * 4.0); + ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * en * 4.0); } else { if (sc_use_lightmap_bicubic_filter) { @@ -2065,7 +2064,7 @@ void fragment_shader(in SceneData scene_data) { #ifdef LIGHT_TRANSMITTANCE_USED float transmittance_z = transmittance_depth; - +#ifndef SHADOWS_DISABLED if (directional_lights.data[i].shadow_opacity > 0.001) { float depth_z = -vertex.z; @@ -2112,7 +2111,8 @@ void fragment_shader(in SceneData scene_data) { transmittance_z = z - shadow_z; } } -#endif +#endif // !SHADOWS_DISABLED +#endif // LIGHT_TRANSMITTANCE_USED float shadow = 1.0; #ifndef SHADOWS_DISABLED diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl index c266161834..b21769f207 100644 --- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl @@ -1280,23 +1280,23 @@ void main() { if (sc_use_lightmap_bicubic_filter) { lm_light_l0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 0.0), lightmaps.data[ofs].light_texture_size).rgb; - lm_light_l1n1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb; - lm_light_l1_0 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb; - lm_light_l1p1 = textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb; + lm_light_l1n1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 1.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0; + lm_light_l1_0 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 2.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0; + lm_light_l1p1 = (textureArray_bicubic(lightmap_textures[ofs], uvw + vec3(0.0, 0.0, 3.0), lightmaps.data[ofs].light_texture_size).rgb - vec3(0.5)) * 2.0; } else { lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb; - lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb; - lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb; - lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb; + lm_light_l1n1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb - vec3(0.5)) * 2.0; + lm_light_l1_0 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb - vec3(0.5)) * 2.0; + lm_light_l1p1 = (textureLod(sampler2DArray(lightmap_textures[ofs], SAMPLER_LINEAR_CLAMP), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb - vec3(0.5)) * 2.0; } vec3 n = normalize(lightmaps.data[ofs].normal_xform * normal); float exposure_normalization = lightmaps.data[ofs].exposure_normalization; ambient_light += lm_light_l0 * exposure_normalization; - ambient_light += lm_light_l1n1 * n.y * exposure_normalization; - ambient_light += lm_light_l1_0 * n.z * exposure_normalization; - ambient_light += lm_light_l1p1 * n.x * exposure_normalization; + ambient_light += lm_light_l1n1 * n.y * (lm_light_l0 * exposure_normalization * 4.0); + ambient_light += lm_light_l1_0 * n.z * (lm_light_l0 * exposure_normalization * 4.0); + ambient_light += lm_light_l1p1 * n.x * (lm_light_l0 * exposure_normalization * 4.0); } else { if (sc_use_lightmap_bicubic_filter) { ambient_light += textureArray_bicubic(lightmap_textures[ofs], uvw, lightmaps.data[ofs].light_texture_size).rgb * lightmaps.data[ofs].exposure_normalization; diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl index 40ca74ae07..748fb59531 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_lights_inc.glsl @@ -582,34 +582,39 @@ void light_process_omni(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v #ifdef LIGHT_TRANSMITTANCE_USED float transmittance_z = transmittance_depth; //no transmittance by default transmittance_color.a *= light_attenuation; - { - vec4 clamp_rect = omni_lights.data[idx].atlas_rect; +#ifndef SHADOWS_DISABLED + if (omni_lights.data[idx].shadow_opacity > 0.001) { + // Redo shadowmapping, but shrink the model a bit to avoid artifacts. + vec2 texel_size = scene_data_block.data.shadow_atlas_pixel_size; + vec4 uv_rect = omni_lights.data[idx].atlas_rect; + uv_rect.xy += texel_size; + uv_rect.zw -= texel_size * 2.0; - //redo shadowmapping, but shrink the model a bit to avoid artifacts - vec4 splane = (omni_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * omni_lights.data[idx].transmittance_bias, 1.0)); + // Omni lights use direction.xy to store to store the offset between the two paraboloid regions + vec2 flip_offset = omni_lights.data[idx].direction.xy; - float shadow_len = length(splane.xyz); - splane.xyz = normalize(splane.xyz); + vec3 local_vert = (omni_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal) * omni_lights.data[idx].transmittance_bias, 1.0)).xyz; - if (splane.z >= 0.0) { - splane.z += 1.0; - clamp_rect.y += clamp_rect.w; - } else { - splane.z = 1.0 - splane.z; - } + float shadow_len = length(local_vert); //need to remember shadow len from here + vec3 shadow_sample = normalize(local_vert); - splane.xy /= splane.z; + if (shadow_sample.z >= 0.0) { + uv_rect.xy += flip_offset; + flip_offset *= -1.0; + } - splane.xy = splane.xy * 0.5 + 0.5; - splane.z = shadow_len * omni_lights.data[idx].inv_radius; - splane.xy = clamp_rect.xy + splane.xy * clamp_rect.zw; - // splane.xy = clamp(splane.xy,clamp_rect.xy + scene_data_block.data.shadow_atlas_pixel_size,clamp_rect.xy + clamp_rect.zw - scene_data_block.data.shadow_atlas_pixel_size ); - splane.w = 1.0; //needed? i think it should be 1 already + shadow_sample.z = 1.0 + abs(shadow_sample.z); + vec2 pos = shadow_sample.xy / shadow_sample.z; + float depth = shadow_len * omni_lights.data[idx].inv_radius; + depth = 1.0 - depth; - float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), splane.xy, 0.0).r; - transmittance_z = (splane.z - shadow_z) / omni_lights.data[idx].inv_radius; + pos = pos * 0.5 + 0.5; + pos = uv_rect.xy + pos * uv_rect.zw; + float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), pos, 0.0).r; + transmittance_z = (depth - shadow_z) / omni_lights.data[idx].inv_radius; } -#endif +#endif // !SHADOWS_DISABLED +#endif // LIGHT_TRANSMITTANCE_USED if (sc_use_light_projector && omni_lights.data[idx].projector_rect != vec4(0.0)) { vec3 local_v = (omni_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)).xyz; @@ -834,12 +839,13 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v #ifdef LIGHT_TRANSMITTANCE_USED float transmittance_z = transmittance_depth; transmittance_color.a *= light_attenuation; - { - vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal_interp) * spot_lights.data[idx].transmittance_bias, 1.0)); +#ifndef SHADOWS_DISABLED + if (spot_lights.data[idx].shadow_opacity > 0.001) { + vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex - normalize(normal) * spot_lights.data[idx].transmittance_bias, 1.0)); splane /= splane.w; - splane.xy = splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy; - float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), splane.xy, 0.0).r; + vec3 shadow_uv = vec3(splane.xy * spot_lights.data[idx].atlas_rect.zw + spot_lights.data[idx].atlas_rect.xy, splane.z); + float shadow_z = textureLod(sampler2D(shadow_atlas, SAMPLER_LINEAR_CLAMP), shadow_uv.xy, 0.0).r; shadow_z = shadow_z * 2.0 - 1.0; float z_far = 1.0 / spot_lights.data[idx].inv_radius; @@ -850,7 +856,8 @@ void light_process_spot(uint idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 v float z = dot(spot_dir, -light_rel_vec); transmittance_z = z - shadow_z; } -#endif //LIGHT_TRANSMITTANCE_USED +#endif // !SHADOWS_DISABLED +#endif // LIGHT_TRANSMITTANCE_USED if (sc_use_light_projector && spot_lights.data[idx].projector_rect != vec4(0.0)) { vec4 splane = (spot_lights.data[idx].shadow_matrix * vec4(vertex, 1.0)); @@ -933,7 +940,7 @@ void reflection_process(uint ref_index, vec3 vertex, vec3 ref_vec, vec3 normal, vec4 reflection; - reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_ref_vec, reflections.data[ref_index].index), roughness * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier; + reflection.rgb = textureLod(samplerCubeArray(reflection_atlas, DEFAULT_SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP), vec4(local_ref_vec, reflections.data[ref_index].index), sqrt(roughness) * MAX_ROUGHNESS_LOD).rgb * sc_luminance_multiplier; reflection.rgb *= reflections.data[ref_index].exposure_normalization; if (reflections.data[ref_index].exterior) { reflection.rgb = mix(specular_light, reflection.rgb, blend); diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp index c217c0fa9a..b07063cfda 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.cpp @@ -685,7 +685,7 @@ void LightStorage::update_light_buffers(RenderDataRD *p_render_data, const Paged float bias_scale = light_instance->shadow_transform[j].bias_scale * light_data.soft_shadow_scale; light_data.shadow_bias[j] = light->param[RS::LIGHT_PARAM_SHADOW_BIAS] / 100.0 * bias_scale; light_data.shadow_normal_bias[j] = light->param[RS::LIGHT_PARAM_SHADOW_NORMAL_BIAS] * light_instance->shadow_transform[j].shadow_texel_size; - light_data.shadow_transmittance_bias[j] = light->param[RS::LIGHT_PARAM_TRANSMITTANCE_BIAS] * bias_scale; + light_data.shadow_transmittance_bias[j] = light->param[RS::LIGHT_PARAM_TRANSMITTANCE_BIAS] / 100.0 * bias_scale; light_data.shadow_z_range[j] = light_instance->shadow_transform[j].farplane; light_data.shadow_range_begin[j] = light_instance->shadow_transform[j].range_begin; RendererRD::MaterialStorage::store_camera(shadow_mtx, light_data.shadow_matrices[j]); diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 539bdcbbd0..9bd62ba065 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -783,6 +783,7 @@ String MeshStorage::mesh_get_path(RID p_mesh) const { } void MeshStorage::mesh_set_shadow_mesh(RID p_mesh, RID p_shadow_mesh) { + ERR_FAIL_COND_MSG(p_mesh == p_shadow_mesh, "Cannot set a mesh as its own shadow mesh."); Mesh *mesh = mesh_owner.get_or_null(p_mesh); ERR_FAIL_NULL(mesh); diff --git a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp index 6e5e8f63e0..be29716f45 100644 --- a/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/texture_storage.cpp @@ -1470,8 +1470,24 @@ void TextureStorage::texture_debug_usage(List<RS::TextureInfo> *r_info) { tinfo.format = t->format; tinfo.width = t->width; tinfo.height = t->height; - tinfo.depth = t->depth; - tinfo.bytes = Image::get_image_data_size(t->width, t->height, t->format, t->mipmaps); + tinfo.bytes = Image::get_image_data_size(t->width, t->height, t->format, t->mipmaps > 1); + + switch (t->type) { + case TextureType::TYPE_3D: + tinfo.depth = t->depth; + tinfo.bytes *= t->depth; + break; + + case TextureType::TYPE_LAYERED: + tinfo.depth = t->layers; + tinfo.bytes *= t->layers; + break; + + default: + tinfo.depth = 0; + break; + } + r_info->push_back(tinfo); } } diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp index 1d25dec633..286944641c 100644 --- a/servers/rendering/renderer_scene_cull.cpp +++ b/servers/rendering/renderer_scene_cull.cpp @@ -1783,6 +1783,8 @@ void RendererSceneCull::_update_instance(Instance *p_instance) { if (p_instance->scenario) { RendererSceneOcclusionCull::get_singleton()->scenario_set_instance(p_instance->scenario->self, p_instance->self, p_instance->base, *instance_xform, p_instance->visible); } + } else if (p_instance->base_type == RS::INSTANCE_NONE) { + return; } if (!p_instance->aabb.has_surface()) { diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 92f0f0dbc0..1848d5602e 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -3263,6 +3263,7 @@ void RenderingServer::_bind_methods() { ClassDB::bind_method(D_METHOD("canvas_item_set_z_index", "item", "z_index"), &RenderingServer::canvas_item_set_z_index); ClassDB::bind_method(D_METHOD("canvas_item_set_z_as_relative_to_parent", "item", "enabled"), &RenderingServer::canvas_item_set_z_as_relative_to_parent); ClassDB::bind_method(D_METHOD("canvas_item_set_copy_to_backbuffer", "item", "enabled", "rect"), &RenderingServer::canvas_item_set_copy_to_backbuffer); + ClassDB::bind_method(D_METHOD("canvas_item_attach_skeleton", "item", "skeleton"), &RenderingServer::canvas_item_attach_skeleton); ClassDB::bind_method(D_METHOD("canvas_item_clear", "item"), &RenderingServer::canvas_item_clear); ClassDB::bind_method(D_METHOD("canvas_item_set_draw_index", "item", "index"), &RenderingServer::canvas_item_set_draw_index); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index d8b6651833..62ca6b3b6d 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -176,7 +176,7 @@ public: uint32_t height; uint32_t depth; Image::Format format; - int bytes; + int64_t bytes; String path; }; diff --git a/servers/text_server.cpp b/servers/text_server.cpp index 860cc5d75d..d2cf4674ae 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -1988,7 +1988,7 @@ TypedArray<Vector3i> TextServer::parse_structured_text(StructuredTextParser p_pa } } break; case STRUCTURED_TEXT_LIST: { - if (p_args.size() == 1 && p_args[0].get_type() == Variant::STRING) { + if (p_args.size() == 1 && p_args[0].is_string()) { Vector<String> tags = p_text.split(String(p_args[0])); int prev = 0; for (int i = 0; i < tags.size(); i++) { @@ -2071,8 +2071,8 @@ TypedArray<Vector3i> TextServer::parse_structured_text(StructuredTextParser p_pa if (prev != i) { ret.push_back(Vector3i(prev, i, TextServer::DIRECTION_AUTO)); } - prev = i + 1; - ret.push_back(Vector3i(i, i + 1, TextServer::DIRECTION_LTR)); + prev = p_text.length(); + ret.push_back(Vector3i(i, p_text.length(), TextServer::DIRECTION_AUTO)); break; } } diff --git a/servers/xr_server.cpp b/servers/xr_server.cpp index a4e68afee0..4c4781fdef 100644 --- a/servers/xr_server.cpp +++ b/servers/xr_server.cpp @@ -183,7 +183,7 @@ Transform3D XRServer::get_reference_frame() const { } void XRServer::center_on_hmd(RotationMode p_rotation_mode, bool p_keep_height) { - if (primary_interface == nullptr) { + if (primary_interface.is_null()) { return; } @@ -235,7 +235,7 @@ void XRServer::_set_render_reference_frame(const Transform3D &p_reference_frame) Transform3D XRServer::get_hmd_transform() { Transform3D hmd_transform; - if (primary_interface != nullptr) { + if (primary_interface.is_valid()) { hmd_transform = primary_interface->get_camera_transform(); } return hmd_transform; diff --git a/tests/core/io/test_http_client.h b/tests/core/io/test_http_client.h index 961c653a0a..114ce3b4ed 100644 --- a/tests/core/io/test_http_client.h +++ b/tests/core/io/test_http_client.h @@ -41,7 +41,7 @@ namespace TestHTTPClient { TEST_CASE("[HTTPClient] Instantiation") { Ref<HTTPClient> client = HTTPClient::create(); - CHECK_MESSAGE(client != nullptr, "A HTTP Client created should not be a null pointer"); + CHECK_MESSAGE(client.is_valid(), "A HTTP Client created should not be a null pointer"); } TEST_CASE("[HTTPClient] query_string_from_dict") { diff --git a/tests/core/variant/test_dictionary.h b/tests/core/variant/test_dictionary.h index aba20972d9..48a48f6ca6 100644 --- a/tests/core/variant/test_dictionary.h +++ b/tests/core/variant/test_dictionary.h @@ -31,7 +31,7 @@ #ifndef TEST_DICTIONARY_H #define TEST_DICTIONARY_H -#include "core/variant/dictionary.h" +#include "core/variant/typed_dictionary.h" #include "tests/test_macros.h" namespace TestDictionary { @@ -66,8 +66,7 @@ TEST_CASE("[Dictionary] Assignment using bracket notation ([])") { map[StringName("HelloName")] = 6; CHECK(int(map[StringName("HelloName")]) == 6); - // Check that StringName key is converted to String. - CHECK(int(map.find_key(6).get_type()) == Variant::STRING); + CHECK(int(map.find_key(6).get_type()) == Variant::STRING_NAME); map[StringName("HelloName")] = 7; CHECK(int(map[StringName("HelloName")]) == 7); @@ -537,6 +536,43 @@ TEST_CASE("[Dictionary] Order and find") { CHECK_EQ(d.find_key("does not exist"), Variant()); } +TEST_CASE("[Dictionary] Typed copying") { + TypedDictionary<int, int> d1; + d1[0] = 1; + + TypedDictionary<double, double> d2; + d2[0] = 1.0; + + Dictionary d3 = d1; + TypedDictionary<int, int> d4 = d3; + + Dictionary d5 = d2; + TypedDictionary<int, int> d6 = d5; + + d3[0] = 2; + d4[0] = 3; + + // Same typed TypedDictionary should be shared. + CHECK_EQ(d1[0], Variant(3)); + CHECK_EQ(d3[0], Variant(3)); + CHECK_EQ(d4[0], Variant(3)); + + d5[0] = 2.0; + d6[0] = 3.0; + + // Different typed TypedDictionary should not be shared. + CHECK_EQ(d2[0], Variant(2.0)); + CHECK_EQ(d5[0], Variant(2.0)); + CHECK_EQ(d6[0], Variant(3.0)); + + d1.clear(); + d2.clear(); + d3.clear(); + d4.clear(); + d5.clear(); + d6.clear(); +} + } // namespace TestDictionary #endif // TEST_DICTIONARY_H diff --git a/tests/create_test.py b/tests/create_test.py index deb53aca20..ad6d6b882f 100755 --- a/tests/create_test.py +++ b/tests/create_test.py @@ -13,12 +13,16 @@ def main(): os.chdir(os.path.dirname(os.path.realpath(__file__))) parser = argparse.ArgumentParser(description="Creates a new unit test file.") - parser.add_argument("name", type=str, help="The unit test name in PascalCase notation") + parser.add_argument( + "name", + type=str, + help="Specifies the class or component name to be tested, in PascalCase (e.g., MeshInstance3D). The name will be prefixed with 'test_' for the header file and 'Test' for the namespace.", + ) parser.add_argument( "path", type=str, nargs="?", - help="The path to the unit test file relative to the tests folder (default: .)", + help="The path to the unit test file relative to the tests folder (e.g. core). This should correspond to the relative path of the class or component being tested. (default: .)", default=".", ) parser.add_argument( @@ -29,9 +33,10 @@ def main(): ) args = parser.parse_args() - snake_case_regex = re.compile(r"(?<!^)(?=[A-Z])") - name_snake_case = snake_case_regex.sub("_", args.name).lower() - + snake_case_regex = re.compile(r"(?<!^)(?=[A-Z, 0-9])") + # Replace 2D, 3D, and 4D with 2d, 3d, and 4d, respectively. This avoids undesired splits like node_3_d. + prefiltered_name = re.sub(r"([234])D", lambda match: match.group(1).lower() + "d", args.name) + name_snake_case = snake_case_regex.sub("_", prefiltered_name).lower() file_path = os.path.normpath(os.path.join(args.path, f"test_{name_snake_case}.h")) print(file_path) diff --git a/tests/scene/test_audio_stream_wav.h b/tests/scene/test_audio_stream_wav.h index e8f3c9e8f5..d3d5cc8a30 100644 --- a/tests/scene/test_audio_stream_wav.h +++ b/tests/scene/test_audio_stream_wav.h @@ -159,6 +159,8 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo, for (const ResourceImporter::ImportOption &E : options_list) { options_map[E.option.name] = E.default_value; } + // Compressed streams can't be saved, disable compression. + options_map["compress/mode"] = 0; REQUIRE(wav_importer->import(save_path, save_path, options_map, nullptr) == OK); @@ -179,27 +181,27 @@ void run_test(String file_name, AudioStreamWAV::Format data_format, bool stereo, } } -TEST_CASE("[AudioStreamWAV] Mono PCM8 format") { +TEST_CASE("[Audio][AudioStreamWAV] Mono PCM8 format") { run_test("test_pcm8_mono.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, WAV_COUNT); } -TEST_CASE("[AudioStreamWAV] Mono PCM16 format") { +TEST_CASE("[Audio][AudioStreamWAV] Mono PCM16 format") { run_test("test_pcm16_mono.wav", AudioStreamWAV::FORMAT_16_BITS, false, WAV_RATE, WAV_COUNT); } -TEST_CASE("[AudioStreamWAV] Stereo PCM8 format") { +TEST_CASE("[Audio][AudioStreamWAV] Stereo PCM8 format") { run_test("test_pcm8_stereo.wav", AudioStreamWAV::FORMAT_8_BITS, true, WAV_RATE, WAV_COUNT); } -TEST_CASE("[AudioStreamWAV] Stereo PCM16 format") { +TEST_CASE("[Audio][AudioStreamWAV] Stereo PCM16 format") { run_test("test_pcm16_stereo.wav", AudioStreamWAV::FORMAT_16_BITS, true, WAV_RATE, WAV_COUNT); } -TEST_CASE("[AudioStreamWAV] Alternate mix rate") { +TEST_CASE("[Audio][AudioStreamWAV] Alternate mix rate") { run_test("test_pcm16_stereo_38000Hz.wav", AudioStreamWAV::FORMAT_16_BITS, true, 38000, 38000); } -TEST_CASE("[AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") { +TEST_CASE("[Audio][AudioStreamWAV] save_to_wav() adds '.wav' file extension automatically") { String save_path = TestUtils::get_temp_path("test_wav_extension"); Vector<uint8_t> test_data = gen_pcm8_test(WAV_RATE, WAV_COUNT, false); Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV); @@ -211,7 +213,7 @@ TEST_CASE("[AudioStreamWAV] save_to_wav() adds '.wav' file extension automatical CHECK(error == OK); } -TEST_CASE("[AudioStreamWAV] Default values") { +TEST_CASE("[Audio][AudioStreamWAV] Default values") { Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV); CHECK(stream->get_format() == AudioStreamWAV::FORMAT_8_BITS); CHECK(stream->get_loop_mode() == AudioStreamWAV::LOOP_DISABLED); @@ -225,11 +227,11 @@ TEST_CASE("[AudioStreamWAV] Default values") { CHECK(stream->get_stream_name() == ""); } -TEST_CASE("[AudioStreamWAV] Save empty file") { +TEST_CASE("[Audio][AudioStreamWAV] Save empty file") { run_test("test_empty.wav", AudioStreamWAV::FORMAT_8_BITS, false, WAV_RATE, 0); } -TEST_CASE("[AudioStreamWAV] Saving IMA ADPCM is not supported") { +TEST_CASE("[Audio][AudioStreamWAV] Saving IMA ADPCM is not supported") { String save_path = TestUtils::get_temp_path("test_adpcm.wav"); Ref<AudioStreamWAV> stream = memnew(AudioStreamWAV); stream->set_format(AudioStreamWAV::FORMAT_IMA_ADPCM); diff --git a/tests/scene/test_height_map_shape_3d.h b/tests/scene/test_height_map_shape_3d.h new file mode 100644 index 0000000000..60a8db4e58 --- /dev/null +++ b/tests/scene/test_height_map_shape_3d.h @@ -0,0 +1,122 @@ +/**************************************************************************/ +/* test_height_map_shape_3d.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 TEST_HEIGHT_MAP_SHAPE_3D_H +#define TEST_HEIGHT_MAP_SHAPE_3D_H + +#include "scene/resources/3d/height_map_shape_3d.h" +#include "scene/resources/image_texture.h" + +#include "tests/test_macros.h" +#include "tests/test_utils.h" + +namespace TestHeightMapShape3D { + +TEST_CASE("[SceneTree][HeightMapShape3D] Constructor") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + CHECK(height_map_shape->get_map_width() == 2); + CHECK(height_map_shape->get_map_depth() == 2); + CHECK(height_map_shape->get_map_data().size() == 4); + CHECK(height_map_shape->get_min_height() == 0.0); + CHECK(height_map_shape->get_max_height() == 0.0); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] set_map_width and get_map_width") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + height_map_shape->set_map_width(10); + CHECK(height_map_shape->get_map_width() == 10); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] set_map_depth and get_map_depth") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + height_map_shape->set_map_depth(15); + CHECK(height_map_shape->get_map_depth() == 15); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] set_map_data and get_map_data") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + Vector<real_t> map_data; + map_data.push_back(1.0); + map_data.push_back(2.0); + height_map_shape->set_map_data(map_data); + CHECK(height_map_shape->get_map_data().size() == 4.0); + CHECK(height_map_shape->get_map_data()[0] == 0.0); + CHECK(height_map_shape->get_map_data()[1] == 0.0); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] get_min_height") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + height_map_shape->set_map_width(3); + height_map_shape->set_map_depth(1); + height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 }); + CHECK(height_map_shape->get_min_height() == 0.5); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] get_max_height") { + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + height_map_shape->set_map_width(3); + height_map_shape->set_map_depth(1); + height_map_shape->set_map_data(Vector<real_t>{ 1.0, 2.0, 0.5 }); + CHECK(height_map_shape->get_max_height() == 2.0); +} + +TEST_CASE("[SceneTree][HeightMapShape3D] update_map_data_from_image") { + // Create a HeightMapShape3D instance. + Ref<HeightMapShape3D> height_map_shape = memnew(HeightMapShape3D); + + // Create a mock image with FORMAT_R8 and set its data. + Vector<uint8_t> image_data; + image_data.push_back(0); + image_data.push_back(128); + image_data.push_back(255); + image_data.push_back(64); + + Ref<Image> image = memnew(Image); + image->set_data(2, 2, false, Image::FORMAT_R8, image_data); + + height_map_shape->update_map_data_from_image(image, 0.0, 10.0); + + // Check the map data. + Vector<real_t> expected_map_data = { 0.0, 5.0, 10.0, 2.5 }; + Vector<real_t> actual_map_data = height_map_shape->get_map_data(); + real_t tolerance = 0.1; + + for (int i = 0; i < expected_map_data.size(); ++i) { + CHECK(Math::abs(actual_map_data[i] - expected_map_data[i]) < tolerance); + } + + // Check the min and max heights. + CHECK(height_map_shape->get_min_height() == 0.0); + CHECK(height_map_shape->get_max_height() == 10.0); +} + +} // namespace TestHeightMapShape3D + +#endif // TEST_HEIGHT_MAP_SHAPE_3D_H diff --git a/tests/scene/test_parallax_2d.h b/tests/scene/test_parallax_2d.h new file mode 100644 index 0000000000..2fdbf09e09 --- /dev/null +++ b/tests/scene/test_parallax_2d.h @@ -0,0 +1,131 @@ +/**************************************************************************/ +/* test_parallax_2d.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 TEST_PARALLAX_2D_H +#define TEST_PARALLAX_2D_H + +#include "scene/2d/parallax_2d.h" +#include "tests/test_macros.h" + +namespace TestParallax2D { + +// Test cases for the Parallax2D class to ensure its properties are set and retrieved correctly. + +TEST_CASE("[SceneTree][Parallax2D] Scroll Scale") { + // Test setting and getting the scroll scale. + Parallax2D *parallax = memnew(Parallax2D); + Size2 scale(2, 2); + parallax->set_scroll_scale(scale); + CHECK(parallax->get_scroll_scale() == scale); + memdelete(parallax); +} + +TEST_CASE("[SceneTree][Parallax2D] Repeat Size") { + // Test setting and getting the repeat size. + Parallax2D *parallax = memnew(Parallax2D); + Size2 size(100, 100); + parallax->set_repeat_size(size); + CHECK(parallax->get_repeat_size() == size); + memdelete(parallax); +} + +TEST_CASE("[SceneTree][Parallax2D] Repeat Times") { + // Test setting and getting the repeat times. + Parallax2D *parallax = memnew(Parallax2D); + int times = 5; + parallax->set_repeat_times(times); + CHECK(parallax->get_repeat_times() == times); + memdelete(parallax); +} + +TEST_CASE("[SceneTree][Parallax2D] Autoscroll") { + // Test setting and getting the autoscroll values. + Parallax2D *parallax = memnew(Parallax2D); + Point2 autoscroll(1, 1); + parallax->set_autoscroll(autoscroll); + CHECK(parallax->get_autoscroll() == autoscroll); + memdelete(parallax); +} + +TEST_CASE("[SceneTree][Parallax2D] Scroll Offset") { + // Test setting and getting the scroll offset. + Parallax2D *parallax = memnew(Parallax2D); + Point2 offset(10, 10); + parallax->set_scroll_offset(offset); + CHECK(parallax->get_scroll_offset() == offset); + memdelete(parallax); +} + +TEST_CASE("[SceneTree][Parallax2D] Screen Offset") { + // Test setting and getting the screen offset. + Parallax2D *parallax = memnew(Parallax2D); + Point2 offset(20, 20); + parallax->set_screen_offset(offset); + CHECK(parallax->get_screen_offset() == offset); + memdelete(parallax); +} + +TEST_CASE("[SceneTree][Parallax2D] Limit Begin") { + // Test setting and getting the limit begin values. + Parallax2D *parallax = memnew(Parallax2D); + Point2 limit_begin(-100, -100); + parallax->set_limit_begin(limit_begin); + CHECK(parallax->get_limit_begin() == limit_begin); + memdelete(parallax); +} + +TEST_CASE("[SceneTree][Parallax2D] Limit End") { + // Test setting and getting the limit end values. + Parallax2D *parallax = memnew(Parallax2D); + Point2 limit_end(100, 100); + parallax->set_limit_end(limit_end); + CHECK(parallax->get_limit_end() == limit_end); + memdelete(parallax); +} + +TEST_CASE("[SceneTree][Parallax2D] Follow Viewport") { + // Test setting and getting the follow viewport flag. + Parallax2D *parallax = memnew(Parallax2D); + parallax->set_follow_viewport(false); + CHECK_FALSE(parallax->get_follow_viewport()); + memdelete(parallax); +} + +TEST_CASE("[SceneTree][Parallax2D] Ignore Camera Scroll") { + // Test setting and getting the ignore camera scroll flag. + Parallax2D *parallax = memnew(Parallax2D); + parallax->set_ignore_camera_scroll(true); + CHECK(parallax->is_ignore_camera_scroll()); + memdelete(parallax); +} + +} // namespace TestParallax2D + +#endif // TEST_PARALLAX_2D_H diff --git a/tests/scene/test_path_2d.h b/tests/scene/test_path_2d.h index 7b6cec96db..4703bfa3bb 100644 --- a/tests/scene/test_path_2d.h +++ b/tests/scene/test_path_2d.h @@ -40,7 +40,7 @@ namespace TestPath2D { TEST_CASE("[SceneTree][Path2D] Initialization") { SUBCASE("Path should be empty right after initialization") { Path2D *test_path = memnew(Path2D); - CHECK(test_path->get_curve() == nullptr); + CHECK(test_path->get_curve().is_null()); memdelete(test_path); } } diff --git a/tests/scene/test_path_3d.h b/tests/scene/test_path_3d.h index f779f514a4..70c7099d48 100644 --- a/tests/scene/test_path_3d.h +++ b/tests/scene/test_path_3d.h @@ -40,7 +40,7 @@ namespace TestPath3D { TEST_CASE("[Path3D] Initialization") { SUBCASE("Path should be empty right after initialization") { Path3D *test_path = memnew(Path3D); - CHECK(test_path->get_curve() == nullptr); + CHECK(test_path->get_curve().is_null()); memdelete(test_path); } } diff --git a/tests/scene/test_primitives.h b/tests/scene/test_primitives.h index f105e1ac04..59f23983e5 100644 --- a/tests/scene/test_primitives.h +++ b/tests/scene/test_primitives.h @@ -609,7 +609,7 @@ TEST_CASE("[SceneTree][Primitive][TubeTrail] TubeTrail Primitive") { CHECK(tube->get_sections() >= 0); CHECK(tube->get_section_length() > 0); CHECK(tube->get_section_rings() >= 0); - CHECK(tube->get_curve() == nullptr); + CHECK(tube->get_curve().is_null()); CHECK(tube->get_builtin_bind_pose_count() >= 0); } @@ -669,7 +669,7 @@ TEST_CASE("[SceneTree][Primitive][RibbonTrail] RibbonTrail Primitive") { CHECK(ribbon->get_section_length() > 0); CHECK(ribbon->get_section_segments() >= 0); CHECK(ribbon->get_builtin_bind_pose_count() >= 0); - CHECK(ribbon->get_curve() == nullptr); + CHECK(ribbon->get_curve().is_null()); CHECK((ribbon->get_shape() == RibbonTrailMesh::SHAPE_CROSS || ribbon->get_shape() == RibbonTrailMesh::SHAPE_FLAT)); } @@ -731,7 +731,7 @@ TEST_CASE("[SceneTree][Primitive][Text] Text Primitive") { text->get_vertical_alignment() == VERTICAL_ALIGNMENT_TOP || text->get_vertical_alignment() == VERTICAL_ALIGNMENT_CENTER || text->get_vertical_alignment() == VERTICAL_ALIGNMENT_FILL)); - CHECK(text->get_font() == nullptr); + CHECK(text->get_font().is_null()); CHECK(text->get_font_size() > 0); CHECK(text->get_line_spacing() >= 0); CHECK((text->get_autowrap_mode() == TextServer::AUTOWRAP_OFF || diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h index 69e27fe7a0..46a5046b21 100644 --- a/tests/scene/test_text_edit.h +++ b/tests/scene/test_text_edit.h @@ -4232,6 +4232,18 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_line(0) == 0); CHECK(text_edit->get_caret_column(0) == 4); text_edit->remove_secondary_carets(); + + // Remove when there are no words, only symbols. + text_edit->set_text("#{}"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(3); + + SEND_GUI_ACTION("ui_text_backspace_word"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_text() == ""); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); } SUBCASE("[TextEdit] ui_text_backspace_word same line") { @@ -4891,6 +4903,18 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { CHECK(text_edit->get_caret_line(0) == 0); CHECK(text_edit->get_caret_column(0) == 2); text_edit->remove_secondary_carets(); + + // Remove when there are no words, only symbols. + text_edit->set_text("#{}"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + + SEND_GUI_ACTION("ui_text_delete_word"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK_FALSE(text_edit->has_selection()); + CHECK(text_edit->get_text() == ""); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); } SUBCASE("[TextEdit] ui_text_delete_word same line") { @@ -5301,6 +5325,16 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Move when there are no words, only symbols. + text_edit->set_text("#{}"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(3); + + SEND_GUI_ACTION("ui_text_caret_word_left"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 0); } SUBCASE("[TextEdit] ui_text_caret_left") { @@ -5563,6 +5597,16 @@ TEST_CASE("[SceneTree][TextEdit] text entry") { SIGNAL_CHECK("caret_changed", empty_signal_args); SIGNAL_CHECK_FALSE("text_changed"); SIGNAL_CHECK_FALSE("lines_edited_from"); + + // Move when there are no words, only symbols. + text_edit->set_text("#{}"); + text_edit->set_caret_line(0); + text_edit->set_caret_column(0); + + SEND_GUI_ACTION("ui_text_caret_word_right"); + CHECK(text_edit->get_viewport()->is_input_handled()); + CHECK(text_edit->get_caret_line(0) == 0); + CHECK(text_edit->get_caret_column(0) == 3); } SUBCASE("[TextEdit] ui_text_caret_right") { diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h index cf6b89c330..4411b1aae5 100644 --- a/tests/servers/test_navigation_server_3d.h +++ b/tests/servers/test_navigation_server_3d.h @@ -31,6 +31,7 @@ #ifndef TEST_NAVIGATION_SERVER_3D_H #define TEST_NAVIGATION_SERVER_3D_H +#include "modules/navigation/nav_utils.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/resources/3d/primitive_meshes.h" #include "servers/navigation_server_3d.h" @@ -61,6 +62,32 @@ static inline Array build_array(Variant item, Targs... Fargs) { return a; } +struct GreaterThan { + bool operator()(int p_a, int p_b) const { return p_a > p_b; } +}; + +struct CompareArrayValues { + const int *array; + + CompareArrayValues(const int *p_array) : + array(p_array) {} + + bool operator()(uint32_t p_index_a, uint32_t p_index_b) const { + return array[p_index_a] < array[p_index_b]; + } +}; + +struct RegisterHeapIndexes { + uint32_t *indexes; + + RegisterHeapIndexes(uint32_t *p_indexes) : + indexes(p_indexes) {} + + void operator()(uint32_t p_vector_index, uint32_t p_heap_index) { + indexes[p_vector_index] = p_heap_index; + } +}; + TEST_SUITE("[Navigation]") { TEST_CASE("[NavigationServer3D] Server should be empty when initialized") { NavigationServer3D *navigation_server = NavigationServer3D::get_singleton(); @@ -788,6 +815,139 @@ TEST_SUITE("[Navigation]") { CHECK_EQ(navigation_mesh->get_vertices().size(), 0); } */ + + TEST_CASE("[Heap] size") { + gd::Heap<int> heap; + + CHECK(heap.size() == 0); + + heap.push(0); + CHECK(heap.size() == 1); + + heap.push(1); + CHECK(heap.size() == 2); + + heap.pop(); + CHECK(heap.size() == 1); + + heap.pop(); + CHECK(heap.size() == 0); + } + + TEST_CASE("[Heap] is_empty") { + gd::Heap<int> heap; + + CHECK(heap.is_empty() == true); + + heap.push(0); + CHECK(heap.is_empty() == false); + + heap.pop(); + CHECK(heap.is_empty() == true); + } + + TEST_CASE("[Heap] push/pop") { + SUBCASE("Default comparator") { + gd::Heap<int> heap; + + heap.push(2); + heap.push(7); + heap.push(5); + heap.push(3); + heap.push(4); + + CHECK(heap.pop() == 7); + CHECK(heap.pop() == 5); + CHECK(heap.pop() == 4); + CHECK(heap.pop() == 3); + CHECK(heap.pop() == 2); + } + + SUBCASE("Custom comparator") { + GreaterThan greaterThan; + gd::Heap<int, GreaterThan> heap(greaterThan); + + heap.push(2); + heap.push(7); + heap.push(5); + heap.push(3); + heap.push(4); + + CHECK(heap.pop() == 2); + CHECK(heap.pop() == 3); + CHECK(heap.pop() == 4); + CHECK(heap.pop() == 5); + CHECK(heap.pop() == 7); + } + + SUBCASE("Intermediate pops") { + gd::Heap<int> heap; + + heap.push(0); + heap.push(3); + heap.pop(); + heap.push(1); + heap.push(2); + + CHECK(heap.pop() == 2); + CHECK(heap.pop() == 1); + CHECK(heap.pop() == 0); + } + } + + TEST_CASE("[Heap] shift") { + int values[] = { 5, 3, 6, 7, 1 }; + uint32_t heap_indexes[] = { 0, 0, 0, 0, 0 }; + CompareArrayValues comparator(values); + RegisterHeapIndexes indexer(heap_indexes); + gd::Heap<uint32_t, CompareArrayValues, RegisterHeapIndexes> heap(comparator, indexer); + + heap.push(0); + heap.push(1); + heap.push(2); + heap.push(3); + heap.push(4); + + // Shift down: 6 -> 2 + values[2] = 2; + heap.shift(heap_indexes[2]); + + // Shift up: 5 -> 8 + values[0] = 8; + heap.shift(heap_indexes[0]); + + CHECK(heap.pop() == 0); + CHECK(heap.pop() == 3); + CHECK(heap.pop() == 1); + CHECK(heap.pop() == 2); + CHECK(heap.pop() == 4); + + CHECK(heap_indexes[0] == UINT32_MAX); + CHECK(heap_indexes[1] == UINT32_MAX); + CHECK(heap_indexes[2] == UINT32_MAX); + CHECK(heap_indexes[3] == UINT32_MAX); + CHECK(heap_indexes[4] == UINT32_MAX); + } + + TEST_CASE("[Heap] clear") { + uint32_t heap_indexes[] = { 0, 0, 0, 0 }; + RegisterHeapIndexes indexer(heap_indexes); + gd::Heap<uint32_t, Comparator<uint32_t>, RegisterHeapIndexes> heap(indexer); + + heap.push(0); + heap.push(2); + heap.push(1); + heap.push(3); + + heap.clear(); + + CHECK(heap.size() == 0); + + CHECK(heap_indexes[0] == UINT32_MAX); + CHECK(heap_indexes[1] == UINT32_MAX); + CHECK(heap_indexes[2] == UINT32_MAX); + CHECK(heap_indexes[3] == UINT32_MAX); + } } } //namespace TestNavigationServer3D diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 7e1c431a3c..2b6461e9ca 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -119,6 +119,7 @@ #include "tests/scene/test_node.h" #include "tests/scene/test_node_2d.h" #include "tests/scene/test_packed_scene.h" +#include "tests/scene/test_parallax_2d.h" #include "tests/scene/test_path_2d.h" #include "tests/scene/test_path_follow_2d.h" #include "tests/scene/test_sprite_frames.h" @@ -155,6 +156,7 @@ #include "tests/scene/test_arraymesh.h" #include "tests/scene/test_camera_3d.h" +#include "tests/scene/test_height_map_shape_3d.h" #include "tests/scene/test_path_3d.h" #include "tests/scene/test_path_follow_3d.h" #include "tests/scene/test_primitives.h" @@ -320,7 +322,7 @@ struct GodotTestCaseListener : public doctest::IReporter { return; } - if (name.contains("Audio")) { + if (name.contains("[Audio]")) { // The last driver index should always be the dummy driver. int dummy_idx = AudioDriverManager::get_driver_count() - 1; AudioDriverManager::initialize(dummy_idx); diff --git a/thirdparty/README.md b/thirdparty/README.md index d87c1c3c33..dbe20ba2a5 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -543,7 +543,7 @@ in the MSVC debugger. ## mbedtls - Upstream: https://github.com/Mbed-TLS/mbedtls -- Version: 3.6.0 (2ca6c285a0dd3f33982dd57299012dacab1ff206, 2024) +- Version: 3.6.1 (71c569d44bf3a8bd53d874c81ee8ac644dd6e9e3, 2024) - License: Apache 2.0 File extracted from upstream release tarball: @@ -553,8 +553,6 @@ File extracted from upstream release tarball: - All `.c` and `.h` from `library/` to `thirdparty/mbedtls/library/` except for the `psa_*.c` source files - The `LICENSE` file (edited to keep only the Apache 2.0 variant) -- Applied the patch `no-flexible-arrays.diff` to fix Windows build (see - upstream GH-9020) - Applied the patch `msvc-redeclaration-bug.diff` to fix a compilation error with some MSVC versions - Added 2 files `godot_core_mbedtls_platform.c` and `godot_core_mbedtls_config.h` @@ -909,7 +907,7 @@ instead of `miniz.h` as an external dependency. ## thorvg - Upstream: https://github.com/thorvg/thorvg -- Version: 0.14.7 (e3a6bf5229a9671c385ee78bc33e6e6b611a9729, 2024) +- Version: 0.14.9 (81a0fbfd590873b21e53c3af77969c71d3d9b586, 2024) - License: MIT Files extracted from upstream source: diff --git a/thirdparty/mbedtls/include/mbedtls/bignum.h b/thirdparty/mbedtls/include/mbedtls/bignum.h index 71d7b97672..8367cd34e6 100644 --- a/thirdparty/mbedtls/include/mbedtls/bignum.h +++ b/thirdparty/mbedtls/include/mbedtls/bignum.h @@ -880,7 +880,7 @@ int mbedtls_mpi_mod_int(mbedtls_mpi_uint *r, const mbedtls_mpi *A, mbedtls_mpi_sint b); /** - * \brief Perform a sliding-window exponentiation: X = A^E mod N + * \brief Perform a modular exponentiation: X = A^E mod N * * \param X The destination MPI. This must point to an initialized MPI. * This must not alias E or N. diff --git a/thirdparty/mbedtls/include/mbedtls/build_info.h b/thirdparty/mbedtls/include/mbedtls/build_info.h index eab167f383..8242ec6828 100644 --- a/thirdparty/mbedtls/include/mbedtls/build_info.h +++ b/thirdparty/mbedtls/include/mbedtls/build_info.h @@ -26,16 +26,16 @@ */ #define MBEDTLS_VERSION_MAJOR 3 #define MBEDTLS_VERSION_MINOR 6 -#define MBEDTLS_VERSION_PATCH 0 +#define MBEDTLS_VERSION_PATCH 1 /** * The single version number has the following structure: * MMNNPP00 * Major version | Minor version | Patch version */ -#define MBEDTLS_VERSION_NUMBER 0x03060000 -#define MBEDTLS_VERSION_STRING "3.6.0" -#define MBEDTLS_VERSION_STRING_FULL "Mbed TLS 3.6.0" +#define MBEDTLS_VERSION_NUMBER 0x03060100 +#define MBEDTLS_VERSION_STRING "3.6.1" +#define MBEDTLS_VERSION_STRING_FULL "Mbed TLS 3.6.1" /* Macros for build-time platform detection */ @@ -101,6 +101,13 @@ #define inline __inline #endif +#if defined(MBEDTLS_CONFIG_FILES_READ) +#error "Something went wrong: MBEDTLS_CONFIG_FILES_READ defined before reading the config files!" +#endif +#if defined(MBEDTLS_CONFIG_IS_FINALIZED) +#error "Something went wrong: MBEDTLS_CONFIG_IS_FINALIZED defined before reading the config files!" +#endif + /* X.509, TLS and non-PSA crypto configuration */ #if !defined(MBEDTLS_CONFIG_FILE) #include "mbedtls/mbedtls_config.h" @@ -135,6 +142,12 @@ #endif #endif /* defined(MBEDTLS_PSA_CRYPTO_CONFIG) */ +/* Indicate that all configuration files have been read. + * It is now time to adjust the configuration (follow through on dependencies, + * make PSA and legacy crypto consistent, etc.). + */ +#define MBEDTLS_CONFIG_FILES_READ + /* Auto-enable MBEDTLS_CTR_DRBG_USE_128_BIT_KEY if * MBEDTLS_AES_ONLY_128_BIT_KEY_LENGTH and MBEDTLS_CTR_DRBG_C defined * to ensure a 128-bit key size in CTR_DRBG. @@ -169,8 +182,13 @@ #include "mbedtls/config_adjust_ssl.h" -/* Make sure all configuration symbols are set before including check_config.h, - * even the ones that are calculated programmatically. */ +/* Indicate that all configuration symbols are set, + * even the ones that are calculated programmatically. + * It is now safe to query the configuration (to check it, to size buffers, + * etc.). + */ +#define MBEDTLS_CONFIG_IS_FINALIZED + #include "mbedtls/check_config.h" #endif /* MBEDTLS_BUILD_INFO_H */ diff --git a/thirdparty/mbedtls/include/mbedtls/check_config.h b/thirdparty/mbedtls/include/mbedtls/check_config.h index b3c038dd2e..67a05f83b8 100644 --- a/thirdparty/mbedtls/include/mbedtls/check_config.h +++ b/thirdparty/mbedtls/include/mbedtls/check_config.h @@ -2,6 +2,13 @@ * \file check_config.h * * \brief Consistency checks for configuration options + * + * This is an internal header. Do not include it directly. + * + * This header is included automatically by all public Mbed TLS headers + * (via mbedtls/build_info.h). Do not include it directly in a configuration + * file such as mbedtls/mbedtls_config.h or #MBEDTLS_USER_CONFIG_FILE! + * It would run at the wrong time due to missing derived symbols. */ /* * Copyright The Mbed TLS Contributors @@ -12,6 +19,13 @@ #define MBEDTLS_CHECK_CONFIG_H /* *INDENT-OFF* */ + +#if !defined(MBEDTLS_CONFIG_IS_FINALIZED) +#warning "Do not include mbedtls/check_config.h manually! " \ + "This may cause spurious errors. " \ + "It is included automatically at the right point since Mbed TLS 3.0." +#endif /* !MBEDTLS_CONFIG_IS_FINALIZED */ + /* * We assume CHAR_BIT is 8 in many places. In practice, this is true on our * target platforms, so not an issue, but let's just be extra sure. diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h index 9b06041228..3ba987ebb2 100644 --- a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h +++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h @@ -2,7 +2,9 @@ * \file mbedtls/config_adjust_legacy_crypto.h * \brief Adjust legacy configuration configuration * - * Automatically enable certain dependencies. Generally, MBEDLTS_xxx + * This is an internal header. Do not include it directly. + * + * Automatically enable certain dependencies. Generally, MBEDTLS_xxx * configurations need to be explicitly enabled by the user: enabling * MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a * compilation error. However, we do automatically enable certain options @@ -22,6 +24,14 @@ #ifndef MBEDTLS_CONFIG_ADJUST_LEGACY_CRYPTO_H #define MBEDTLS_CONFIG_ADJUST_LEGACY_CRYPTO_H +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + /* Ideally, we'd set those as defaults in mbedtls_config.h, but * putting an #ifdef _WIN32 in mbedtls_config.h would confuse config.py. * @@ -48,7 +58,8 @@ defined(MBEDTLS_PSA_BUILTIN_ALG_ECB_NO_PADDING) || \ defined(MBEDTLS_PSA_BUILTIN_ALG_CBC_NO_PADDING) || \ defined(MBEDTLS_PSA_BUILTIN_ALG_CBC_PKCS7) || \ - defined(MBEDTLS_PSA_BUILTIN_ALG_CCM_STAR_NO_TAG)) + defined(MBEDTLS_PSA_BUILTIN_ALG_CCM_STAR_NO_TAG) || \ + defined(MBEDTLS_PSA_BUILTIN_ALG_CMAC)) #define MBEDTLS_CIPHER_C #endif @@ -293,6 +304,14 @@ #define MBEDTLS_ECP_LIGHT #endif +/* Backward compatibility: after #8740 the RSA module offers functions to parse + * and write RSA private/public keys without relying on the PK one. Of course + * this needs ASN1 support to do so, so we enable it here. */ +#if defined(MBEDTLS_RSA_C) +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_WRITE_C +#endif + /* MBEDTLS_PK_PARSE_EC_COMPRESSED is introduced in Mbed TLS version 3.5, while * in previous version compressed points were automatically supported as long * as PK_PARSE_C and ECP_C were enabled. As a consequence, for backward @@ -409,12 +428,12 @@ /* psa_util file features some ECDSA conversion functions, to convert between * legacy's ASN.1 DER format and PSA's raw one. */ -#if defined(MBEDTLS_ECDSA_C) || (defined(MBEDTLS_PSA_CRYPTO_C) && \ +#if (defined(MBEDTLS_PSA_CRYPTO_CLIENT) && \ (defined(PSA_WANT_ALG_ECDSA) || defined(PSA_WANT_ALG_DETERMINISTIC_ECDSA))) #define MBEDTLS_PSA_UTIL_HAVE_ECDSA #endif -/* Some internal helpers to determine which keys are availble. */ +/* Some internal helpers to determine which keys are available. */ #if (!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_AES_C)) || \ (defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_KEY_TYPE_AES)) #define MBEDTLS_SSL_HAVE_AES @@ -428,7 +447,7 @@ #define MBEDTLS_SSL_HAVE_CAMELLIA #endif -/* Some internal helpers to determine which operation modes are availble. */ +/* Some internal helpers to determine which operation modes are available. */ #if (!defined(MBEDTLS_USE_PSA_CRYPTO) && defined(MBEDTLS_CIPHER_MODE_CBC)) || \ (defined(MBEDTLS_USE_PSA_CRYPTO) && defined(PSA_WANT_ALG_CBC_NO_PADDING)) #define MBEDTLS_SSL_HAVE_CBC diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h index 0091e246b2..04bdae61bb 100644 --- a/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h +++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_legacy_from_psa.h @@ -2,6 +2,8 @@ * \file mbedtls/config_adjust_legacy_from_psa.h * \brief Adjust PSA configuration: activate legacy implementations * + * This is an internal header. Do not include it directly. + * * When MBEDTLS_PSA_CRYPTO_CONFIG is enabled, activate legacy implementations * of cryptographic mechanisms as needed to fulfill the needs of the PSA * configuration. Generally speaking, we activate a legacy mechanism if @@ -16,6 +18,14 @@ #ifndef MBEDTLS_CONFIG_ADJUST_LEGACY_FROM_PSA_H #define MBEDTLS_CONFIG_ADJUST_LEGACY_FROM_PSA_H +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + /* Define appropriate ACCEL macros for the p256-m driver. * In the future, those should be generated from the drivers JSON description. */ @@ -498,7 +508,6 @@ * The PSA implementation has its own implementation of HKDF, separate from * hkdf.c. No need to enable MBEDTLS_HKDF_C here. */ -#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1 #define MBEDTLS_PSA_BUILTIN_ALG_HKDF 1 #endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF */ #endif /* PSA_WANT_ALG_HKDF */ @@ -509,7 +518,6 @@ * The PSA implementation has its own implementation of HKDF, separate from * hkdf.c. No need to enable MBEDTLS_HKDF_C here. */ -#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1 #define MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXTRACT 1 #endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF_EXTRACT */ #endif /* PSA_WANT_ALG_HKDF_EXTRACT */ @@ -520,7 +528,6 @@ * The PSA implementation has its own implementation of HKDF, separate from * hkdf.c. No need to enable MBEDTLS_HKDF_C here. */ -#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1 #define MBEDTLS_PSA_BUILTIN_ALG_HKDF_EXPAND 1 #endif /* !MBEDTLS_PSA_ACCEL_ALG_HKDF_EXPAND */ #endif /* PSA_WANT_ALG_HKDF_EXPAND */ @@ -630,9 +637,6 @@ #if !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_HMAC) #define MBEDTLS_PSA_BUILTIN_ALG_PBKDF2_HMAC 1 #define PSA_HAVE_SOFT_PBKDF2_HMAC 1 -#if !defined(MBEDTLS_PSA_ACCEL_ALG_HMAC) -#define MBEDTLS_PSA_BUILTIN_ALG_HMAC 1 -#endif /* !MBEDTLS_PSA_ACCEL_ALG_HMAC */ #endif /* !MBEDTLS_PSA_BUILTIN_ALG_PBKDF2_HMAC */ #endif /* PSA_WANT_ALG_PBKDF2_HMAC */ diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h index 3456615943..14ca14696f 100644 --- a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h +++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_from_legacy.h @@ -2,6 +2,8 @@ * \file mbedtls/config_adjust_psa_from_legacy.h * \brief Adjust PSA configuration: construct PSA configuration from legacy * + * This is an internal header. Do not include it directly. + * * When MBEDTLS_PSA_CRYPTO_CONFIG is disabled, we automatically enable * cryptographic mechanisms through the PSA interface when the corresponding * legacy mechanism is enabled. In many cases, this just enables the PSA @@ -18,6 +20,14 @@ #ifndef MBEDTLS_CONFIG_ADJUST_PSA_FROM_LEGACY_H #define MBEDTLS_CONFIG_ADJUST_PSA_FROM_LEGACY_H +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + /* * Ensure PSA_WANT_* defines are setup properly if MBEDTLS_PSA_CRYPTO_CONFIG * is not defined diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h index 3a55c3f6e1..ef65cce0d9 100644 --- a/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h +++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_psa_superset_legacy.h @@ -2,6 +2,8 @@ * \file mbedtls/config_adjust_psa_superset_legacy.h * \brief Adjust PSA configuration: automatic enablement from legacy * + * This is an internal header. Do not include it directly. + * * To simplify some edge cases, we automatically enable certain cryptographic * mechanisms in the PSA API if they are enabled in the legacy API. The general * idea is that if legacy module M uses mechanism A internally, and A has @@ -17,6 +19,14 @@ #ifndef MBEDTLS_CONFIG_ADJUST_PSA_SUPERSET_LEGACY_H #define MBEDTLS_CONFIG_ADJUST_PSA_SUPERSET_LEGACY_H +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + /****************************************************************/ /* Hashes that are built in are also enabled in PSA. * This simplifies dependency declarations especially diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h index 39c7b3b117..1f82d9c006 100644 --- a/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h +++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_ssl.h @@ -2,7 +2,9 @@ * \file mbedtls/config_adjust_ssl.h * \brief Adjust TLS configuration * - * Automatically enable certain dependencies. Generally, MBEDLTS_xxx + * This is an internal header. Do not include it directly. + * + * Automatically enable certain dependencies. Generally, MBEDTLS_xxx * configurations need to be explicitly enabled by the user: enabling * MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a * compilation error. However, we do automatically enable certain options @@ -22,6 +24,14 @@ #ifndef MBEDTLS_CONFIG_ADJUST_SSL_H #define MBEDTLS_CONFIG_ADJUST_SSL_H +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + /* The following blocks make it easier to disable all of TLS, * or of TLS 1.2 or 1.3 or DTLS, without having to manually disable all * key exchanges, options and extensions related to them. */ diff --git a/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h b/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h index 346c8ae6d5..cfb2d88916 100644 --- a/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h +++ b/thirdparty/mbedtls/include/mbedtls/config_adjust_x509.h @@ -2,7 +2,9 @@ * \file mbedtls/config_adjust_x509.h * \brief Adjust X.509 configuration * - * Automatically enable certain dependencies. Generally, MBEDLTS_xxx + * This is an internal header. Do not include it directly. + * + * Automatically enable certain dependencies. Generally, MBEDTLS_xxx * configurations need to be explicitly enabled by the user: enabling * MBEDTLS_xxx_A but not MBEDTLS_xxx_B when A requires B results in a * compilation error. However, we do automatically enable certain options @@ -22,4 +24,12 @@ #ifndef MBEDTLS_CONFIG_ADJUST_X509_H #define MBEDTLS_CONFIG_ADJUST_X509_H +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include mbedtls/config_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + #endif /* MBEDTLS_CONFIG_ADJUST_X509_H */ diff --git a/thirdparty/mbedtls/include/mbedtls/config_psa.h b/thirdparty/mbedtls/include/mbedtls/config_psa.h index 17da61b3e8..5f3d0f3d5d 100644 --- a/thirdparty/mbedtls/include/mbedtls/config_psa.h +++ b/thirdparty/mbedtls/include/mbedtls/config_psa.h @@ -22,6 +22,8 @@ #include "psa/crypto_adjust_config_synonyms.h" +#include "psa/crypto_adjust_config_dependencies.h" + #include "mbedtls/config_adjust_psa_superset_legacy.h" #if defined(MBEDTLS_PSA_CRYPTO_CONFIG) @@ -32,7 +34,11 @@ * before we deduce what built-ins are required. */ #include "psa/crypto_adjust_config_key_pair_types.h" +#if defined(MBEDTLS_PSA_CRYPTO_C) +/* If we are implementing PSA crypto ourselves, then we want to enable the + * required built-ins. Otherwise, PSA features will be provided by the server. */ #include "mbedtls/config_adjust_legacy_from_psa.h" +#endif #else /* MBEDTLS_PSA_CRYPTO_CONFIG */ diff --git a/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h b/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h index c00756df1b..0b7cce1923 100644 --- a/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h +++ b/thirdparty/mbedtls/include/mbedtls/ctr_drbg.h @@ -32,12 +32,27 @@ #include "mbedtls/build_info.h" -/* In case AES_C is defined then it is the primary option for backward - * compatibility purposes. If that's not available, PSA is used instead */ -#if defined(MBEDTLS_AES_C) -#include "mbedtls/aes.h" -#else +/* The CTR_DRBG implementation can either directly call the low-level AES + * module (gated by MBEDTLS_AES_C) or call the PSA API to perform AES + * operations. Calling the AES module directly is the default, both for + * maximum backward compatibility and because it's a bit more efficient + * (less glue code). + * + * When MBEDTLS_AES_C is disabled, the CTR_DRBG module calls PSA crypto and + * thus benefits from the PSA AES accelerator driver. + * It is technically possible to enable MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO + * to use PSA even when MBEDTLS_AES_C is enabled, but there is very little + * reason to do so other than testing purposes and this is not officially + * supported. + */ +#if !defined(MBEDTLS_AES_C) +#define MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO +#endif + +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) #include "psa/crypto.h" +#else +#include "mbedtls/aes.h" #endif #include "entropy.h" @@ -157,7 +172,7 @@ extern "C" { #define MBEDTLS_CTR_DRBG_ENTROPY_NONCE_LEN (MBEDTLS_CTR_DRBG_ENTROPY_LEN + 1) / 2 #endif -#if !defined(MBEDTLS_AES_C) +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) typedef struct mbedtls_ctr_drbg_psa_context { mbedtls_svc_key_id_t key_id; psa_cipher_operation_t operation; @@ -189,10 +204,10 @@ typedef struct mbedtls_ctr_drbg_context { * This is the maximum number of requests * that can be made between reseedings. */ -#if defined(MBEDTLS_AES_C) - mbedtls_aes_context MBEDTLS_PRIVATE(aes_ctx); /*!< The AES context. */ -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) mbedtls_ctr_drbg_psa_context MBEDTLS_PRIVATE(psa_ctx); /*!< The PSA context. */ +#else + mbedtls_aes_context MBEDTLS_PRIVATE(aes_ctx); /*!< The AES context. */ #endif /* diff --git a/thirdparty/mbedtls/include/mbedtls/ecdh.h b/thirdparty/mbedtls/include/mbedtls/ecdh.h index a0909d6b44..a6a5069337 100644 --- a/thirdparty/mbedtls/include/mbedtls/ecdh.h +++ b/thirdparty/mbedtls/include/mbedtls/ecdh.h @@ -325,7 +325,7 @@ int mbedtls_ecdh_read_params(mbedtls_ecdh_context *ctx, * \brief This function sets up an ECDH context from an EC key. * * It is used by clients and servers in place of the - * ServerKeyEchange for static ECDH, and imports ECDH + * ServerKeyExchange for static ECDH, and imports ECDH * parameters from the EC key information of a certificate. * * \see ecp.h diff --git a/thirdparty/mbedtls/include/mbedtls/ecp.h b/thirdparty/mbedtls/include/mbedtls/ecp.h index d8f73ae965..623910bcbd 100644 --- a/thirdparty/mbedtls/include/mbedtls/ecp.h +++ b/thirdparty/mbedtls/include/mbedtls/ecp.h @@ -216,7 +216,7 @@ mbedtls_ecp_point; * range of <code>0..2^(2*pbits)-1</code>, and transforms it in-place to an integer * which is congruent mod \p P to the given MPI, and is close enough to \p pbits * in size, so that it may be efficiently brought in the 0..P-1 range by a few - * additions or subtractions. Therefore, it is only an approximative modular + * additions or subtractions. Therefore, it is only an approximate modular * reduction. It must return 0 on success and non-zero on failure. * * \note Alternative implementations of the ECP module must obey the diff --git a/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h b/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h index 35921412c6..bd3f71d5bc 100644 --- a/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h +++ b/thirdparty/mbedtls/include/mbedtls/mbedtls_config.h @@ -1118,7 +1118,7 @@ * MBEDTLS_ECP_DP_SECP256R1_ENABLED * * \warning If SHA-256 is provided only by a PSA driver, you must call - * psa_crypto_init() before the first hanshake (even if + * psa_crypto_init() before the first handshake (even if * MBEDTLS_USE_PSA_CRYPTO is disabled). * * This enables the following ciphersuites (if other requisites are @@ -1415,6 +1415,23 @@ //#define MBEDTLS_PSA_CRYPTO_SPM /** + * \def MBEDTLS_PSA_KEY_STORE_DYNAMIC + * + * Dynamically resize the PSA key store to accommodate any number of + * volatile keys (until the heap memory is exhausted). + * + * If this option is disabled, the key store has a fixed size + * #MBEDTLS_PSA_KEY_SLOT_COUNT for volatile keys and loaded persistent keys + * together. + * + * This option has no effect when #MBEDTLS_PSA_CRYPTO_C is disabled. + * + * Module: library/psa_crypto.c + * Requires: MBEDTLS_PSA_CRYPTO_C + */ +#define MBEDTLS_PSA_KEY_STORE_DYNAMIC + +/** * Uncomment to enable p256-m. This is an alternative implementation of * key generation, ECDH and (randomized) ECDSA on the curve SECP256R1. * Compared to the default implementation: @@ -1781,8 +1798,9 @@ * Requires: MBEDTLS_PSA_CRYPTO_C * * \note TLS 1.3 uses PSA crypto for cryptographic operations that are - * directly performed by TLS 1.3 code. As a consequence, you must - * call psa_crypto_init() before the first TLS 1.3 handshake. + * directly performed by TLS 1.3 code. As a consequence, when TLS 1.3 + * is enabled, a TLS handshake may call psa_crypto_init(), even + * if it ends up negotiating a different TLS version. * * \note Cryptographic operations performed indirectly via another module * (X.509, PK) or by code shared with TLS 1.2 (record protection, @@ -2625,7 +2643,7 @@ * The CTR_DRBG generator uses AES-256 by default. * To use AES-128 instead, enable \c MBEDTLS_CTR_DRBG_USE_128_BIT_KEY above. * - * AES support can either be achived through builtin (MBEDTLS_AES_C) or PSA. + * AES support can either be achieved through builtin (MBEDTLS_AES_C) or PSA. * Builtin is the default option when MBEDTLS_AES_C is defined otherwise PSA * is used. * @@ -4016,22 +4034,38 @@ * Use HMAC_DRBG with the specified hash algorithm for HMAC_DRBG for the * PSA crypto subsystem. * - * If this option is unset: - * - If CTR_DRBG is available, the PSA subsystem uses it rather than HMAC_DRBG. - * - Otherwise, the PSA subsystem uses HMAC_DRBG with either - * #MBEDTLS_MD_SHA512 or #MBEDTLS_MD_SHA256 based on availability and - * on unspecified heuristics. + * If this option is unset, the library chooses a hash (currently between + * #MBEDTLS_MD_SHA512 and #MBEDTLS_MD_SHA256) based on availability and + * unspecified heuristics. + * + * \note The PSA crypto subsystem uses the first available mechanism amongst + * the following: + * - #MBEDTLS_PSA_CRYPTO_EXTERNAL_RNG if enabled; + * - Entropy from #MBEDTLS_ENTROPY_C plus CTR_DRBG with AES + * if #MBEDTLS_CTR_DRBG_C is enabled; + * - Entropy from #MBEDTLS_ENTROPY_C plus HMAC_DRBG. + * + * A future version may reevaluate the prioritization of DRBG mechanisms. */ //#define MBEDTLS_PSA_HMAC_DRBG_MD_TYPE MBEDTLS_MD_SHA256 /** \def MBEDTLS_PSA_KEY_SLOT_COUNT - * Restrict the PSA library to supporting a maximum amount of simultaneously - * loaded keys. A loaded key is a key stored by the PSA Crypto core as a - * volatile key, or a persistent key which is loaded temporarily by the - * library as part of a crypto operation in flight. * - * If this option is unset, the library will fall back to a default value of - * 32 keys. + * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is disabled, + * the maximum amount of PSA keys simultaneously in memory. This counts all + * volatile keys, plus loaded persistent keys. + * + * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled, + * the maximum number of loaded persistent keys. + * + * Currently, persistent keys do not need to be loaded all the time while + * a multipart operation is in progress, only while the operation is being + * set up. This may change in future versions of the library. + * + * Currently, the library traverses of the whole table on each access to a + * persistent key. Therefore large values may cause poor performance. + * + * This option has no effect when #MBEDTLS_PSA_CRYPTO_C is disabled. */ //#define MBEDTLS_PSA_KEY_SLOT_COUNT 32 diff --git a/thirdparty/mbedtls/include/mbedtls/pk.h b/thirdparty/mbedtls/include/mbedtls/pk.h index fde302f872..52f4cc6c9e 100644 --- a/thirdparty/mbedtls/include/mbedtls/pk.h +++ b/thirdparty/mbedtls/include/mbedtls/pk.h @@ -359,32 +359,40 @@ int mbedtls_pk_setup(mbedtls_pk_context *ctx, const mbedtls_pk_info_t *info); #if defined(MBEDTLS_USE_PSA_CRYPTO) /** - * \brief Initialize a PK context to wrap a PSA key. - * - * \note This function replaces mbedtls_pk_setup() for contexts - * that wrap a (possibly opaque) PSA key instead of - * storing and manipulating the key material directly. - * - * \param ctx The context to initialize. It must be empty (type NONE). - * \param key The PSA key to wrap, which must hold an ECC or RSA key - * pair (see notes below). - * - * \note The wrapped key must remain valid as long as the - * wrapping PK context is in use, that is at least between - * the point this function is called and the point - * mbedtls_pk_free() is called on this context. The wrapped - * key might then be independently used or destroyed. - * - * \note This function is currently only available for ECC or RSA - * key pairs (that is, keys containing private key material). - * Support for other key types may be added later. - * - * \return \c 0 on success. - * \return #MBEDTLS_ERR_PK_BAD_INPUT_DATA on invalid input - * (context already used, invalid key identifier). - * \return #MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE if the key is not an - * ECC key pair. - * \return #MBEDTLS_ERR_PK_ALLOC_FAILED on allocation failure. + * \brief Initialize a PK context to wrap a PSA key. + * + * This function creates a PK context which wraps a PSA key. The PSA wrapped + * key must be an EC or RSA key pair (DH is not suported in the PK module). + * + * Under the hood PSA functions will be used to perform the required + * operations and, based on the key type, used algorithms will be: + * * EC: + * * verify, verify_ext, sign, sign_ext: ECDSA. + * * RSA: + * * sign, decrypt: use the primary algorithm in the wrapped PSA key; + * * sign_ext: RSA PSS if the pk_type is #MBEDTLS_PK_RSASSA_PSS, otherwise + * it falls back to the sign() case; + * * verify, verify_ext, encrypt: not supported. + * + * In order for the above operations to succeed, the policy of the wrapped PSA + * key must allow the specified algorithm. + * + * Opaque PK contexts wrapping an EC keys also support \c mbedtls_pk_check_pair(), + * whereas RSA ones do not. + * + * \warning The PSA wrapped key must remain valid as long as the wrapping PK + * context is in use, that is at least between the point this function + * is called and the point mbedtls_pk_free() is called on this context. + * + * \param ctx The context to initialize. It must be empty (type NONE). + * \param key The PSA key to wrap, which must hold an ECC or RSA key pair. + * + * \return \c 0 on success. + * \return #MBEDTLS_ERR_PK_BAD_INPUT_DATA on invalid input (context already + * used, invalid key identifier). + * \return #MBEDTLS_ERR_PK_FEATURE_UNAVAILABLE if the key is not an ECC or + * RSA key pair. + * \return #MBEDTLS_ERR_PK_ALLOC_FAILED on allocation failure. */ int mbedtls_pk_setup_opaque(mbedtls_pk_context *ctx, const mbedtls_svc_key_id_t key); diff --git a/thirdparty/mbedtls/include/mbedtls/ssl.h b/thirdparty/mbedtls/include/mbedtls/ssl.h index 172d4693b2..42fffbf860 100644 --- a/thirdparty/mbedtls/include/mbedtls/ssl.h +++ b/thirdparty/mbedtls/include/mbedtls/ssl.h @@ -83,10 +83,7 @@ /** Processing of the Certificate handshake message failed. */ #define MBEDTLS_ERR_SSL_BAD_CERTIFICATE -0x7A00 /* Error space gap */ -/** - * Received NewSessionTicket Post Handshake Message. - * This error code is experimental and may be changed or removed without notice. - */ +/** A TLS 1.3 NewSessionTicket message has been received. */ #define MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET -0x7B00 /** Not possible to read early data */ #define MBEDTLS_ERR_SSL_CANNOT_READ_EARLY_DATA -0x7B80 @@ -324,6 +321,9 @@ #define MBEDTLS_SSL_SESSION_TICKETS_DISABLED 0 #define MBEDTLS_SSL_SESSION_TICKETS_ENABLED 1 +#define MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED 0 +#define MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED 1 + #define MBEDTLS_SSL_PRESET_DEFAULT 0 #define MBEDTLS_SSL_PRESET_SUITEB 2 @@ -1446,6 +1446,12 @@ struct mbedtls_ssl_config { #endif #if defined(MBEDTLS_SSL_SESSION_TICKETS) && \ defined(MBEDTLS_SSL_CLI_C) + /** Encodes two booleans, one stating whether TLS 1.2 session tickets are + * enabled or not, the other one whether the handling of TLS 1.3 + * NewSessionTicket messages is enabled or not. They are respectively set + * by mbedtls_ssl_conf_session_tickets() and + * mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(). + */ uint8_t MBEDTLS_PRIVATE(session_tickets); /*!< use session tickets? */ #endif @@ -2364,7 +2370,7 @@ int mbedtls_ssl_set_cid(mbedtls_ssl_context *ssl, */ int mbedtls_ssl_get_own_cid(mbedtls_ssl_context *ssl, int *enabled, - unsigned char own_cid[MBEDTLS_SSL_CID_OUT_LEN_MAX], + unsigned char own_cid[MBEDTLS_SSL_CID_IN_LEN_MAX], size_t *own_cid_len); /** @@ -3216,16 +3222,16 @@ void mbedtls_ssl_conf_session_cache(mbedtls_ssl_config *conf, * a full handshake. * * \note This function can handle a variety of mechanisms for session - * resumption: For TLS 1.2, both session ID-based resumption and - * ticket-based resumption will be considered. For TLS 1.3, - * once implemented, sessions equate to tickets, and loading - * one or more sessions via this call will lead to their - * corresponding tickets being advertised as resumption PSKs - * by the client. - * - * \note Calling this function multiple times will only be useful - * once TLS 1.3 is supported. For TLS 1.2 connections, this - * function should be called at most once. + * resumption: For TLS 1.2, both session ID-based resumption + * and ticket-based resumption will be considered. For TLS 1.3, + * sessions equate to tickets, and loading one session by + * calling this function will lead to its corresponding ticket + * being advertised as resumption PSK by the client. This + * depends on session tickets being enabled (see + * #MBEDTLS_SSL_SESSION_TICKETS configuration option) though. + * If session tickets are disabled, a call to this function + * with a TLS 1.3 session, will not have any effect on the next + * handshake for the SSL context \p ssl. * * \param ssl The SSL context representing the connection which should * be attempted to be setup using session resumption. This @@ -3240,9 +3246,10 @@ void mbedtls_ssl_conf_session_cache(mbedtls_ssl_config *conf, * * \return \c 0 if successful. * \return \c MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if the session - * could not be loaded because of an implementation limitation. - * This error is non-fatal, and has no observable effect on - * the SSL context or the session that was attempted to be loaded. + * could not be loaded because one session has already been + * loaded. This error is non-fatal, and has no observable + * effect on the SSL context or the session that was attempted + * to be loaded. * \return Another negative error code on other kinds of failure. * * \sa mbedtls_ssl_get_session() @@ -3309,8 +3316,16 @@ int mbedtls_ssl_session_load(mbedtls_ssl_session *session, * to determine the necessary size by calling this function * with \p buf set to \c NULL and \p buf_len to \c 0. * + * \note For TLS 1.3 sessions, this feature is supported only if the + * MBEDTLS_SSL_SESSION_TICKETS configuration option is enabled, + * as in TLS 1.3 session resumption is possible only with + * tickets. + * * \return \c 0 if successful. * \return #MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL if \p buf is too small. + * \return #MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if the + * MBEDTLS_SSL_SESSION_TICKETS configuration option is disabled + * and the session is a TLS 1.3 session. */ int mbedtls_ssl_session_save(const mbedtls_ssl_session *session, unsigned char *buf, @@ -4456,21 +4471,50 @@ int mbedtls_ssl_conf_max_frag_len(mbedtls_ssl_config *conf, unsigned char mfl_co void mbedtls_ssl_conf_preference_order(mbedtls_ssl_config *conf, int order); #endif /* MBEDTLS_SSL_SRV_C */ -#if defined(MBEDTLS_SSL_SESSION_TICKETS) && \ - defined(MBEDTLS_SSL_CLI_C) +#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C) /** - * \brief Enable / Disable session tickets (client only). - * (Default: MBEDTLS_SSL_SESSION_TICKETS_ENABLED.) + * \brief Enable / Disable TLS 1.2 session tickets (client only, + * TLS 1.2 only). Enabled by default. * * \note On server, use \c mbedtls_ssl_conf_session_tickets_cb(). * * \param conf SSL configuration - * \param use_tickets Enable or disable (MBEDTLS_SSL_SESSION_TICKETS_ENABLED or - * MBEDTLS_SSL_SESSION_TICKETS_DISABLED) + * \param use_tickets Enable or disable (#MBEDTLS_SSL_SESSION_TICKETS_ENABLED or + * #MBEDTLS_SSL_SESSION_TICKETS_DISABLED) */ void mbedtls_ssl_conf_session_tickets(mbedtls_ssl_config *conf, int use_tickets); -#endif /* MBEDTLS_SSL_SESSION_TICKETS && - MBEDTLS_SSL_CLI_C */ + +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) +/** + * \brief Enable / Disable handling of TLS 1.3 NewSessionTicket messages + * (client only, TLS 1.3 only). + * + * The handling of TLS 1.3 NewSessionTicket messages is disabled by + * default. + * + * In TLS 1.3, servers may send a NewSessionTicket message at any time, + * and may send multiple NewSessionTicket messages. By default, TLS 1.3 + * clients ignore NewSessionTicket messages. + * + * To support session tickets in TLS 1.3 clients, call this function + * with #MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED. When + * this is enabled, when a client receives a NewSessionTicket message, + * the next call to a message processing functions (notably + * mbedtls_ssl_handshake() and mbedtls_ssl_read()) will return + * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET. The client should then + * call mbedtls_ssl_get_session() to retrieve the session ticket before + * calling the same message processing function again. + * + * \param conf SSL configuration + * \param signal_new_session_tickets Enable or disable + * (#MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED or + * #MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED) + */ +void mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets( + mbedtls_ssl_config *conf, int signal_new_session_tickets); + +#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */ +#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */ #if defined(MBEDTLS_SSL_SESSION_TICKETS) && \ defined(MBEDTLS_SSL_SRV_C) && \ @@ -4837,23 +4881,16 @@ const mbedtls_x509_crt *mbedtls_ssl_get_peer_cert(const mbedtls_ssl_context *ssl * \note This function can handle a variety of mechanisms for session * resumption: For TLS 1.2, both session ID-based resumption and * ticket-based resumption will be considered. For TLS 1.3, - * once implemented, sessions equate to tickets, and calling - * this function multiple times will export the available - * tickets one a time until no further tickets are available, - * in which case MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE will - * be returned. - * - * \note Calling this function multiple times will only be useful - * once TLS 1.3 is supported. For TLS 1.2 connections, this - * function should be called at most once. + * sessions equate to tickets, and if session tickets are + * enabled (see #MBEDTLS_SSL_SESSION_TICKETS configuration + * option), this function exports the last received ticket and + * the exported session may be used to resume the TLS 1.3 + * session. If session tickets are disabled, exported sessions + * cannot be used to resume a TLS 1.3 session. * * \return \c 0 if successful. In this case, \p session can be used for * session resumption by passing it to mbedtls_ssl_set_session(), * and serialized for storage via mbedtls_ssl_session_save(). - * \return #MBEDTLS_ERR_SSL_FEATURE_UNAVAILABLE if no further session - * is available for export. - * This error is a non-fatal, and has no observable effect on - * the SSL context or the destination session. * \return Another negative error code on other kinds of failure. * * \sa mbedtls_ssl_set_session() @@ -4885,6 +4922,10 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl, * \return #MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED if DTLS is in use * and the client did not demonstrate reachability yet - in * this case you must stop using the context (see below). + * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3 + * NewSessionTicket message has been received. See the + * documentation of mbedtls_ssl_read() for more information + * about this error code. * \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as * defined in RFC 8446 (TLS 1.3 specification), has been * received as part of the handshake. This is server specific @@ -4901,6 +4942,7 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl, * #MBEDTLS_ERR_SSL_WANT_WRITE, * #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS or * #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or + * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or * #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA, * you must stop using the SSL context for reading or writing, * and either free it or call \c mbedtls_ssl_session_reset() @@ -4921,10 +4963,13 @@ int mbedtls_ssl_get_session(const mbedtls_ssl_context *ssl, * currently being processed might or might not contain further * DTLS records. * - * \note If the context is configured to allow TLS 1.3, or if - * #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto + * \note If #MBEDTLS_USE_PSA_CRYPTO is enabled, the PSA crypto * subsystem must have been initialized by calling * psa_crypto_init() before calling this function. + * Otherwise, the handshake may call psa_crypto_init() + * if a negotiation involving TLS 1.3 takes place (this may + * be the case even if TLS 1.3 is offered but eventually + * not selected). */ int mbedtls_ssl_handshake(mbedtls_ssl_context *ssl); @@ -4972,6 +5017,7 @@ static inline int mbedtls_ssl_is_handshake_over(mbedtls_ssl_context *ssl) * #MBEDTLS_ERR_SSL_WANT_READ, #MBEDTLS_ERR_SSL_WANT_WRITE, * #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS, * #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or + * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or * #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA, you must stop using * the SSL context for reading or writing, and either free it * or call \c mbedtls_ssl_session_reset() on it before @@ -5040,6 +5086,17 @@ int mbedtls_ssl_renegotiate(mbedtls_ssl_context *ssl); * \return #MBEDTLS_ERR_SSL_CLIENT_RECONNECT if we're at the server * side of a DTLS connection and the client is initiating a * new connection using the same source port. See below. + * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3 + * NewSessionTicket message has been received. + * This error code is only returned on the client side. It is + * only returned if handling of TLS 1.3 NewSessionTicket + * messages has been enabled through + * mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets(). + * This error code indicates that a TLS 1.3 NewSessionTicket + * message has been received and parsed successfully by the + * client. The ticket data can be retrieved from the SSL + * context by calling mbedtls_ssl_get_session(). It remains + * available until the next call to mbedtls_ssl_read(). * \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as * defined in RFC 8446 (TLS 1.3 specification), has been * received as part of the handshake. This is server specific @@ -5057,6 +5114,7 @@ int mbedtls_ssl_renegotiate(mbedtls_ssl_context *ssl); * #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS, * #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS, * #MBEDTLS_ERR_SSL_CLIENT_RECONNECT or + * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or * #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA, * you must stop using the SSL context for reading or writing, * and either free it or call \c mbedtls_ssl_session_reset() @@ -5122,6 +5180,10 @@ int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len); * operation is in progress (see mbedtls_ecp_set_max_ops()) - * in this case you must call this function again to complete * the handshake when you're done attending other tasks. + * \return #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET if a TLS 1.3 + * NewSessionTicket message has been received. See the + * documentation of mbedtls_ssl_read() for more information + * about this error code. * \return #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA if early data, as * defined in RFC 8446 (TLS 1.3 specification), has been * received as part of the handshake. This is server specific @@ -5138,6 +5200,7 @@ int mbedtls_ssl_read(mbedtls_ssl_context *ssl, unsigned char *buf, size_t len); * #MBEDTLS_ERR_SSL_WANT_WRITE, * #MBEDTLS_ERR_SSL_ASYNC_IN_PROGRESS, * #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS or + * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET or * #MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA, * you must stop using the SSL context for reading or writing, * and either free it or call \c mbedtls_ssl_session_reset() diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h index 390534edf5..96baf8f3ed 100644 --- a/thirdparty/mbedtls/include/psa/crypto.h +++ b/thirdparty/mbedtls/include/psa/crypto.h @@ -121,8 +121,8 @@ static psa_key_attributes_t psa_key_attributes_init(void); * value in the structure. * The persistent key will be written to storage when the attribute * structure is passed to a key creation function such as - * psa_import_key(), psa_generate_key(), psa_generate_key_ext(), - * psa_key_derivation_output_key(), psa_key_derivation_output_key_ext() + * psa_import_key(), psa_generate_key(), psa_generate_key_custom(), + * psa_key_derivation_output_key(), psa_key_derivation_output_key_custom() * or psa_copy_key(). * * This function may be declared as `static` (i.e. without external @@ -131,6 +131,9 @@ static psa_key_attributes_t psa_key_attributes_init(void); * * \param[out] attributes The attribute structure to write to. * \param key The persistent identifier for the key. + * This can be any value in the range from + * #PSA_KEY_ID_USER_MIN to #PSA_KEY_ID_USER_MAX + * inclusive. */ static void psa_set_key_id(psa_key_attributes_t *attributes, mbedtls_svc_key_id_t key); @@ -166,8 +169,8 @@ static void mbedtls_set_key_owner_id(psa_key_attributes_t *attributes, * value in the structure. * The persistent key will be written to storage when the attribute * structure is passed to a key creation function such as - * psa_import_key(), psa_generate_key(), psa_generate_key_ext(), - * psa_key_derivation_output_key(), psa_key_derivation_output_key_ext() + * psa_import_key(), psa_generate_key(), psa_generate_key_custom(), + * psa_key_derivation_output_key(), psa_key_derivation_output_key_custom() * or psa_copy_key(). * * This function may be declared as `static` (i.e. without external @@ -875,7 +878,7 @@ psa_status_t psa_hash_compute(psa_algorithm_t alg, * such that #PSA_ALG_IS_HASH(\p alg) is true). * \param[in] input Buffer containing the message to hash. * \param input_length Size of the \p input buffer in bytes. - * \param[out] hash Buffer containing the expected hash value. + * \param[in] hash Buffer containing the expected hash value. * \param hash_length Size of the \p hash buffer in bytes. * * \retval #PSA_SUCCESS @@ -1230,7 +1233,7 @@ psa_status_t psa_mac_compute(mbedtls_svc_key_id_t key, * such that #PSA_ALG_IS_MAC(\p alg) is true). * \param[in] input Buffer containing the input message. * \param input_length Size of the \p input buffer in bytes. - * \param[out] mac Buffer containing the expected MAC value. + * \param[in] mac Buffer containing the expected MAC value. * \param mac_length Size of the \p mac buffer in bytes. * * \retval #PSA_SUCCESS @@ -2922,7 +2925,7 @@ psa_status_t psa_sign_message(mbedtls_svc_key_id_t key, * \p key. * \param[in] input The message whose signature is to be verified. * \param[in] input_length Size of the \p input buffer in bytes. - * \param[out] signature Buffer containing the signature to verify. + * \param[in] signature Buffer containing the signature to verify. * \param[in] signature_length Size of the \p signature buffer in bytes. * * \retval #PSA_SUCCESS \emptydescription @@ -3248,7 +3251,7 @@ static psa_key_derivation_operation_t psa_key_derivation_operation_init(void); * of or after providing inputs. For some algorithms, this step is mandatory * because the output depends on the maximum capacity. * -# To derive a key, call psa_key_derivation_output_key() or - * psa_key_derivation_output_key_ext(). + * psa_key_derivation_output_key_custom(). * To derive a byte string for a different purpose, call * psa_key_derivation_output_bytes(). * Successive calls to these functions use successive output bytes @@ -3471,7 +3474,7 @@ psa_status_t psa_key_derivation_input_integer( * \note Once all inputs steps are completed, the operations will allow: * - psa_key_derivation_output_bytes() if each input was either a direct input * or a key with #PSA_KEY_USAGE_DERIVE set; - * - psa_key_derivation_output_key() or psa_key_derivation_output_key_ext() + * - psa_key_derivation_output_key() or psa_key_derivation_output_key_custom() * if the input for step * #PSA_KEY_DERIVATION_INPUT_SECRET or #PSA_KEY_DERIVATION_INPUT_PASSWORD * was from a key slot with #PSA_KEY_USAGE_DERIVE and each other input was @@ -3721,9 +3724,9 @@ psa_status_t psa_key_derivation_output_bytes( * on the derived key based on the attributes and strength of the secret key. * * \note This function is equivalent to calling - * psa_key_derivation_output_key_ext() - * with the production parameters #PSA_KEY_PRODUCTION_PARAMETERS_INIT - * and `params_data_length == 0` (i.e. `params->data` is empty). + * psa_key_derivation_output_key_custom() + * with the custom production parameters #PSA_CUSTOM_KEY_PARAMETERS_INIT + * and `custom_data_length == 0` (i.e. `custom_data` is empty). * * \param[in] attributes The attributes for the new key. * If the key type to be created is @@ -3795,6 +3798,85 @@ psa_status_t psa_key_derivation_output_key( * the policy must be the same as in the current * operation. * \param[in,out] operation The key derivation operation object to read from. + * \param[in] custom Customization parameters for the key generation. + * When this is #PSA_CUSTOM_KEY_PARAMETERS_INIT + * with \p custom_data_length = 0, + * this function is equivalent to + * psa_key_derivation_output_key(). + * \param[in] custom_data Variable-length data associated with \c custom. + * \param custom_data_length + * Length of `custom_data` in bytes. + * \param[out] key On success, an identifier for the newly created + * key. For persistent keys, this is the key + * identifier defined in \p attributes. + * \c 0 on failure. + * + * \retval #PSA_SUCCESS + * Success. + * If the key is persistent, the key material and the key's metadata + * have been saved to persistent storage. + * \retval #PSA_ERROR_ALREADY_EXISTS + * This is an attempt to create a persistent key, and there is + * already a persistent key with the given identifier. + * \retval #PSA_ERROR_INSUFFICIENT_DATA + * There was not enough data to create the desired key. + * Note that in this case, no output is written to the output buffer. + * The operation's capacity is set to 0, thus subsequent calls to + * this function will not succeed, even with a smaller output buffer. + * \retval #PSA_ERROR_NOT_SUPPORTED + * The key type or key size is not supported, either by the + * implementation in general or in this particular location. + * \retval #PSA_ERROR_INVALID_ARGUMENT + * The provided key attributes are not valid for the operation. + * \retval #PSA_ERROR_NOT_PERMITTED + * The #PSA_KEY_DERIVATION_INPUT_SECRET or + * #PSA_KEY_DERIVATION_INPUT_PASSWORD input was not provided through a + * key; or one of the inputs was a key whose policy didn't allow + * #PSA_KEY_USAGE_DERIVE. + * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription + * \retval #PSA_ERROR_INSUFFICIENT_STORAGE \emptydescription + * \retval #PSA_ERROR_COMMUNICATION_FAILURE \emptydescription + * \retval #PSA_ERROR_HARDWARE_FAILURE \emptydescription + * \retval #PSA_ERROR_CORRUPTION_DETECTED \emptydescription + * \retval #PSA_ERROR_DATA_INVALID \emptydescription + * \retval #PSA_ERROR_DATA_CORRUPT \emptydescription + * \retval #PSA_ERROR_STORAGE_FAILURE \emptydescription + * \retval #PSA_ERROR_BAD_STATE + * The operation state is not valid (it must be active and completed + * all required input steps), or the library has not been previously + * initialized by psa_crypto_init(). + * It is implementation-dependent whether a failure to initialize + * results in this error code. + */ +psa_status_t psa_key_derivation_output_key_custom( + const psa_key_attributes_t *attributes, + psa_key_derivation_operation_t *operation, + const psa_custom_key_parameters_t *custom, + const uint8_t *custom_data, + size_t custom_data_length, + mbedtls_svc_key_id_t *key); + +#ifndef __cplusplus +/* Omitted when compiling in C++, because one of the parameters is a + * pointer to a struct with a flexible array member, and that is not + * standard C++. + * https://github.com/Mbed-TLS/mbedtls/issues/9020 + */ +/** Derive a key from an ongoing key derivation operation with custom + * production parameters. + * + * \note + * This is a deprecated variant of psa_key_derivation_output_key_custom(). + * It is equivalent except that the associated variable-length data + * is passed in `params->data` instead of a separate parameter. + * This function will be removed in a future version of Mbed TLS. + * + * \param[in] attributes The attributes for the new key. + * If the key type to be created is + * #PSA_KEY_TYPE_PASSWORD_HASH then the algorithm in + * the policy must be the same as in the current + * operation. + * \param[in,out] operation The key derivation operation object to read from. * \param[in] params Customization parameters for the key derivation. * When this is #PSA_KEY_PRODUCTION_PARAMETERS_INIT * with \p params_data_length = 0, @@ -3848,14 +3930,13 @@ psa_status_t psa_key_derivation_output_key( * It is implementation-dependent whether a failure to initialize * results in this error code. */ -#ifndef __cplusplus psa_status_t psa_key_derivation_output_key_ext( const psa_key_attributes_t *attributes, psa_key_derivation_operation_t *operation, const psa_key_production_parameters_t *params, size_t params_data_length, mbedtls_svc_key_id_t *key); -#endif +#endif /* !__cplusplus */ /** Compare output data from a key derivation operation to an expected value. * @@ -3881,8 +3962,8 @@ psa_status_t psa_key_derivation_output_key_ext( * psa_key_derivation_abort(). * * \param[in,out] operation The key derivation operation object to read from. - * \param[in] expected_output Buffer containing the expected derivation output. - * \param output_length Length of the expected output; this is also the + * \param[in] expected Buffer containing the expected derivation output. + * \param expected_length Length of the expected output; this is also the * number of bytes that will be read. * * \retval #PSA_SUCCESS \emptydescription @@ -3912,8 +3993,8 @@ psa_status_t psa_key_derivation_output_key_ext( */ psa_status_t psa_key_derivation_verify_bytes( psa_key_derivation_operation_t *operation, - const uint8_t *expected_output, - size_t output_length); + const uint8_t *expected, + size_t expected_length); /** Compare output data from a key derivation operation to an expected value * stored in a key object. @@ -3943,7 +4024,7 @@ psa_status_t psa_key_derivation_verify_bytes( * operation. The value of this key was likely * computed by a previous call to * psa_key_derivation_output_key() or - * psa_key_derivation_output_key_ext(). + * psa_key_derivation_output_key_custom(). * * \retval #PSA_SUCCESS \emptydescription * \retval #PSA_ERROR_INVALID_SIGNATURE @@ -4111,9 +4192,9 @@ psa_status_t psa_generate_random(uint8_t *output, * between 2^{n-1} and 2^n where n is the bit size specified in the * attributes. * - * \note This function is equivalent to calling psa_generate_key_ext() - * with the production parameters #PSA_KEY_PRODUCTION_PARAMETERS_INIT - * and `params_data_length == 0` (i.e. `params->data` is empty). + * \note This function is equivalent to calling psa_generate_key_custom() + * with the custom production parameters #PSA_CUSTOM_KEY_PARAMETERS_INIT + * and `custom_data_length == 0` (i.e. `custom_data` is empty). * * \param[in] attributes The attributes for the new key. * \param[out] key On success, an identifier for the newly created @@ -4153,7 +4234,7 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes, * See the description of psa_generate_key() for the operation of this * function with the default production parameters. In addition, this function * supports the following production customizations, described in more detail - * in the documentation of ::psa_key_production_parameters_t: + * in the documentation of ::psa_custom_key_parameters_t: * * - RSA keys: generation with a custom public exponent. * @@ -4161,6 +4242,64 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes, * versions of Mbed TLS. * * \param[in] attributes The attributes for the new key. + * \param[in] custom Customization parameters for the key generation. + * When this is #PSA_CUSTOM_KEY_PARAMETERS_INIT + * with \p custom_data_length = 0, + * this function is equivalent to + * psa_generate_key(). + * \param[in] custom_data Variable-length data associated with \c custom. + * \param custom_data_length + * Length of `custom_data` in bytes. + * \param[out] key On success, an identifier for the newly created + * key. For persistent keys, this is the key + * identifier defined in \p attributes. + * \c 0 on failure. + * + * \retval #PSA_SUCCESS + * Success. + * If the key is persistent, the key material and the key's metadata + * have been saved to persistent storage. + * \retval #PSA_ERROR_ALREADY_EXISTS + * This is an attempt to create a persistent key, and there is + * already a persistent key with the given identifier. + * \retval #PSA_ERROR_NOT_SUPPORTED \emptydescription + * \retval #PSA_ERROR_INVALID_ARGUMENT \emptydescription + * \retval #PSA_ERROR_INSUFFICIENT_MEMORY \emptydescription + * \retval #PSA_ERROR_INSUFFICIENT_ENTROPY \emptydescription + * \retval #PSA_ERROR_COMMUNICATION_FAILURE \emptydescription + * \retval #PSA_ERROR_HARDWARE_FAILURE \emptydescription + * \retval #PSA_ERROR_CORRUPTION_DETECTED \emptydescription + * \retval #PSA_ERROR_INSUFFICIENT_STORAGE \emptydescription + * \retval #PSA_ERROR_DATA_INVALID \emptydescription + * \retval #PSA_ERROR_DATA_CORRUPT \emptydescription + * \retval #PSA_ERROR_STORAGE_FAILURE \emptydescription + * \retval #PSA_ERROR_BAD_STATE + * The library has not been previously initialized by psa_crypto_init(). + * It is implementation-dependent whether a failure to initialize + * results in this error code. + */ +psa_status_t psa_generate_key_custom(const psa_key_attributes_t *attributes, + const psa_custom_key_parameters_t *custom, + const uint8_t *custom_data, + size_t custom_data_length, + mbedtls_svc_key_id_t *key); + +#ifndef __cplusplus +/* Omitted when compiling in C++, because one of the parameters is a + * pointer to a struct with a flexible array member, and that is not + * standard C++. + * https://github.com/Mbed-TLS/mbedtls/issues/9020 + */ +/** + * \brief Generate a key or key pair using custom production parameters. + * + * \note + * This is a deprecated variant of psa_key_derivation_output_key_custom(). + * It is equivalent except that the associated variable-length data + * is passed in `params->data` instead of a separate parameter. + * This function will be removed in a future version of Mbed TLS. + * + * \param[in] attributes The attributes for the new key. * \param[in] params Customization parameters for the key generation. * When this is #PSA_KEY_PRODUCTION_PARAMETERS_INIT * with \p params_data_length = 0, @@ -4196,12 +4335,11 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes, * It is implementation-dependent whether a failure to initialize * results in this error code. */ -#ifndef __cplusplus psa_status_t psa_generate_key_ext(const psa_key_attributes_t *attributes, const psa_key_production_parameters_t *params, size_t params_data_length, mbedtls_svc_key_id_t *key); -#endif +#endif /* !__cplusplus */ /**@}*/ diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h b/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h index 63fb29e85b..3a2af15180 100644 --- a/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h +++ b/thirdparty/mbedtls/include/psa/crypto_adjust_auto_enabled.h @@ -2,6 +2,8 @@ * \file psa/crypto_adjust_auto_enabled.h * \brief Adjust PSA configuration: enable always-on features * + * This is an internal header. Do not include it directly. + * * Always enable certain features which require a negligible amount of code * to implement, to avoid some edge cases in the configuration combinatorics. */ @@ -13,6 +15,14 @@ #ifndef PSA_CRYPTO_ADJUST_AUTO_ENABLED_H #define PSA_CRYPTO_ADJUST_AUTO_ENABLED_H +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + #define PSA_WANT_KEY_TYPE_DERIVE 1 #define PSA_WANT_KEY_TYPE_PASSWORD 1 #define PSA_WANT_KEY_TYPE_PASSWORD_HASH 1 diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h new file mode 100644 index 0000000000..92e9c4de28 --- /dev/null +++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_dependencies.h @@ -0,0 +1,51 @@ +/** + * \file psa/crypto_adjust_config_dependencies.h + * \brief Adjust PSA configuration by resolving some dependencies. + * + * This is an internal header. Do not include it directly. + * + * See docs/proposed/psa-conditional-inclusion-c.md. + * If the Mbed TLS implementation of a cryptographic mechanism A depends on a + * cryptographic mechanism B then if the cryptographic mechanism A is enabled + * and not accelerated enable B. Note that if A is enabled and accelerated, it + * is not necessary to enable B for A support. + */ +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ + +#ifndef PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H +#define PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H + +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + +#if (defined(PSA_WANT_ALG_TLS12_PRF) && \ + !defined(MBEDTLS_PSA_ACCEL_ALG_TLS12_PRF)) || \ + (defined(PSA_WANT_ALG_TLS12_PSK_TO_MS) && \ + !defined(MBEDTLS_PSA_ACCEL_ALG_TLS12_PSK_TO_MS)) || \ + (defined(PSA_WANT_ALG_HKDF) && \ + !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF)) || \ + (defined(PSA_WANT_ALG_HKDF_EXTRACT) && \ + !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF_EXTRACT)) || \ + (defined(PSA_WANT_ALG_HKDF_EXPAND) && \ + !defined(MBEDTLS_PSA_ACCEL_ALG_HKDF_EXPAND)) || \ + (defined(PSA_WANT_ALG_PBKDF2_HMAC) && \ + !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_HMAC)) +#define PSA_WANT_ALG_HMAC 1 +#define PSA_WANT_KEY_TYPE_HMAC 1 +#endif + +#if (defined(PSA_WANT_ALG_PBKDF2_AES_CMAC_PRF_128) && \ + !defined(MBEDTLS_PSA_ACCEL_ALG_PBKDF2_AES_CMAC_PRF_128)) +#define PSA_WANT_KEY_TYPE_AES 1 +#define PSA_WANT_ALG_CMAC 1 +#endif + +#endif /* PSA_CRYPTO_ADJUST_CONFIG_DEPENDENCIES_H */ diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h index 63afc0e402..cec39e01ce 100644 --- a/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h +++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_key_pair_types.h @@ -2,6 +2,8 @@ * \file psa/crypto_adjust_config_key_pair_types.h * \brief Adjust PSA configuration for key pair types. * + * This is an internal header. Do not include it directly. + * * See docs/proposed/psa-conditional-inclusion-c.md. * - Support non-basic operations in a keypair type implicitly enables basic * support for that keypair type. @@ -19,6 +21,14 @@ #ifndef PSA_CRYPTO_ADJUST_KEYPAIR_TYPES_H #define PSA_CRYPTO_ADJUST_KEYPAIR_TYPES_H +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + /***************************************************************** * ANYTHING -> BASIC ****************************************************************/ diff --git a/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h b/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h index 332b622c9b..54b116f434 100644 --- a/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h +++ b/thirdparty/mbedtls/include/psa/crypto_adjust_config_synonyms.h @@ -2,6 +2,8 @@ * \file psa/crypto_adjust_config_synonyms.h * \brief Adjust PSA configuration: enable quasi-synonyms * + * This is an internal header. Do not include it directly. + * * When two features require almost the same code, we automatically enable * both when either one is requested, to reduce the combinatorics of * possible configurations. @@ -14,6 +16,14 @@ #ifndef PSA_CRYPTO_ADJUST_CONFIG_SYNONYMS_H #define PSA_CRYPTO_ADJUST_CONFIG_SYNONYMS_H +#if !defined(MBEDTLS_CONFIG_FILES_READ) +#error "Do not include psa/crypto_adjust_*.h manually! This can lead to problems, " \ + "up to and including runtime errors such as buffer overflows. " \ + "If you're trying to fix a complaint from check_config.h, just remove " \ + "it from your configuration file: since Mbed TLS 3.0, it is included " \ + "automatically at the right point." +#endif /* */ + /****************************************************************/ /* De facto synonyms */ /****************************************************************/ diff --git a/thirdparty/mbedtls/include/psa/crypto_extra.h b/thirdparty/mbedtls/include/psa/crypto_extra.h index bd985e9b78..d276cd4c7f 100644 --- a/thirdparty/mbedtls/include/psa/crypto_extra.h +++ b/thirdparty/mbedtls/include/psa/crypto_extra.h @@ -154,6 +154,14 @@ static inline void psa_clear_key_slot_number( * specified in \p attributes. * * \param[in] attributes The attributes of the existing key. + * - The lifetime must be a persistent lifetime + * in a secure element. Volatile lifetimes are + * not currently supported. + * - The key identifier must be in the valid + * range for persistent keys. + * - The key type and size must be specified and + * must be consistent with the key material + * in the secure element. * * \retval #PSA_SUCCESS * The key was successfully registered. @@ -479,7 +487,7 @@ psa_status_t mbedtls_psa_external_get_random( * #PSA_KEY_ID_VENDOR_MIN and #PSA_KEY_ID_VENDOR_MAX and must not intersect * with any other set of implementation-chosen key identifiers. * - * This value is part of the library's ABI since changing it would invalidate + * This value is part of the library's API since changing it would invalidate * the values of built-in key identifiers in applications. */ #define MBEDTLS_PSA_KEY_ID_BUILTIN_MIN ((psa_key_id_t) 0x7fff0000) diff --git a/thirdparty/mbedtls/include/psa/crypto_struct.h b/thirdparty/mbedtls/include/psa/crypto_struct.h index e2c227b2eb..362e921a36 100644 --- a/thirdparty/mbedtls/include/psa/crypto_struct.h +++ b/thirdparty/mbedtls/include/psa/crypto_struct.h @@ -223,13 +223,36 @@ static inline struct psa_key_derivation_s psa_key_derivation_operation_init( return v; } +struct psa_custom_key_parameters_s { + /* Future versions may add other fields in this structure. */ + uint32_t flags; +}; + +/** The default production parameters for key generation or key derivation. + * + * Calling psa_generate_key_custom() or psa_key_derivation_output_key_custom() + * with `custom=PSA_CUSTOM_KEY_PARAMETERS_INIT` and `custom_data_length=0` is + * equivalent to calling psa_generate_key() or psa_key_derivation_output_key() + * respectively. + */ +#define PSA_CUSTOM_KEY_PARAMETERS_INIT { 0 } + #ifndef __cplusplus +/* Omitted when compiling in C++, because one of the parameters is a + * pointer to a struct with a flexible array member, and that is not + * standard C++. + * https://github.com/Mbed-TLS/mbedtls/issues/9020 + */ +/* This is a deprecated variant of `struct psa_custom_key_parameters_s`. + * It has exactly the same layout, plus an extra field which is a flexible + * array member. Thus a `const struct psa_key_production_parameters_s *` + * can be passed to any function that reads a + * `const struct psa_custom_key_parameters_s *`. + */ struct psa_key_production_parameters_s { - /* Future versions may add other fields in this structure. */ uint32_t flags; uint8_t data[]; }; -#endif /** The default production parameters for key generation or key derivation. * @@ -240,6 +263,7 @@ struct psa_key_production_parameters_s { * respectively. */ #define PSA_KEY_PRODUCTION_PARAMETERS_INIT { 0 } +#endif /* !__cplusplus */ struct psa_key_policy_s { psa_key_usage_t MBEDTLS_PRIVATE(usage); diff --git a/thirdparty/mbedtls/include/psa/crypto_types.h b/thirdparty/mbedtls/include/psa/crypto_types.h index a36b6ee65d..f831486f4e 100644 --- a/thirdparty/mbedtls/include/psa/crypto_types.h +++ b/thirdparty/mbedtls/include/psa/crypto_types.h @@ -457,6 +457,30 @@ typedef uint16_t psa_key_derivation_step_t; /** \brief Custom parameters for key generation or key derivation. * + * This is a structure type with at least the following field: + * + * - \c flags: an unsigned integer type. 0 for the default production parameters. + * + * Functions that take such a structure as input also take an associated + * input buffer \c custom_data of length \c custom_data_length. + * + * The interpretation of this structure and the associated \c custom_data + * parameter depend on the type of the created key. + * + * - #PSA_KEY_TYPE_RSA_KEY_PAIR: + * - \c flags: must be 0. + * - \c custom_data: the public exponent, in little-endian order. + * This must be an odd integer and must not be 1. + * Implementations must support 65537, should support 3 and may + * support other values. + * When not using a driver, Mbed TLS supports values up to \c INT_MAX. + * If this is empty, the default value 65537 is used. + * - Other key types: reserved for future use. \c flags must be 0. + */ +typedef struct psa_custom_key_parameters_s psa_custom_key_parameters_t; + +/** \brief Custom parameters for key generation or key derivation. + * * This is a structure type with at least the following fields: * * - \c flags: an unsigned integer type. 0 for the default production parameters. @@ -477,9 +501,7 @@ typedef uint16_t psa_key_derivation_step_t; * - Other key types: reserved for future use. \c flags must be 0. * */ -#ifndef __cplusplus typedef struct psa_key_production_parameters_s psa_key_production_parameters_t; -#endif /**@}*/ diff --git a/thirdparty/mbedtls/library/bignum.c b/thirdparty/mbedtls/library/bignum.c index c45fd5bf24..424490951d 100644 --- a/thirdparty/mbedtls/library/bignum.c +++ b/thirdparty/mbedtls/library/bignum.c @@ -27,6 +27,7 @@ #include "mbedtls/bignum.h" #include "bignum_core.h" +#include "bignum_internal.h" #include "bn_mul.h" #include "mbedtls/platform_util.h" #include "mbedtls/error.h" @@ -1610,9 +1611,13 @@ int mbedtls_mpi_mod_int(mbedtls_mpi_uint *r, const mbedtls_mpi *A, mbedtls_mpi_s return 0; } -int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A, - const mbedtls_mpi *E, const mbedtls_mpi *N, - mbedtls_mpi *prec_RR) +/* + * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value, + * this function is not constant time with respect to the exponent (parameter E). + */ +static int mbedtls_mpi_exp_mod_optionally_safe(mbedtls_mpi *X, const mbedtls_mpi *A, + const mbedtls_mpi *E, int E_public, + const mbedtls_mpi *N, mbedtls_mpi *prec_RR) { int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED; @@ -1695,7 +1700,11 @@ int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A, { mbedtls_mpi_uint mm = mbedtls_mpi_core_montmul_init(N->p); mbedtls_mpi_core_to_mont_rep(X->p, X->p, N->p, N->n, mm, RR.p, T); - mbedtls_mpi_core_exp_mod(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T); + if (E_public == MBEDTLS_MPI_IS_PUBLIC) { + mbedtls_mpi_core_exp_mod_unsafe(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T); + } else { + mbedtls_mpi_core_exp_mod(X->p, X->p, N->p, N->n, E->p, E->n, RR.p, T); + } mbedtls_mpi_core_from_mont_rep(X->p, X->p, N->p, N->n, mm, T); } @@ -1720,6 +1729,20 @@ cleanup: return ret; } +int mbedtls_mpi_exp_mod(mbedtls_mpi *X, const mbedtls_mpi *A, + const mbedtls_mpi *E, const mbedtls_mpi *N, + mbedtls_mpi *prec_RR) +{ + return mbedtls_mpi_exp_mod_optionally_safe(X, A, E, MBEDTLS_MPI_IS_SECRET, N, prec_RR); +} + +int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A, + const mbedtls_mpi *E, const mbedtls_mpi *N, + mbedtls_mpi *prec_RR) +{ + return mbedtls_mpi_exp_mod_optionally_safe(X, A, E, MBEDTLS_MPI_IS_PUBLIC, N, prec_RR); +} + /* * Greatest common divisor: G = gcd(A, B) (HAC 14.54) */ diff --git a/thirdparty/mbedtls/library/bignum_core.c b/thirdparty/mbedtls/library/bignum_core.c index 1a3e0b9b6f..4231554b84 100644 --- a/thirdparty/mbedtls/library/bignum_core.c +++ b/thirdparty/mbedtls/library/bignum_core.c @@ -746,8 +746,94 @@ static void exp_mod_precompute_window(const mbedtls_mpi_uint *A, } } +#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C) +// Set to a default that is neither MBEDTLS_MPI_IS_PUBLIC nor MBEDTLS_MPI_IS_SECRET +int mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC + MBEDTLS_MPI_IS_SECRET + 1; +#endif + +/* + * This function calculates the indices of the exponent where the exponentiation algorithm should + * start processing. + * + * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value, + * this function is not constant time with respect to the exponent (parameter E). + */ +static inline void exp_mod_calc_first_bit_optionally_safe(const mbedtls_mpi_uint *E, + size_t E_limbs, + int E_public, + size_t *E_limb_index, + size_t *E_bit_index) +{ + if (E_public == MBEDTLS_MPI_IS_PUBLIC) { + /* + * Skip leading zero bits. + */ + size_t E_bits = mbedtls_mpi_core_bitlen(E, E_limbs); + if (E_bits == 0) { + /* + * If E is 0 mbedtls_mpi_core_bitlen() returns 0. Even if that is the case, we will want + * to represent it as a single 0 bit and as such the bitlength will be 1. + */ + E_bits = 1; + } + + *E_limb_index = E_bits / biL; + *E_bit_index = E_bits % biL; + +#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C) + mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC; +#endif + } else { + /* + * Here we need to be constant time with respect to E and can't do anything better than + * start at the first allocated bit. + */ + *E_limb_index = E_limbs; + *E_bit_index = 0; +#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C) + // Only mark the codepath safe if there wasn't an unsafe codepath before + if (mbedtls_mpi_optionally_safe_codepath != MBEDTLS_MPI_IS_PUBLIC) { + mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_SECRET; + } +#endif + } +} + +/* + * Warning! If the parameter window_public has MBEDTLS_MPI_IS_PUBLIC as its value, this function is + * not constant time with respect to the window parameter and consequently the exponent of the + * exponentiation (parameter E of mbedtls_mpi_core_exp_mod_optionally_safe). + */ +static inline void exp_mod_table_lookup_optionally_safe(mbedtls_mpi_uint *Wselect, + mbedtls_mpi_uint *Wtable, + size_t AN_limbs, size_t welem, + mbedtls_mpi_uint window, + int window_public) +{ + if (window_public == MBEDTLS_MPI_IS_PUBLIC) { + memcpy(Wselect, Wtable + window * AN_limbs, AN_limbs * ciL); +#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C) + mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC; +#endif + } else { + /* Select Wtable[window] without leaking window through + * memory access patterns. */ + mbedtls_mpi_core_ct_uint_table_lookup(Wselect, Wtable, + AN_limbs, welem, window); +#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C) + // Only mark the codepath safe if there wasn't an unsafe codepath before + if (mbedtls_mpi_optionally_safe_codepath != MBEDTLS_MPI_IS_PUBLIC) { + mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_SECRET; + } +#endif + } +} + /* Exponentiation: X := A^E mod N. * + * Warning! If the parameter E_public has MBEDTLS_MPI_IS_PUBLIC as its value, + * this function is not constant time with respect to the exponent (parameter E). + * * A must already be in Montgomery form. * * As in other bignum functions, assume that AN_limbs and E_limbs are nonzero. @@ -758,16 +844,25 @@ static void exp_mod_precompute_window(const mbedtls_mpi_uint *A, * (The difference is that the body in our loop processes a single bit instead * of a full window.) */ -void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X, - const mbedtls_mpi_uint *A, - const mbedtls_mpi_uint *N, - size_t AN_limbs, - const mbedtls_mpi_uint *E, - size_t E_limbs, - const mbedtls_mpi_uint *RR, - mbedtls_mpi_uint *T) +static void mbedtls_mpi_core_exp_mod_optionally_safe(mbedtls_mpi_uint *X, + const mbedtls_mpi_uint *A, + const mbedtls_mpi_uint *N, + size_t AN_limbs, + const mbedtls_mpi_uint *E, + size_t E_limbs, + int E_public, + const mbedtls_mpi_uint *RR, + mbedtls_mpi_uint *T) { - const size_t wsize = exp_mod_get_window_size(E_limbs * biL); + /* We'll process the bits of E from most significant + * (limb_index=E_limbs-1, E_bit_index=biL-1) to least significant + * (limb_index=0, E_bit_index=0). */ + size_t E_limb_index; + size_t E_bit_index; + exp_mod_calc_first_bit_optionally_safe(E, E_limbs, E_public, + &E_limb_index, &E_bit_index); + + const size_t wsize = exp_mod_get_window_size(E_limb_index * biL); const size_t welem = ((size_t) 1) << wsize; /* This is how we will use the temporary storage T, which must have space @@ -786,7 +881,7 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X, const mbedtls_mpi_uint mm = mbedtls_mpi_core_montmul_init(N); - /* Set Wtable[i] = A^(2^i) (in Montgomery representation) */ + /* Set Wtable[i] = A^i (in Montgomery representation) */ exp_mod_precompute_window(A, N, AN_limbs, mm, RR, welem, Wtable, temp); @@ -798,11 +893,6 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X, /* X = 1 (in Montgomery presentation) initially */ memcpy(X, Wtable, AN_limbs * ciL); - /* We'll process the bits of E from most significant - * (limb_index=E_limbs-1, E_bit_index=biL-1) to least significant - * (limb_index=0, E_bit_index=0). */ - size_t E_limb_index = E_limbs; - size_t E_bit_index = 0; /* At any given time, window contains window_bits bits from E. * window_bits can go up to wsize. */ size_t window_bits = 0; @@ -828,10 +918,9 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X, * when we've finished processing the exponent. */ if (window_bits == wsize || (E_bit_index == 0 && E_limb_index == 0)) { - /* Select Wtable[window] without leaking window through - * memory access patterns. */ - mbedtls_mpi_core_ct_uint_table_lookup(Wselect, Wtable, - AN_limbs, welem, window); + + exp_mod_table_lookup_optionally_safe(Wselect, Wtable, AN_limbs, welem, + window, E_public); /* Multiply X by the selected element. */ mbedtls_mpi_core_montmul(X, X, Wselect, AN_limbs, N, AN_limbs, mm, temp); @@ -841,6 +930,42 @@ void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X, } while (!(E_bit_index == 0 && E_limb_index == 0)); } +void mbedtls_mpi_core_exp_mod(mbedtls_mpi_uint *X, + const mbedtls_mpi_uint *A, + const mbedtls_mpi_uint *N, size_t AN_limbs, + const mbedtls_mpi_uint *E, size_t E_limbs, + const mbedtls_mpi_uint *RR, + mbedtls_mpi_uint *T) +{ + mbedtls_mpi_core_exp_mod_optionally_safe(X, + A, + N, + AN_limbs, + E, + E_limbs, + MBEDTLS_MPI_IS_SECRET, + RR, + T); +} + +void mbedtls_mpi_core_exp_mod_unsafe(mbedtls_mpi_uint *X, + const mbedtls_mpi_uint *A, + const mbedtls_mpi_uint *N, size_t AN_limbs, + const mbedtls_mpi_uint *E, size_t E_limbs, + const mbedtls_mpi_uint *RR, + mbedtls_mpi_uint *T) +{ + mbedtls_mpi_core_exp_mod_optionally_safe(X, + A, + N, + AN_limbs, + E, + E_limbs, + MBEDTLS_MPI_IS_PUBLIC, + RR, + T); +} + mbedtls_mpi_uint mbedtls_mpi_core_sub_int(mbedtls_mpi_uint *X, const mbedtls_mpi_uint *A, mbedtls_mpi_uint c, /* doubles as carry */ diff --git a/thirdparty/mbedtls/library/bignum_core.h b/thirdparty/mbedtls/library/bignum_core.h index 92c8d47db5..cf6485a148 100644 --- a/thirdparty/mbedtls/library/bignum_core.h +++ b/thirdparty/mbedtls/library/bignum_core.h @@ -90,6 +90,27 @@ #define GET_BYTE(X, i) \ (((X)[(i) / ciL] >> (((i) % ciL) * 8)) & 0xff) +/* Constants to identify whether a value is public or secret. If a parameter is marked as secret by + * this constant, the function must be constant time with respect to the parameter. + * + * This is only needed for functions with the _optionally_safe postfix. All other functions have + * fixed behavior that can't be changed at runtime and are constant time with respect to their + * parameters as prescribed by their documentation or by conventions in their module's documentation. + * + * Parameters should be named X_public where X is the name of the + * corresponding input parameter. + * + * Implementation should always check using + * if (X_public == MBEDTLS_MPI_IS_PUBLIC) { + * // unsafe path + * } else { + * // safe path + * } + * not the other way round, in order to prevent misuse. (This is, if a value + * other than the two below is passed, default to the safe path.) */ +#define MBEDTLS_MPI_IS_PUBLIC 0x2a2a2a2a +#define MBEDTLS_MPI_IS_SECRET 0 + /** Count leading zero bits in a given integer. * * \warning The result is undefined if \p a == 0 @@ -605,6 +626,42 @@ int mbedtls_mpi_core_random(mbedtls_mpi_uint *X, size_t mbedtls_mpi_core_exp_mod_working_limbs(size_t AN_limbs, size_t E_limbs); /** + * \brief Perform a modular exponentiation with public or secret exponent: + * X = A^E mod N, where \p A is already in Montgomery form. + * + * \warning This function is not constant time with respect to \p E (the exponent). + * + * \p X may be aliased to \p A, but not to \p RR or \p E, even if \p E_limbs == + * \p AN_limbs. + * + * \param[out] X The destination MPI, as a little endian array of length + * \p AN_limbs. + * \param[in] A The base MPI, as a little endian array of length \p AN_limbs. + * Must be in Montgomery form. + * \param[in] N The modulus, as a little endian array of length \p AN_limbs. + * \param AN_limbs The number of limbs in \p X, \p A, \p N, \p RR. + * \param[in] E The exponent, as a little endian array of length \p E_limbs. + * \param E_limbs The number of limbs in \p E. + * \param[in] RR The precomputed residue of 2^{2*biL} modulo N, as a little + * endian array of length \p AN_limbs. + * \param[in,out] T Temporary storage of at least the number of limbs returned + * by `mbedtls_mpi_core_exp_mod_working_limbs()`. + * Its initial content is unused and its final content is + * indeterminate. + * It must not alias or otherwise overlap any of the other + * parameters. + * It is up to the caller to zeroize \p T when it is no + * longer needed, and before freeing it if it was dynamically + * allocated. + */ +void mbedtls_mpi_core_exp_mod_unsafe(mbedtls_mpi_uint *X, + const mbedtls_mpi_uint *A, + const mbedtls_mpi_uint *N, size_t AN_limbs, + const mbedtls_mpi_uint *E, size_t E_limbs, + const mbedtls_mpi_uint *RR, + mbedtls_mpi_uint *T); + +/** * \brief Perform a modular exponentiation with secret exponent: * X = A^E mod N, where \p A is already in Montgomery form. * @@ -760,4 +817,17 @@ void mbedtls_mpi_core_from_mont_rep(mbedtls_mpi_uint *X, mbedtls_mpi_uint mm, mbedtls_mpi_uint *T); +/* + * Can't define thread local variables with our abstraction layer: do nothing if threading is on. + */ +#if defined(MBEDTLS_TEST_HOOKS) && !defined(MBEDTLS_THREADING_C) +extern int mbedtls_mpi_optionally_safe_codepath; + +static inline void mbedtls_mpi_optionally_safe_codepath_reset(void) +{ + // Set to a default that is neither MBEDTLS_MPI_IS_PUBLIC nor MBEDTLS_MPI_IS_SECRET + mbedtls_mpi_optionally_safe_codepath = MBEDTLS_MPI_IS_PUBLIC + MBEDTLS_MPI_IS_SECRET + 1; +} +#endif + #endif /* MBEDTLS_BIGNUM_CORE_H */ diff --git a/thirdparty/mbedtls/library/bignum_internal.h b/thirdparty/mbedtls/library/bignum_internal.h new file mode 100644 index 0000000000..aceaf55ea2 --- /dev/null +++ b/thirdparty/mbedtls/library/bignum_internal.h @@ -0,0 +1,50 @@ +/** + * \file bignum_internal.h + * + * \brief Internal-only bignum public-key cryptosystem API. + * + * This file declares bignum-related functions that are to be used + * only from within the Mbed TLS library itself. + * + */ +/* + * Copyright The Mbed TLS Contributors + * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + */ +#ifndef MBEDTLS_BIGNUM_INTERNAL_H +#define MBEDTLS_BIGNUM_INTERNAL_H + +/** + * \brief Perform a modular exponentiation: X = A^E mod N + * + * \warning This function is not constant time with respect to \p E (the exponent). + * + * \param X The destination MPI. This must point to an initialized MPI. + * This must not alias E or N. + * \param A The base of the exponentiation. + * This must point to an initialized MPI. + * \param E The exponent MPI. This must point to an initialized MPI. + * \param N The base for the modular reduction. This must point to an + * initialized MPI. + * \param prec_RR A helper MPI depending solely on \p N which can be used to + * speed-up multiple modular exponentiations for the same value + * of \p N. This may be \c NULL. If it is not \c NULL, it must + * point to an initialized MPI. If it hasn't been used after + * the call to mbedtls_mpi_init(), this function will compute + * the helper value and store it in \p prec_RR for reuse on + * subsequent calls to this function. Otherwise, the function + * will assume that \p prec_RR holds the helper value set by a + * previous call to mbedtls_mpi_exp_mod(), and reuse it. + * + * \return \c 0 if successful. + * \return #MBEDTLS_ERR_MPI_ALLOC_FAILED if a memory allocation failed. + * \return #MBEDTLS_ERR_MPI_BAD_INPUT_DATA if \c N is negative or + * even, or if \c E is negative. + * \return Another negative error code on different kinds of failures. + * + */ +int mbedtls_mpi_exp_mod_unsafe(mbedtls_mpi *X, const mbedtls_mpi *A, + const mbedtls_mpi *E, const mbedtls_mpi *N, + mbedtls_mpi *prec_RR); + +#endif /* bignum_internal.h */ diff --git a/thirdparty/mbedtls/library/block_cipher.c b/thirdparty/mbedtls/library/block_cipher.c index 04cd7fb444..51cdcdf46b 100644 --- a/thirdparty/mbedtls/library/block_cipher.c +++ b/thirdparty/mbedtls/library/block_cipher.c @@ -51,6 +51,10 @@ static int mbedtls_cipher_error_from_psa(psa_status_t status) void mbedtls_block_cipher_free(mbedtls_block_cipher_context_t *ctx) { + if (ctx == NULL) { + return; + } + #if defined(MBEDTLS_BLOCK_CIPHER_SOME_PSA) if (ctx->engine == MBEDTLS_BLOCK_CIPHER_ENGINE_PSA) { psa_destroy_key(ctx->psa_key_id); diff --git a/thirdparty/mbedtls/library/cipher.c b/thirdparty/mbedtls/library/cipher.c index 0683677eda..7f4c121492 100644 --- a/thirdparty/mbedtls/library/cipher.c +++ b/thirdparty/mbedtls/library/cipher.c @@ -849,6 +849,9 @@ static int get_pkcs_padding(unsigned char *input, size_t input_len, } padding_len = input[input_len - 1]; + if (padding_len == 0 || padding_len > input_len) { + return MBEDTLS_ERR_CIPHER_INVALID_PADDING; + } *data_len = input_len - padding_len; mbedtls_ct_condition_t bad = mbedtls_ct_uint_gt(padding_len, input_len); diff --git a/thirdparty/mbedtls/library/common.h b/thirdparty/mbedtls/library/common.h index 3936ffdfe1..7bb2674293 100644 --- a/thirdparty/mbedtls/library/common.h +++ b/thirdparty/mbedtls/library/common.h @@ -352,17 +352,19 @@ static inline void mbedtls_xor_no_simd(unsigned char *r, #endif /* Always provide a static assert macro, so it can be used unconditionally. - * It will expand to nothing on some systems. - * Can be used outside functions (but don't add a trailing ';' in that case: - * the semicolon is included here to avoid triggering -Wextra-semi when - * MBEDTLS_STATIC_ASSERT() expands to nothing). - * Can't use the C11-style `defined(static_assert)` on FreeBSD, since it + * It does nothing on systems where we don't know how to define a static assert. + */ +/* Can't use the C11-style `defined(static_assert)` on FreeBSD, since it * defines static_assert even with -std=c99, but then complains about it. */ #if defined(static_assert) && !defined(__FreeBSD__) -#define MBEDTLS_STATIC_ASSERT(expr, msg) static_assert(expr, msg); +#define MBEDTLS_STATIC_ASSERT(expr, msg) static_assert(expr, msg) #else -#define MBEDTLS_STATIC_ASSERT(expr, msg) +/* Make sure `MBEDTLS_STATIC_ASSERT(expr, msg);` is valid both inside and + * outside a function. We choose a struct declaration, which can be repeated + * any number of times and does not need a matching definition. */ +#define MBEDTLS_STATIC_ASSERT(expr, msg) \ + struct ISO_C_does_not_allow_extra_semicolon_outside_of_a_function #endif #if defined(__has_builtin) diff --git a/thirdparty/mbedtls/library/ctr_drbg.c b/thirdparty/mbedtls/library/ctr_drbg.c index 66d9d28c58..b82044eb7d 100644 --- a/thirdparty/mbedtls/library/ctr_drbg.c +++ b/thirdparty/mbedtls/library/ctr_drbg.c @@ -26,13 +26,13 @@ #endif /* Using error translation functions from PSA to MbedTLS */ -#if !defined(MBEDTLS_AES_C) +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) #include "psa_util_internal.h" #endif #include "mbedtls/platform.h" -#if !defined(MBEDTLS_AES_C) +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) static psa_status_t ctr_drbg_setup_psa_context(mbedtls_ctr_drbg_psa_context *psa_ctx, unsigned char *key, size_t key_len) { @@ -73,11 +73,11 @@ static void ctr_drbg_destroy_psa_contex(mbedtls_ctr_drbg_psa_context *psa_ctx) void mbedtls_ctr_drbg_init(mbedtls_ctr_drbg_context *ctx) { memset(ctx, 0, sizeof(mbedtls_ctr_drbg_context)); -#if defined(MBEDTLS_AES_C) - mbedtls_aes_init(&ctx->aes_ctx); -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) ctx->psa_ctx.key_id = MBEDTLS_SVC_KEY_ID_INIT; ctx->psa_ctx.operation = psa_cipher_operation_init(); +#else + mbedtls_aes_init(&ctx->aes_ctx); #endif /* Indicate that the entropy nonce length is not set explicitly. * See mbedtls_ctr_drbg_set_nonce_len(). */ @@ -102,10 +102,10 @@ void mbedtls_ctr_drbg_free(mbedtls_ctr_drbg_context *ctx) mbedtls_mutex_free(&ctx->mutex); } #endif -#if defined(MBEDTLS_AES_C) - mbedtls_aes_free(&ctx->aes_ctx); -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) ctr_drbg_destroy_psa_contex(&ctx->psa_ctx); +#else + mbedtls_aes_free(&ctx->aes_ctx); #endif mbedtls_platform_zeroize(ctx, sizeof(mbedtls_ctr_drbg_context)); ctx->reseed_interval = MBEDTLS_CTR_DRBG_RESEED_INTERVAL; @@ -168,15 +168,15 @@ static int block_cipher_df(unsigned char *output, unsigned char chain[MBEDTLS_CTR_DRBG_BLOCKSIZE]; unsigned char *p, *iv; int ret = 0; -#if defined(MBEDTLS_AES_C) - mbedtls_aes_context aes_ctx; -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) psa_status_t status; size_t tmp_len; mbedtls_ctr_drbg_psa_context psa_ctx; psa_ctx.key_id = MBEDTLS_SVC_KEY_ID_INIT; psa_ctx.operation = psa_cipher_operation_init(); +#else + mbedtls_aes_context aes_ctx; #endif int i, j; @@ -209,19 +209,19 @@ static int block_cipher_df(unsigned char *output, key[i] = i; } -#if defined(MBEDTLS_AES_C) +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) + status = ctr_drbg_setup_psa_context(&psa_ctx, key, sizeof(key)); + if (status != PSA_SUCCESS) { + ret = psa_generic_status_to_mbedtls(status); + goto exit; + } +#else mbedtls_aes_init(&aes_ctx); if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, key, MBEDTLS_CTR_DRBG_KEYBITS)) != 0) { goto exit; } -#else - status = ctr_drbg_setup_psa_context(&psa_ctx, key, sizeof(key)); - if (status != PSA_SUCCESS) { - ret = psa_generic_status_to_mbedtls(status); - goto exit; - } #endif /* @@ -238,18 +238,18 @@ static int block_cipher_df(unsigned char *output, use_len -= (use_len >= MBEDTLS_CTR_DRBG_BLOCKSIZE) ? MBEDTLS_CTR_DRBG_BLOCKSIZE : use_len; -#if defined(MBEDTLS_AES_C) - if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT, - chain, chain)) != 0) { - goto exit; - } -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) status = psa_cipher_update(&psa_ctx.operation, chain, MBEDTLS_CTR_DRBG_BLOCKSIZE, chain, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len); if (status != PSA_SUCCESS) { ret = psa_generic_status_to_mbedtls(status); goto exit; } +#else + if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT, + chain, chain)) != 0) { + goto exit; + } #endif } @@ -264,12 +264,7 @@ static int block_cipher_df(unsigned char *output, /* * Do final encryption with reduced data */ -#if defined(MBEDTLS_AES_C) - if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, tmp, - MBEDTLS_CTR_DRBG_KEYBITS)) != 0) { - goto exit; - } -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) ctr_drbg_destroy_psa_contex(&psa_ctx); status = ctr_drbg_setup_psa_context(&psa_ctx, tmp, MBEDTLS_CTR_DRBG_KEYSIZE); @@ -277,32 +272,37 @@ static int block_cipher_df(unsigned char *output, ret = psa_generic_status_to_mbedtls(status); goto exit; } +#else + if ((ret = mbedtls_aes_setkey_enc(&aes_ctx, tmp, + MBEDTLS_CTR_DRBG_KEYBITS)) != 0) { + goto exit; + } #endif iv = tmp + MBEDTLS_CTR_DRBG_KEYSIZE; p = output; for (j = 0; j < MBEDTLS_CTR_DRBG_SEEDLEN; j += MBEDTLS_CTR_DRBG_BLOCKSIZE) { -#if defined(MBEDTLS_AES_C) - if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT, - iv, iv)) != 0) { - goto exit; - } -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) status = psa_cipher_update(&psa_ctx.operation, iv, MBEDTLS_CTR_DRBG_BLOCKSIZE, iv, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len); if (status != PSA_SUCCESS) { ret = psa_generic_status_to_mbedtls(status); goto exit; } +#else + if ((ret = mbedtls_aes_crypt_ecb(&aes_ctx, MBEDTLS_AES_ENCRYPT, + iv, iv)) != 0) { + goto exit; + } #endif memcpy(p, iv, MBEDTLS_CTR_DRBG_BLOCKSIZE); p += MBEDTLS_CTR_DRBG_BLOCKSIZE; } exit: -#if defined(MBEDTLS_AES_C) - mbedtls_aes_free(&aes_ctx); -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) ctr_drbg_destroy_psa_contex(&psa_ctx); +#else + mbedtls_aes_free(&aes_ctx); #endif /* * tidy up the stack @@ -336,7 +336,7 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx, unsigned char *p = tmp; int j; int ret = 0; -#if !defined(MBEDTLS_AES_C) +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) psa_status_t status; size_t tmp_len; #endif @@ -352,18 +352,18 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx, /* * Crypt counter block */ -#if defined(MBEDTLS_AES_C) - if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT, - ctx->counter, p)) != 0) { - goto exit; - } -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) status = psa_cipher_update(&ctx->psa_ctx.operation, ctx->counter, sizeof(ctx->counter), p, MBEDTLS_CTR_DRBG_BLOCKSIZE, &tmp_len); if (status != PSA_SUCCESS) { ret = psa_generic_status_to_mbedtls(status); goto exit; } +#else + if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT, + ctx->counter, p)) != 0) { + goto exit; + } #endif p += MBEDTLS_CTR_DRBG_BLOCKSIZE; @@ -374,12 +374,7 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx, /* * Update key and counter */ -#if defined(MBEDTLS_AES_C) - if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, tmp, - MBEDTLS_CTR_DRBG_KEYBITS)) != 0) { - goto exit; - } -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) ctr_drbg_destroy_psa_contex(&ctx->psa_ctx); status = ctr_drbg_setup_psa_context(&ctx->psa_ctx, tmp, MBEDTLS_CTR_DRBG_KEYSIZE); @@ -387,6 +382,11 @@ static int ctr_drbg_update_internal(mbedtls_ctr_drbg_context *ctx, ret = psa_generic_status_to_mbedtls(status); goto exit; } +#else + if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, tmp, + MBEDTLS_CTR_DRBG_KEYBITS)) != 0) { + goto exit; + } #endif memcpy(ctx->counter, tmp + MBEDTLS_CTR_DRBG_KEYSIZE, MBEDTLS_CTR_DRBG_BLOCKSIZE); @@ -564,12 +564,7 @@ int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context *ctx, good_nonce_len(ctx->entropy_len)); /* Initialize with an empty key. */ -#if defined(MBEDTLS_AES_C) - if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, key, - MBEDTLS_CTR_DRBG_KEYBITS)) != 0) { - return ret; - } -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) psa_status_t status; status = ctr_drbg_setup_psa_context(&ctx->psa_ctx, key, MBEDTLS_CTR_DRBG_KEYSIZE); @@ -577,6 +572,11 @@ int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context *ctx, ret = psa_generic_status_to_mbedtls(status); return status; } +#else + if ((ret = mbedtls_aes_setkey_enc(&ctx->aes_ctx, key, + MBEDTLS_CTR_DRBG_KEYBITS)) != 0) { + return ret; + } #endif /* Do the initial seeding. */ @@ -655,12 +655,7 @@ int mbedtls_ctr_drbg_random_with_add(void *p_rng, /* * Crypt counter block */ -#if defined(MBEDTLS_AES_C) - if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT, - ctx->counter, locals.tmp)) != 0) { - goto exit; - } -#else +#if defined(MBEDTLS_CTR_DRBG_USE_PSA_CRYPTO) psa_status_t status; size_t tmp_len; @@ -670,6 +665,11 @@ int mbedtls_ctr_drbg_random_with_add(void *p_rng, ret = psa_generic_status_to_mbedtls(status); goto exit; } +#else + if ((ret = mbedtls_aes_crypt_ecb(&ctx->aes_ctx, MBEDTLS_AES_ENCRYPT, + ctx->counter, locals.tmp)) != 0) { + goto exit; + } #endif use_len = (output_len > MBEDTLS_CTR_DRBG_BLOCKSIZE) diff --git a/thirdparty/mbedtls/library/entropy.c b/thirdparty/mbedtls/library/entropy.c index e3bc8516e2..7dcf067a52 100644 --- a/thirdparty/mbedtls/library/entropy.c +++ b/thirdparty/mbedtls/library/entropy.c @@ -61,6 +61,10 @@ void mbedtls_entropy_init(mbedtls_entropy_context *ctx) void mbedtls_entropy_free(mbedtls_entropy_context *ctx) { + if (ctx == NULL) { + return; + } + /* If the context was already free, don't call free() again. * This is important for mutexes which don't allow double-free. */ if (ctx->accumulator_started == -1) { diff --git a/thirdparty/mbedtls/library/entropy_poll.c b/thirdparty/mbedtls/library/entropy_poll.c index 794ee03a83..611768cd85 100644 --- a/thirdparty/mbedtls/library/entropy_poll.c +++ b/thirdparty/mbedtls/library/entropy_poll.c @@ -5,10 +5,12 @@ * SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */ -#if defined(__linux__) || defined(__midipix__) && !defined(_GNU_SOURCE) +#if defined(__linux__) || defined(__midipix__) /* Ensure that syscall() is available even when compiling with -std=c99 */ +#if !defined(_GNU_SOURCE) #define _GNU_SOURCE #endif +#endif #include "common.h" diff --git a/thirdparty/mbedtls/library/error.c b/thirdparty/mbedtls/library/error.c index 84b637aeb2..6ad7162ab5 100644 --- a/thirdparty/mbedtls/library/error.c +++ b/thirdparty/mbedtls/library/error.c @@ -418,7 +418,7 @@ const char *mbedtls_high_level_strerr(int error_code) case -(MBEDTLS_ERR_SSL_BAD_CERTIFICATE): return( "SSL - Processing of the Certificate handshake message failed" ); case -(MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET): - return( "SSL - * Received NewSessionTicket Post Handshake Message. This error code is experimental and may be changed or removed without notice" ); + return( "SSL - A TLS 1.3 NewSessionTicket message has been received" ); case -(MBEDTLS_ERR_SSL_CANNOT_READ_EARLY_DATA): return( "SSL - Not possible to read early data" ); case -(MBEDTLS_ERR_SSL_RECEIVED_EARLY_DATA): diff --git a/thirdparty/mbedtls/library/lmots.c b/thirdparty/mbedtls/library/lmots.c index c7091b49e1..c51cb41ece 100644 --- a/thirdparty/mbedtls/library/lmots.c +++ b/thirdparty/mbedtls/library/lmots.c @@ -387,6 +387,10 @@ void mbedtls_lmots_public_init(mbedtls_lmots_public_t *ctx) void mbedtls_lmots_public_free(mbedtls_lmots_public_t *ctx) { + if (ctx == NULL) { + return; + } + mbedtls_platform_zeroize(ctx, sizeof(*ctx)); } @@ -556,6 +560,10 @@ void mbedtls_lmots_private_init(mbedtls_lmots_private_t *ctx) void mbedtls_lmots_private_free(mbedtls_lmots_private_t *ctx) { + if (ctx == NULL) { + return; + } + mbedtls_platform_zeroize(ctx, sizeof(*ctx)); } diff --git a/thirdparty/mbedtls/library/lms.c b/thirdparty/mbedtls/library/lms.c index 8d3cae0524..7f7bec068b 100644 --- a/thirdparty/mbedtls/library/lms.c +++ b/thirdparty/mbedtls/library/lms.c @@ -229,6 +229,10 @@ void mbedtls_lms_public_init(mbedtls_lms_public_t *ctx) void mbedtls_lms_public_free(mbedtls_lms_public_t *ctx) { + if (ctx == NULL) { + return; + } + mbedtls_platform_zeroize(ctx, sizeof(*ctx)); } @@ -528,6 +532,10 @@ void mbedtls_lms_private_init(mbedtls_lms_private_t *ctx) void mbedtls_lms_private_free(mbedtls_lms_private_t *ctx) { + if (ctx == NULL) { + return; + } + unsigned int idx; if (ctx->have_private_key) { diff --git a/thirdparty/mbedtls/library/md.c b/thirdparty/mbedtls/library/md.c index 12a3ea2374..c95846aa04 100644 --- a/thirdparty/mbedtls/library/md.c +++ b/thirdparty/mbedtls/library/md.c @@ -41,7 +41,7 @@ #include "mbedtls/sha512.h" #include "mbedtls/sha3.h" -#if defined(MBEDTLS_PSA_CRYPTO_C) +#if defined(MBEDTLS_PSA_CRYPTO_CLIENT) #include <psa/crypto.h> #include "md_psa.h" #include "psa_util_internal.h" @@ -761,13 +761,13 @@ mbedtls_md_type_t mbedtls_md_get_type(const mbedtls_md_info_t *md_info) return md_info->type; } -#if defined(MBEDTLS_PSA_CRYPTO_C) +#if defined(MBEDTLS_PSA_CRYPTO_CLIENT) int mbedtls_md_error_from_psa(psa_status_t status) { return PSA_TO_MBEDTLS_ERR_LIST(status, psa_to_md_errors, psa_generic_status_to_mbedtls); } -#endif /* MBEDTLS_PSA_CRYPTO_C */ +#endif /* MBEDTLS_PSA_CRYPTO_CLIENT */ /************************************************************************ diff --git a/thirdparty/mbedtls/library/net_sockets.c b/thirdparty/mbedtls/library/net_sockets.c index edec5876ad..ef89a88ef0 100644 --- a/thirdparty/mbedtls/library/net_sockets.c +++ b/thirdparty/mbedtls/library/net_sockets.c @@ -683,7 +683,7 @@ void mbedtls_net_close(mbedtls_net_context *ctx) */ void mbedtls_net_free(mbedtls_net_context *ctx) { - if (ctx->fd == -1) { + if (ctx == NULL || ctx->fd == -1) { return; } diff --git a/thirdparty/mbedtls/library/nist_kw.c b/thirdparty/mbedtls/library/nist_kw.c index f15425b8bd..8faafe43f1 100644 --- a/thirdparty/mbedtls/library/nist_kw.c +++ b/thirdparty/mbedtls/library/nist_kw.c @@ -102,6 +102,10 @@ int mbedtls_nist_kw_setkey(mbedtls_nist_kw_context *ctx, */ void mbedtls_nist_kw_free(mbedtls_nist_kw_context *ctx) { + if (ctx == NULL) { + return; + } + mbedtls_cipher_free(&ctx->cipher_ctx); mbedtls_platform_zeroize(ctx, sizeof(mbedtls_nist_kw_context)); } diff --git a/thirdparty/mbedtls/library/pem.c b/thirdparty/mbedtls/library/pem.c index 0fee5df43a..0207601456 100644 --- a/thirdparty/mbedtls/library/pem.c +++ b/thirdparty/mbedtls/library/pem.c @@ -481,6 +481,10 @@ int mbedtls_pem_read_buffer(mbedtls_pem_context *ctx, const char *header, const void mbedtls_pem_free(mbedtls_pem_context *ctx) { + if (ctx == NULL) { + return; + } + if (ctx->buf != NULL) { mbedtls_zeroize_and_free(ctx->buf, ctx->buflen); } diff --git a/thirdparty/mbedtls/library/pk.c b/thirdparty/mbedtls/library/pk.c index 097777f2c0..3fe51ea34f 100644 --- a/thirdparty/mbedtls/library/pk.c +++ b/thirdparty/mbedtls/library/pk.c @@ -868,7 +868,6 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, psa_status_t status; psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT; psa_key_type_t key_type; - psa_algorithm_t alg_type; size_t key_bits; /* Use a buffer size large enough to contain either a key pair or public key. */ unsigned char exp_key[PSA_EXPORT_KEY_PAIR_OR_PUBLIC_MAX_SIZE]; @@ -899,7 +898,6 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, key_type = PSA_KEY_TYPE_PUBLIC_KEY_OF_KEY_PAIR(key_type); } key_bits = psa_get_key_bits(&key_attr); - alg_type = psa_get_key_algorithm(&key_attr); #if defined(MBEDTLS_RSA_C) if ((key_type == PSA_KEY_TYPE_RSA_KEY_PAIR) || @@ -919,6 +917,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, goto exit; } + psa_algorithm_t alg_type = psa_get_key_algorithm(&key_attr); mbedtls_md_type_t md_type = MBEDTLS_MD_NONE; if (PSA_ALG_GET_HASH(alg_type) != PSA_ALG_ANY_HASH) { md_type = mbedtls_md_type_from_psa_alg(alg_type); @@ -968,6 +967,7 @@ static int copy_from_psa(mbedtls_svc_key_id_t key_id, } else #endif /* MBEDTLS_PK_HAVE_ECC_KEYS */ { + (void) key_bits; return MBEDTLS_ERR_PK_BAD_INPUT_DATA; } @@ -1327,43 +1327,19 @@ int mbedtls_pk_sign_ext(mbedtls_pk_type_t pk_type, } if (mbedtls_pk_get_type(ctx) == MBEDTLS_PK_OPAQUE) { - psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT; - psa_algorithm_t psa_alg, sign_alg; -#if defined(MBEDTLS_PSA_CRYPTO_C) - psa_algorithm_t psa_enrollment_alg; -#endif /* MBEDTLS_PSA_CRYPTO_C */ psa_status_t status; - status = psa_get_key_attributes(ctx->priv_id, &key_attr); - if (status != PSA_SUCCESS) { - return PSA_PK_RSA_TO_MBEDTLS_ERR(status); - } - psa_alg = psa_get_key_algorithm(&key_attr); -#if defined(MBEDTLS_PSA_CRYPTO_C) - psa_enrollment_alg = psa_get_key_enrollment_algorithm(&key_attr); -#endif /* MBEDTLS_PSA_CRYPTO_C */ - psa_reset_key_attributes(&key_attr); - - /* Since we're PK type is MBEDTLS_PK_RSASSA_PSS at least one between - * alg and enrollment alg should be of type RSA_PSS. */ - if (PSA_ALG_IS_RSA_PSS(psa_alg)) { - sign_alg = psa_alg; - } -#if defined(MBEDTLS_PSA_CRYPTO_C) - else if (PSA_ALG_IS_RSA_PSS(psa_enrollment_alg)) { - sign_alg = psa_enrollment_alg; - } -#endif /* MBEDTLS_PSA_CRYPTO_C */ - else { - /* The opaque key has no RSA PSS algorithm associated. */ - return MBEDTLS_ERR_PK_BAD_INPUT_DATA; - } - /* Adjust the hashing algorithm. */ - sign_alg = (sign_alg & ~PSA_ALG_HASH_MASK) | PSA_ALG_GET_HASH(psa_md_alg); - - status = psa_sign_hash(ctx->priv_id, sign_alg, + /* PSA_ALG_RSA_PSS() behaves the same as PSA_ALG_RSA_PSS_ANY_SALT() when + * performing a signature, but they are encoded differently. Instead of + * extracting the proper one from the wrapped key policy, just try both. */ + status = psa_sign_hash(ctx->priv_id, PSA_ALG_RSA_PSS(psa_md_alg), hash, hash_len, sig, sig_size, sig_len); + if (status == PSA_ERROR_NOT_PERMITTED) { + status = psa_sign_hash(ctx->priv_id, PSA_ALG_RSA_PSS_ANY_SALT(psa_md_alg), + hash, hash_len, + sig, sig_size, sig_len); + } return PSA_PK_RSA_TO_MBEDTLS_ERR(status); } diff --git a/thirdparty/mbedtls/library/platform_util.c b/thirdparty/mbedtls/library/platform_util.c index 0741bf575e..19ef07aead 100644 --- a/thirdparty/mbedtls/library/platform_util.c +++ b/thirdparty/mbedtls/library/platform_util.c @@ -149,7 +149,7 @@ void mbedtls_zeroize_and_free(void *buf, size_t len) #include <time.h> #if !defined(_WIN32) && (defined(unix) || \ defined(__unix) || defined(__unix__) || (defined(__APPLE__) && \ - defined(__MACH__)) || defined__midipix__) + defined(__MACH__)) || defined(__midipix__)) #include <unistd.h> #endif /* !_WIN32 && (unix || __unix || __unix__ || * (__APPLE__ && __MACH__) || __midipix__) */ diff --git a/thirdparty/mbedtls/library/psa_crypto_core.h b/thirdparty/mbedtls/library/psa_crypto_core.h index c059162efe..21e7559f01 100644 --- a/thirdparty/mbedtls/library/psa_crypto_core.h +++ b/thirdparty/mbedtls/library/psa_crypto_core.h @@ -59,6 +59,8 @@ typedef enum { * and metadata for one key. */ typedef struct { + /* This field is accessed in a lot of places. Putting it first + * reduces the code size. */ psa_key_attributes_t attr; /* @@ -78,35 +80,77 @@ typedef struct { * slots that are in a suitable state for the function. * For example, psa_get_and_lock_key_slot_in_memory, which finds a slot * containing a given key ID, will only check slots whose state variable is - * PSA_SLOT_FULL. */ + * PSA_SLOT_FULL. + */ psa_key_slot_state_t state; - /* - * Number of functions registered as reading the material in the key slot. - * - * Library functions must not write directly to registered_readers - * - * A function must call psa_register_read(slot) before reading the current - * contents of the slot for an operation. - * They then must call psa_unregister_read(slot) once they have finished - * reading the current contents of the slot. If the key slot mutex is not - * held (when mutexes are enabled), this call must be done via a call to - * psa_unregister_read_under_mutex(slot). - * A function must call psa_key_slot_has_readers(slot) to check if - * the slot is in use for reading. +#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC) + /* The index of the slice containing this slot. + * This field must be filled if the slot contains a key + * (including keys being created or destroyed), and can be either + * filled or 0 when the slot is free. * - * This counter is used to prevent resetting the key slot while the library - * may access it. For example, such control is needed in the following - * scenarios: - * . In case of key slot starvation, all key slots contain the description - * of a key, and the library asks for the description of a persistent - * key not present in the key slots, the key slots currently accessed by - * the library cannot be reclaimed to free a key slot to load the - * persistent key. - * . In case of a multi-threaded application where one thread asks to close - * or purge or destroy a key while it is in use by the library through - * another thread. */ - size_t registered_readers; + * In most cases, the slice index can be deduced from the key identifer. + * We keep it in a separate field for robustness (it reduces the chance + * that a coding mistake in the key store will result in accessing the + * wrong slice), and also so that it's available even on code paths + * during creation or destruction where the key identifier might not be + * filled in. + * */ + uint8_t slice_index; +#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */ + + union { + struct { + /* The index of the next slot in the free list for this + * slice, relative * to the next array element. + * + * That is, 0 means the next slot, 1 means the next slot + * but one, etc. -1 would mean the slot itself. -2 means + * the previous slot, etc. + * + * If this is beyond the array length, the free list ends with the + * current element. + * + * The reason for this strange encoding is that 0 means the next + * element. This way, when we allocate a slice and initialize it + * to all-zero, the slice is ready for use, with a free list that + * consists of all the slots in order. + */ + int32_t next_free_relative_to_next; + } free; + + struct { + /* + * Number of functions registered as reading the material in the key slot. + * + * Library functions must not write directly to registered_readers + * + * A function must call psa_register_read(slot) before reading + * the current contents of the slot for an operation. + * They then must call psa_unregister_read(slot) once they have + * finished reading the current contents of the slot. If the key + * slot mutex is not held (when mutexes are enabled), this call + * must be done via a call to + * psa_unregister_read_under_mutex(slot). + * A function must call psa_key_slot_has_readers(slot) to check if + * the slot is in use for reading. + * + * This counter is used to prevent resetting the key slot while + * the library may access it. For example, such control is needed + * in the following scenarios: + * . In case of key slot starvation, all key slots contain the + * description of a key, and the library asks for the + * description of a persistent key not present in the + * key slots, the key slots currently accessed by the + * library cannot be reclaimed to free a key slot to load + * the persistent key. + * . In case of a multi-threaded application where one thread + * asks to close or purge or destroy a key while it is in use + * by the library through another thread. */ + size_t registered_readers; + } occupied; + } var; /* Dynamically allocated key data buffer. * Format as specified in psa_export_key(). */ @@ -169,7 +213,7 @@ typedef struct { */ static inline int psa_key_slot_has_readers(const psa_key_slot_t *slot) { - return slot->registered_readers > 0; + return slot->var.occupied.registered_readers > 0; } #if defined(MBEDTLS_PSA_CRYPTO_SE_C) @@ -343,19 +387,18 @@ psa_status_t psa_export_public_key_internal( const uint8_t *key_buffer, size_t key_buffer_size, uint8_t *data, size_t data_size, size_t *data_length); -/** Whether a key production parameters structure is the default. +/** Whether a key custom production parameters structure is the default. * - * Calls to a key generation driver with non-default production parameters + * Calls to a key generation driver with non-default custom production parameters * require a driver supporting custom production parameters. * - * \param[in] params The key production parameters to check. - * \param params_data_length Size of `params->data` in bytes. + * \param[in] custom The key custom production parameters to check. + * \param custom_data_length Size of the associated variable-length data + * in bytes. */ -#ifndef __cplusplus -int psa_key_production_parameters_are_default( - const psa_key_production_parameters_t *params, - size_t params_data_length); -#endif +int psa_custom_key_parameters_are_default( + const psa_custom_key_parameters_t *custom, + size_t custom_data_length); /** * \brief Generate a key. @@ -364,9 +407,9 @@ int psa_key_production_parameters_are_default( * entry point. * * \param[in] attributes The attributes for the key to generate. - * \param[in] params The production parameters from - * psa_generate_key_ext(). - * \param params_data_length The size of `params->data` in bytes. + * \param[in] custom Custom parameters for the key generation. + * \param[in] custom_data Variable-length data associated with \c custom. + * \param custom_data_length Length of `custom_data` in bytes. * \param[out] key_buffer Buffer where the key data is to be written. * \param[in] key_buffer_size Size of \p key_buffer in bytes. * \param[out] key_buffer_length On success, the number of bytes written in @@ -380,14 +423,13 @@ int psa_key_production_parameters_are_default( * \retval #PSA_ERROR_BUFFER_TOO_SMALL * The size of \p key_buffer is too small. */ -#ifndef __cplusplus psa_status_t psa_generate_key_internal(const psa_key_attributes_t *attributes, - const psa_key_production_parameters_t *params, - size_t params_data_length, + const psa_custom_key_parameters_t *custom, + const uint8_t *custom_data, + size_t custom_data_length, uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length); -#endif /** Sign a message with a private key. For hash-and-sign algorithms, * this includes the hashing step. diff --git a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h index 6919971aca..b901557208 100644 --- a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h +++ b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h @@ -728,10 +728,10 @@ static inline psa_status_t psa_driver_wrapper_get_key_buffer_size_from_key_data( } } -#ifndef __cplusplus static inline psa_status_t psa_driver_wrapper_generate_key( const psa_key_attributes_t *attributes, - const psa_key_production_parameters_t *params, size_t params_data_length, + const psa_custom_key_parameters_t *custom, + const uint8_t *custom_data, size_t custom_data_length, uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length ) { psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED; @@ -740,7 +740,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key( #if defined(PSA_WANT_KEY_TYPE_RSA_KEY_PAIR_GENERATE) int is_default_production = - psa_key_production_parameters_are_default(params, params_data_length); + psa_custom_key_parameters_are_default(custom, custom_data_length); if( location != PSA_KEY_LOCATION_LOCAL_STORAGE && !is_default_production ) { /* We don't support passing custom production parameters @@ -811,7 +811,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key( /* Software fallback */ status = psa_generate_key_internal( - attributes, params, params_data_length, + attributes, custom, custom_data, custom_data_length, key_buffer, key_buffer_size, key_buffer_length ); break; @@ -833,7 +833,6 @@ static inline psa_status_t psa_driver_wrapper_generate_key( return( status ); } -#endif static inline psa_status_t psa_driver_wrapper_import_key( const psa_key_attributes_t *attributes, diff --git a/thirdparty/mbedtls/library/psa_crypto_random_impl.h b/thirdparty/mbedtls/library/psa_crypto_random_impl.h index 533fb2e940..5b5163111b 100644 --- a/thirdparty/mbedtls/library/psa_crypto_random_impl.h +++ b/thirdparty/mbedtls/library/psa_crypto_random_impl.h @@ -21,13 +21,10 @@ typedef mbedtls_psa_external_random_context_t mbedtls_psa_random_context_t; #include "mbedtls/entropy.h" /* Choose a DRBG based on configuration and availability */ -#if defined(MBEDTLS_PSA_HMAC_DRBG_MD_TYPE) - -#include "mbedtls/hmac_drbg.h" - -#elif defined(MBEDTLS_CTR_DRBG_C) +#if defined(MBEDTLS_CTR_DRBG_C) #include "mbedtls/ctr_drbg.h" +#undef MBEDTLS_PSA_HMAC_DRBG_MD_TYPE #elif defined(MBEDTLS_HMAC_DRBG_C) @@ -49,17 +46,11 @@ typedef mbedtls_psa_external_random_context_t mbedtls_psa_random_context_t; #error "No hash algorithm available for HMAC_DBRG." #endif -#else /* !MBEDTLS_PSA_HMAC_DRBG_MD_TYPE && !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/ +#else /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/ #error "No DRBG module available for the psa_crypto module." -#endif /* !MBEDTLS_PSA_HMAC_DRBG_MD_TYPE && !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/ - -#if defined(MBEDTLS_CTR_DRBG_C) -#include "mbedtls/ctr_drbg.h" -#elif defined(MBEDTLS_HMAC_DRBG_C) -#include "mbedtls/hmac_drbg.h" -#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C */ +#endif /* !MBEDTLS_CTR_DRBG_C && !MBEDTLS_HMAC_DRBG_C*/ /* The maximum number of bytes that mbedtls_psa_get_random() is expected to return. */ #if defined(MBEDTLS_CTR_DRBG_C) diff --git a/thirdparty/mbedtls/library/psa_crypto_rsa.h b/thirdparty/mbedtls/library/psa_crypto_rsa.h index 6d695ddf50..1a780006a9 100644 --- a/thirdparty/mbedtls/library/psa_crypto_rsa.h +++ b/thirdparty/mbedtls/library/psa_crypto_rsa.h @@ -105,17 +105,11 @@ psa_status_t mbedtls_psa_rsa_export_public_key( /** * \brief Generate an RSA key. * - * \note The signature of the function is that of a PSA driver generate_key - * entry point. - * * \param[in] attributes The attributes for the RSA key to generate. - * \param[in] params Production parameters for the key - * generation. This function only uses - * `params->data`, - * which contains the public exponent. + * \param[in] custom_data The public exponent to use. * This can be a null pointer if * \c params_data_length is 0. - * \param params_data_length Length of `params->data` in bytes. + * \param custom_data_length Length of \p custom_data in bytes. * This can be 0, in which case the * public exponent will be 65537. * \param[out] key_buffer Buffer where the key data is to be written. @@ -130,12 +124,10 @@ psa_status_t mbedtls_psa_rsa_export_public_key( * \retval #PSA_ERROR_BUFFER_TOO_SMALL * The size of \p key_buffer is too small. */ -#ifndef __cplusplus psa_status_t mbedtls_psa_rsa_generate_key( const psa_key_attributes_t *attributes, - const psa_key_production_parameters_t *params, size_t params_data_length, + const uint8_t *custom_data, size_t custom_data_length, uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length); -#endif /** Sign an already-calculated hash with an RSA private key. * diff --git a/thirdparty/mbedtls/library/psa_crypto_slot_management.h b/thirdparty/mbedtls/library/psa_crypto_slot_management.h index bcfc9d8adc..af1208e3ae 100644 --- a/thirdparty/mbedtls/library/psa_crypto_slot_management.h +++ b/thirdparty/mbedtls/library/psa_crypto_slot_management.h @@ -15,20 +15,26 @@ /** Range of volatile key identifiers. * - * The last #MBEDTLS_PSA_KEY_SLOT_COUNT identifiers of the implementation + * The first #MBEDTLS_PSA_KEY_SLOT_COUNT identifiers of the implementation * range of key identifiers are reserved for volatile key identifiers. - * A volatile key identifier is equal to #PSA_KEY_ID_VOLATILE_MIN plus the - * index of the key slot containing the volatile key definition. + * + * If \c id is a a volatile key identifier, #PSA_KEY_ID_VOLATILE_MIN - \c id + * indicates the key slot containing the volatile key definition. See + * psa_crypto_slot_management.c for details. */ /** The minimum value for a volatile key identifier. */ -#define PSA_KEY_ID_VOLATILE_MIN (PSA_KEY_ID_VENDOR_MAX - \ - MBEDTLS_PSA_KEY_SLOT_COUNT + 1) +#define PSA_KEY_ID_VOLATILE_MIN PSA_KEY_ID_VENDOR_MIN /** The maximum value for a volatile key identifier. */ -#define PSA_KEY_ID_VOLATILE_MAX PSA_KEY_ID_VENDOR_MAX +#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC) +#define PSA_KEY_ID_VOLATILE_MAX (MBEDTLS_PSA_KEY_ID_BUILTIN_MIN - 1) +#else /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */ +#define PSA_KEY_ID_VOLATILE_MAX \ + (PSA_KEY_ID_VOLATILE_MIN + MBEDTLS_PSA_KEY_SLOT_COUNT - 1) +#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */ /** Test whether a key identifier is a volatile key identifier. * @@ -58,6 +64,9 @@ static inline int psa_key_id_is_volatile(psa_key_id_t key_id) * It is the responsibility of the caller to call psa_unregister_read(slot) * when they have finished reading the contents of the slot. * + * On failure, `*p_slot` is set to NULL. This ensures that it is always valid + * to call psa_unregister_read on the returned slot. + * * \param key Key identifier to query. * \param[out] p_slot On success, `*p_slot` contains a pointer to the * key slot containing the description of the key @@ -91,6 +100,24 @@ psa_status_t psa_get_and_lock_key_slot(mbedtls_svc_key_id_t key, */ psa_status_t psa_initialize_key_slots(void); +#if defined(MBEDTLS_TEST_HOOKS) && defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC) +/* Allow test code to customize the key slice length. We use this in tests + * that exhaust the key store to reach a full key store in reasonable time + * and memory. + * + * The length of each slice must be between 1 and + * (1 << KEY_ID_SLOT_INDEX_WIDTH) inclusive. + * + * The length for a given slice index must not change while + * the key store is initialized. + */ +extern size_t (*mbedtls_test_hook_psa_volatile_key_slice_length)( + size_t slice_idx); + +/* The number of volatile key slices. */ +size_t psa_key_slot_volatile_slice_count(void); +#endif + /** Delete all data from key slots in memory. * This function is not thread safe, it wipes every key slot regardless of * state and reader count. It should only be called when no slot is in use. @@ -110,13 +137,22 @@ void psa_wipe_all_key_slots(void); * If multi-threading is enabled, the caller must hold the * global key slot mutex. * - * \param[out] volatile_key_id On success, volatile key identifier - * associated to the returned slot. + * \param[out] volatile_key_id - If null, reserve a cache slot for + * a persistent or built-in key. + * - If non-null, allocate a slot for + * a volatile key. On success, + * \p *volatile_key_id is the + * identifier corresponding to the + * returned slot. It is the caller's + * responsibility to set this key identifier + * in the attributes. * \param[out] p_slot On success, a pointer to the slot. * * \retval #PSA_SUCCESS \emptydescription * \retval #PSA_ERROR_INSUFFICIENT_MEMORY * There were no free key slots. + * When #MBEDTLS_PSA_KEY_STORE_DYNAMIC is enabled, there was not + * enough memory to allocate more slots. * \retval #PSA_ERROR_BAD_STATE \emptydescription * \retval #PSA_ERROR_CORRUPTION_DETECTED * This function attempted to operate on a key slot which was in an @@ -125,6 +161,29 @@ void psa_wipe_all_key_slots(void); psa_status_t psa_reserve_free_key_slot(psa_key_id_t *volatile_key_id, psa_key_slot_t **p_slot); +#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC) +/** Return a key slot to the free list. + * + * Call this function when a slot obtained from psa_reserve_free_key_slot() + * is no longer in use. + * + * If multi-threading is enabled, the caller must hold the + * global key slot mutex. + * + * \param slice_idx The slice containing the slot. + * This is `slot->slice_index` when the slot + * is obtained from psa_reserve_free_key_slot(). + * \param slot The key slot. + * + * \retval #PSA_SUCCESS \emptydescription + * \retval #PSA_ERROR_CORRUPTION_DETECTED + * This function attempted to operate on a key slot which was in an + * unexpected state. + */ +psa_status_t psa_free_key_slot(size_t slice_idx, + psa_key_slot_t *slot); +#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */ + /** Change the state of a key slot. * * This function changes the state of the key slot from expected_state to @@ -171,10 +230,10 @@ static inline psa_status_t psa_key_slot_state_transition( static inline psa_status_t psa_register_read(psa_key_slot_t *slot) { if ((slot->state != PSA_SLOT_FULL) || - (slot->registered_readers >= SIZE_MAX)) { + (slot->var.occupied.registered_readers >= SIZE_MAX)) { return PSA_ERROR_CORRUPTION_DETECTED; } - slot->registered_readers++; + slot->var.occupied.registered_readers++; return PSA_SUCCESS; } diff --git a/thirdparty/mbedtls/library/rsa.c b/thirdparty/mbedtls/library/rsa.c index 7eb4a259ea..557faaf363 100644 --- a/thirdparty/mbedtls/library/rsa.c +++ b/thirdparty/mbedtls/library/rsa.c @@ -29,6 +29,7 @@ #include "mbedtls/rsa.h" #include "bignum_core.h" +#include "bignum_internal.h" #include "rsa_alt_helpers.h" #include "rsa_internal.h" #include "mbedtls/oid.h" @@ -1259,7 +1260,7 @@ int mbedtls_rsa_public(mbedtls_rsa_context *ctx, } olen = ctx->len; - MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod(&T, &T, &ctx->E, &ctx->N, &ctx->RN)); + MBEDTLS_MPI_CHK(mbedtls_mpi_exp_mod_unsafe(&T, &T, &ctx->E, &ctx->N, &ctx->RN)); MBEDTLS_MPI_CHK(mbedtls_mpi_write_binary(&T, output, olen)); cleanup: diff --git a/thirdparty/mbedtls/library/sha256.c b/thirdparty/mbedtls/library/sha256.c index 87889817a4..159acccaeb 100644 --- a/thirdparty/mbedtls/library/sha256.c +++ b/thirdparty/mbedtls/library/sha256.c @@ -44,7 +44,9 @@ #endif /* defined(__clang__) && (__clang_major__ >= 4) */ /* Ensure that SIG_SETMASK is defined when -std=c99 is used. */ +#if !defined(_GNU_SOURCE) #define _GNU_SOURCE +#endif #include "common.h" @@ -150,7 +152,9 @@ static int mbedtls_a64_crypto_sha256_determine_support(void) return 1; } #elif defined(MBEDTLS_PLATFORM_IS_WINDOWS_ON_ARM64) +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif #include <Windows.h> #include <processthreadsapi.h> diff --git a/thirdparty/mbedtls/library/ssl_cookie.c b/thirdparty/mbedtls/library/ssl_cookie.c index 2772cac4be..acc9e8c080 100644 --- a/thirdparty/mbedtls/library/ssl_cookie.c +++ b/thirdparty/mbedtls/library/ssl_cookie.c @@ -84,6 +84,10 @@ void mbedtls_ssl_cookie_set_timeout(mbedtls_ssl_cookie_ctx *ctx, unsigned long d void mbedtls_ssl_cookie_free(mbedtls_ssl_cookie_ctx *ctx) { + if (ctx == NULL) { + return; + } + #if defined(MBEDTLS_USE_PSA_CRYPTO) psa_destroy_key(ctx->psa_hmac_key); #else diff --git a/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c b/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c index f8b4448c86..734c417b8b 100644 --- a/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c +++ b/thirdparty/mbedtls/library/ssl_debug_helpers_generated.c @@ -60,7 +60,7 @@ const char *mbedtls_ssl_named_group_to_str( uint16_t in ) return "ffdhe8192"; }; - return "UNKOWN"; + return "UNKNOWN"; } const char *mbedtls_ssl_sig_alg_to_str( uint16_t in ) { diff --git a/thirdparty/mbedtls/library/ssl_misc.h b/thirdparty/mbedtls/library/ssl_misc.h index a8807f67c6..98668798a8 100644 --- a/thirdparty/mbedtls/library/ssl_misc.h +++ b/thirdparty/mbedtls/library/ssl_misc.h @@ -1507,7 +1507,7 @@ int mbedtls_ssl_psk_derive_premaster(mbedtls_ssl_context *ssl, #endif /* MBEDTLS_KEY_EXCHANGE_SOME_PSK_ENABLED */ #if defined(MBEDTLS_SSL_HANDSHAKE_WITH_PSK_ENABLED) -#if defined(MBEDTLS_SSL_CLI_C) +#if defined(MBEDTLS_SSL_CLI_C) || defined(MBEDTLS_SSL_SRV_C) MBEDTLS_CHECK_RETURN_CRITICAL int mbedtls_ssl_conf_has_static_psk(mbedtls_ssl_config const *conf); #endif @@ -1674,18 +1674,53 @@ static inline mbedtls_x509_crt *mbedtls_ssl_own_cert(mbedtls_ssl_context *ssl) } /* - * Check usage of a certificate wrt extensions: - * keyUsage, extendedKeyUsage (later), and nSCertType (later). + * Verify a certificate. + * + * [in/out] ssl: misc. things read + * ssl->session_negotiate->verify_result updated + * [in] authmode: one of MBEDTLS_SSL_VERIFY_{NONE,OPTIONAL,REQUIRED} + * [in] chain: the certificate chain to verify (ie the peer's chain) + * [in] ciphersuite_info: For TLS 1.2, this session's ciphersuite; + * for TLS 1.3, may be left NULL. + * [in] rs_ctx: restart context if restartable ECC is in use; + * leave NULL for no restartable behaviour. + * + * Return: + * - 0 if the handshake should continue. Depending on the + * authmode it means: + * - REQUIRED: the certificate was found to be valid, trusted & acceptable. + * ssl->session_negotiate->verify_result is 0. + * - OPTIONAL: the certificate may or may not be acceptable, but + * ssl->session_negotiate->verify_result was updated with the result. + * - NONE: the certificate wasn't even checked. + * - MBEDTLS_ERR_X509_CERT_VERIFY_FAILED or MBEDTLS_ERR_SSL_BAD_CERTIFICATE if + * the certificate was found to be invalid/untrusted/unacceptable and the + * handshake should be aborted (can only happen with REQUIRED). + * - another error code if another error happened (out-of-memory, etc.) + */ +MBEDTLS_CHECK_RETURN_CRITICAL +int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl, + int authmode, + mbedtls_x509_crt *chain, + const mbedtls_ssl_ciphersuite_t *ciphersuite_info, + void *rs_ctx); + +/* + * Check usage of a certificate wrt usage extensions: + * keyUsage and extendedKeyUsage. + * (Note: nSCertType is deprecated and not standard, we don't check it.) * - * Warning: cert_endpoint is the endpoint of the cert (ie, of our peer when we - * check a cert we received from them)! + * Note: if tls_version is 1.3, ciphersuite is ignored and can be NULL. + * + * Note: recv_endpoint is the receiver's endpoint. * * Return 0 if everything is OK, -1 if not. */ MBEDTLS_CHECK_RETURN_CRITICAL int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert, const mbedtls_ssl_ciphersuite_t *ciphersuite, - int cert_endpoint, + int recv_endpoint, + mbedtls_ssl_protocol_version tls_version, uint32_t *flags); #endif /* MBEDTLS_X509_CRT_PARSE_C */ @@ -1891,6 +1926,26 @@ static inline int mbedtls_ssl_conf_is_hybrid_tls12_tls13(const mbedtls_ssl_confi #endif /* MBEDTLS_SSL_PROTO_TLS1_2 && MBEDTLS_SSL_PROTO_TLS1_3 */ #if defined(MBEDTLS_SSL_PROTO_TLS1_3) + +/** \brief Initialize the PSA crypto subsystem if necessary. + * + * Call this function before doing any cryptography in a TLS 1.3 handshake. + * + * This is necessary in Mbed TLS 3.x for backward compatibility. + * Up to Mbed TLS 3.5, in the default configuration, you could perform + * a TLS connection with default parameters without having called + * psa_crypto_init(), since the TLS layer only supported TLS 1.2 and + * did not use PSA crypto. (TLS 1.2 only uses PSA crypto if + * MBEDTLS_USE_PSA_CRYPTO is enabled, which is not the case in the default + * configuration.) Starting with Mbed TLS 3.6.0, TLS 1.3 is enabled + * by default, and the TLS 1.3 layer uses PSA crypto. This means that + * applications that are not otherwise using PSA crypto and that worked + * with Mbed TLS 3.5 started failing in TLS 3.6.0 if they connected to + * a peer that supports TLS 1.3. See + * https://github.com/Mbed-TLS/mbedtls/issues/9072 + */ +int mbedtls_ssl_tls13_crypto_init(mbedtls_ssl_context *ssl); + extern const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[ MBEDTLS_SERVER_HELLO_RANDOM_LEN]; MBEDTLS_CHECK_RETURN_CRITICAL @@ -2914,8 +2969,37 @@ static inline void mbedtls_ssl_tls13_session_clear_ticket_flags( { session->ticket_flags &= ~(flags & MBEDTLS_SSL_TLS1_3_TICKET_FLAGS_MASK); } + #endif /* MBEDTLS_SSL_PROTO_TLS1_3 && MBEDTLS_SSL_SESSION_TICKETS */ +#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C) +#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT 0 +#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT 1 + +#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK \ + (1 << MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT) +#define MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK \ + (1 << MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT) + +static inline int mbedtls_ssl_conf_get_session_tickets( + const mbedtls_ssl_config *conf) +{ + return conf->session_tickets & MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK ? + MBEDTLS_SSL_SESSION_TICKETS_ENABLED : + MBEDTLS_SSL_SESSION_TICKETS_DISABLED; +} + +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) +static inline int mbedtls_ssl_conf_is_signal_new_session_tickets_enabled( + const mbedtls_ssl_config *conf) +{ + return conf->session_tickets & MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK ? + MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED : + MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED; +} +#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */ +#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */ + #if defined(MBEDTLS_SSL_CLI_C) && defined(MBEDTLS_SSL_PROTO_TLS1_3) int mbedtls_ssl_tls13_finalize_client_hello(mbedtls_ssl_context *ssl); #endif diff --git a/thirdparty/mbedtls/library/ssl_msg.c b/thirdparty/mbedtls/library/ssl_msg.c index b07cd96f1b..ef722d7bdc 100644 --- a/thirdparty/mbedtls/library/ssl_msg.c +++ b/thirdparty/mbedtls/library/ssl_msg.c @@ -5570,9 +5570,9 @@ static int ssl_check_ctr_renegotiate(mbedtls_ssl_context *ssl) #if defined(MBEDTLS_SSL_PROTO_TLS1_3) -#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C) +#if defined(MBEDTLS_SSL_CLI_C) MBEDTLS_CHECK_RETURN_CRITICAL -static int ssl_tls13_check_new_session_ticket(mbedtls_ssl_context *ssl) +static int ssl_tls13_is_new_session_ticket(mbedtls_ssl_context *ssl) { if ((ssl->in_hslen == mbedtls_ssl_hs_hdr_len(ssl)) || @@ -5580,15 +5580,9 @@ static int ssl_tls13_check_new_session_ticket(mbedtls_ssl_context *ssl) return 0; } - ssl->keep_current_message = 1; - - MBEDTLS_SSL_DEBUG_MSG(3, ("NewSessionTicket received")); - mbedtls_ssl_handshake_set_state(ssl, - MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET); - - return MBEDTLS_ERR_SSL_WANT_READ; + return 1; } -#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */ +#endif /* MBEDTLS_SSL_CLI_C */ MBEDTLS_CHECK_RETURN_CRITICAL static int ssl_tls13_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl) @@ -5596,14 +5590,29 @@ static int ssl_tls13_handle_hs_message_post_handshake(mbedtls_ssl_context *ssl) MBEDTLS_SSL_DEBUG_MSG(3, ("received post-handshake message")); -#if defined(MBEDTLS_SSL_SESSION_TICKETS) && defined(MBEDTLS_SSL_CLI_C) +#if defined(MBEDTLS_SSL_CLI_C) if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) { - int ret = ssl_tls13_check_new_session_ticket(ssl); - if (ret != 0) { - return ret; + if (ssl_tls13_is_new_session_ticket(ssl)) { +#if defined(MBEDTLS_SSL_SESSION_TICKETS) + MBEDTLS_SSL_DEBUG_MSG(3, ("NewSessionTicket received")); + if (mbedtls_ssl_conf_is_signal_new_session_tickets_enabled(ssl->conf) == + MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_ENABLED) { + ssl->keep_current_message = 1; + + mbedtls_ssl_handshake_set_state(ssl, + MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET); + return MBEDTLS_ERR_SSL_WANT_READ; + } else { + MBEDTLS_SSL_DEBUG_MSG(3, ("Ignoring NewSessionTicket, handling disabled.")); + return 0; + } +#else + MBEDTLS_SSL_DEBUG_MSG(3, ("Ignoring NewSessionTicket, not supported.")); + return 0; +#endif } } -#endif /* MBEDTLS_SSL_SESSION_TICKETS && MBEDTLS_SSL_CLI_C */ +#endif /* MBEDTLS_SSL_CLI_C */ /* Fail in all other cases. */ return MBEDTLS_ERR_SSL_UNEXPECTED_MESSAGE; diff --git a/thirdparty/mbedtls/library/ssl_ticket.c b/thirdparty/mbedtls/library/ssl_ticket.c index 6a31b0bee6..bfb656cf62 100644 --- a/thirdparty/mbedtls/library/ssl_ticket.c +++ b/thirdparty/mbedtls/library/ssl_ticket.c @@ -534,6 +534,10 @@ cleanup: */ void mbedtls_ssl_ticket_free(mbedtls_ssl_ticket_context *ctx) { + if (ctx == NULL) { + return; + } + #if defined(MBEDTLS_USE_PSA_CRYPTO) psa_destroy_key(ctx->keys[0].key); psa_destroy_key(ctx->keys[1].key); diff --git a/thirdparty/mbedtls/library/ssl_tls.c b/thirdparty/mbedtls/library/ssl_tls.c index c5e06491c1..c773365bf6 100644 --- a/thirdparty/mbedtls/library/ssl_tls.c +++ b/thirdparty/mbedtls/library/ssl_tls.c @@ -132,7 +132,7 @@ int mbedtls_ssl_set_cid(mbedtls_ssl_context *ssl, int mbedtls_ssl_get_own_cid(mbedtls_ssl_context *ssl, int *enabled, - unsigned char own_cid[MBEDTLS_SSL_CID_OUT_LEN_MAX], + unsigned char own_cid[MBEDTLS_SSL_CID_IN_LEN_MAX], size_t *own_cid_len) { *enabled = MBEDTLS_SSL_CID_DISABLED; @@ -1354,29 +1354,6 @@ static int ssl_conf_check(const mbedtls_ssl_context *ssl) return ret; } -#if defined(MBEDTLS_SSL_PROTO_TLS1_3) - /* RFC 8446 section 4.4.3 - * - * If the verification fails, the receiver MUST terminate the handshake with - * a "decrypt_error" alert. - * - * If the client is configured as TLS 1.3 only with optional verify, return - * bad config. - * - */ - if (mbedtls_ssl_conf_tls13_is_ephemeral_enabled( - (mbedtls_ssl_context *) ssl) && - ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT && - ssl->conf->max_tls_version == MBEDTLS_SSL_VERSION_TLS1_3 && - ssl->conf->min_tls_version == MBEDTLS_SSL_VERSION_TLS1_3 && - ssl->conf->authmode == MBEDTLS_SSL_VERIFY_OPTIONAL) { - MBEDTLS_SSL_DEBUG_MSG( - 1, ("Optional verify auth mode " - "is not available for TLS 1.3 client")); - return MBEDTLS_ERR_SSL_BAD_CONFIG; - } -#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */ - if (ssl->conf->f_rng == NULL) { MBEDTLS_SSL_DEBUG_MSG(1, ("no RNG provided")); return MBEDTLS_ERR_SSL_NO_RNG; @@ -1760,6 +1737,7 @@ int mbedtls_ssl_set_session(mbedtls_ssl_context *ssl, const mbedtls_ssl_session #if defined(MBEDTLS_SSL_PROTO_TLS1_3) if (session->tls_version == MBEDTLS_SSL_VERSION_TLS1_3) { +#if defined(MBEDTLS_SSL_SESSION_TICKETS) const mbedtls_ssl_ciphersuite_t *ciphersuite_info = mbedtls_ssl_ciphersuite_from_id(session->ciphersuite); @@ -1770,6 +1748,14 @@ int mbedtls_ssl_set_session(mbedtls_ssl_context *ssl, const mbedtls_ssl_session session->ciphersuite)); return MBEDTLS_ERR_SSL_BAD_INPUT_DATA; } +#else + /* + * If session tickets are not enabled, it is not possible to resume a + * TLS 1.3 session, thus do not make any change to the SSL context in + * the first place. + */ + return 0; +#endif } #endif /* MBEDTLS_SSL_PROTO_TLS1_3 */ @@ -2234,6 +2220,7 @@ static void ssl_remove_psk(mbedtls_ssl_context *ssl) mbedtls_zeroize_and_free(ssl->handshake->psk, ssl->handshake->psk_len); ssl->handshake->psk_len = 0; + ssl->handshake->psk = NULL; } #endif /* MBEDTLS_USE_PSA_CRYPTO */ } @@ -2999,11 +2986,24 @@ void mbedtls_ssl_conf_renegotiation_period(mbedtls_ssl_config *conf, #if defined(MBEDTLS_SSL_SESSION_TICKETS) #if defined(MBEDTLS_SSL_CLI_C) + void mbedtls_ssl_conf_session_tickets(mbedtls_ssl_config *conf, int use_tickets) { - conf->session_tickets = use_tickets; + conf->session_tickets &= ~MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_MASK; + conf->session_tickets |= (use_tickets != 0) << + MBEDTLS_SSL_SESSION_TICKETS_TLS1_2_BIT; } -#endif + +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) +void mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets( + mbedtls_ssl_config *conf, int signal_new_session_tickets) +{ + conf->session_tickets &= ~MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_MASK; + conf->session_tickets |= (signal_new_session_tickets != 0) << + MBEDTLS_SSL_SESSION_TICKETS_TLS1_3_BIT; +} +#endif /* MBEDTLS_SSL_PROTO_TLS1_3 */ +#endif /* MBEDTLS_SSL_CLI_C */ #if defined(MBEDTLS_SSL_SRV_C) @@ -4049,7 +4049,7 @@ static int ssl_tls13_session_save(const mbedtls_ssl_session *session, } static int ssl_tls13_session_load(const mbedtls_ssl_session *session, - unsigned char *buf, + const unsigned char *buf, size_t buf_len) { ((void) session); @@ -5868,7 +5868,33 @@ int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf, if (endpoint == MBEDTLS_SSL_IS_CLIENT) { conf->authmode = MBEDTLS_SSL_VERIFY_REQUIRED; #if defined(MBEDTLS_SSL_SESSION_TICKETS) - conf->session_tickets = MBEDTLS_SSL_SESSION_TICKETS_ENABLED; + mbedtls_ssl_conf_session_tickets(conf, MBEDTLS_SSL_SESSION_TICKETS_ENABLED); +#if defined(MBEDTLS_SSL_PROTO_TLS1_3) + /* Contrary to TLS 1.2 tickets, TLS 1.3 NewSessionTicket message + * handling is disabled by default in Mbed TLS 3.6.x for backward + * compatibility with client applications developed using Mbed TLS 3.5 + * or earlier with the default configuration. + * + * Up to Mbed TLS 3.5, in the default configuration TLS 1.3 was + * disabled, and a Mbed TLS client with the default configuration would + * establish a TLS 1.2 connection with a TLS 1.2 and TLS 1.3 capable + * server. + * + * Starting with Mbed TLS 3.6.0, TLS 1.3 is enabled by default, and thus + * an Mbed TLS client with the default configuration establishes a + * TLS 1.3 connection with a TLS 1.2 and TLS 1.3 capable server. If + * following the handshake the TLS 1.3 server sends NewSessionTicket + * messages and the Mbed TLS client processes them, this results in + * Mbed TLS high level APIs (mbedtls_ssl_read(), + * mbedtls_ssl_handshake(), ...) to eventually return an + * #MBEDTLS_ERR_SSL_RECEIVED_NEW_SESSION_TICKET non fatal error code + * (see the documentation of mbedtls_ssl_read() for more information on + * that error code). Applications unaware of that TLS 1.3 specific non + * fatal error code are then failing. + */ + mbedtls_ssl_conf_tls13_enable_signal_new_session_tickets( + conf, MBEDTLS_SSL_TLS1_3_SIGNAL_NEW_SESSION_TICKETS_DISABLED); +#endif #endif } #endif @@ -6030,6 +6056,10 @@ int mbedtls_ssl_config_defaults(mbedtls_ssl_config *conf, */ void mbedtls_ssl_config_free(mbedtls_ssl_config *conf) { + if (conf == NULL) { + return; + } + #if defined(MBEDTLS_DHM_C) mbedtls_mpi_free(&conf->dhm_P); mbedtls_mpi_free(&conf->dhm_G); @@ -6344,71 +6374,6 @@ const char *mbedtls_ssl_get_curve_name_from_tls_id(uint16_t tls_id) } #endif -#if defined(MBEDTLS_X509_CRT_PARSE_C) -int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert, - const mbedtls_ssl_ciphersuite_t *ciphersuite, - int cert_endpoint, - uint32_t *flags) -{ - int ret = 0; - unsigned int usage = 0; - const char *ext_oid; - size_t ext_len; - - if (cert_endpoint == MBEDTLS_SSL_IS_SERVER) { - /* Server part of the key exchange */ - switch (ciphersuite->key_exchange) { - case MBEDTLS_KEY_EXCHANGE_RSA: - case MBEDTLS_KEY_EXCHANGE_RSA_PSK: - usage = MBEDTLS_X509_KU_KEY_ENCIPHERMENT; - break; - - case MBEDTLS_KEY_EXCHANGE_DHE_RSA: - case MBEDTLS_KEY_EXCHANGE_ECDHE_RSA: - case MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA: - usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE; - break; - - case MBEDTLS_KEY_EXCHANGE_ECDH_RSA: - case MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA: - usage = MBEDTLS_X509_KU_KEY_AGREEMENT; - break; - - /* Don't use default: we want warnings when adding new values */ - case MBEDTLS_KEY_EXCHANGE_NONE: - case MBEDTLS_KEY_EXCHANGE_PSK: - case MBEDTLS_KEY_EXCHANGE_DHE_PSK: - case MBEDTLS_KEY_EXCHANGE_ECDHE_PSK: - case MBEDTLS_KEY_EXCHANGE_ECJPAKE: - usage = 0; - } - } else { - /* Client auth: we only implement rsa_sign and mbedtls_ecdsa_sign for now */ - usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE; - } - - if (mbedtls_x509_crt_check_key_usage(cert, usage) != 0) { - *flags |= MBEDTLS_X509_BADCERT_KEY_USAGE; - ret = -1; - } - - if (cert_endpoint == MBEDTLS_SSL_IS_SERVER) { - ext_oid = MBEDTLS_OID_SERVER_AUTH; - ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH); - } else { - ext_oid = MBEDTLS_OID_CLIENT_AUTH; - ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH); - } - - if (mbedtls_x509_crt_check_extended_key_usage(cert, ext_oid, ext_len) != 0) { - *flags |= MBEDTLS_X509_BADCERT_EXT_KEY_USAGE; - ret = -1; - } - - return ret; -} -#endif /* MBEDTLS_X509_CRT_PARSE_C */ - #if defined(MBEDTLS_USE_PSA_CRYPTO) int mbedtls_ssl_get_handshake_transcript(mbedtls_ssl_context *ssl, const mbedtls_md_type_t md, @@ -7927,196 +7892,6 @@ static int ssl_parse_certificate_coordinate(mbedtls_ssl_context *ssl, return SSL_CERTIFICATE_EXPECTED; } -MBEDTLS_CHECK_RETURN_CRITICAL -static int ssl_parse_certificate_verify(mbedtls_ssl_context *ssl, - int authmode, - mbedtls_x509_crt *chain, - void *rs_ctx) -{ - int ret = 0; - const mbedtls_ssl_ciphersuite_t *ciphersuite_info = - ssl->handshake->ciphersuite_info; - int have_ca_chain = 0; - - int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *); - void *p_vrfy; - - if (authmode == MBEDTLS_SSL_VERIFY_NONE) { - return 0; - } - - if (ssl->f_vrfy != NULL) { - MBEDTLS_SSL_DEBUG_MSG(3, ("Use context-specific verification callback")); - f_vrfy = ssl->f_vrfy; - p_vrfy = ssl->p_vrfy; - } else { - MBEDTLS_SSL_DEBUG_MSG(3, ("Use configuration-specific verification callback")); - f_vrfy = ssl->conf->f_vrfy; - p_vrfy = ssl->conf->p_vrfy; - } - - /* - * Main check: verify certificate - */ -#if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK) - if (ssl->conf->f_ca_cb != NULL) { - ((void) rs_ctx); - have_ca_chain = 1; - - MBEDTLS_SSL_DEBUG_MSG(3, ("use CA callback for X.509 CRT verification")); - ret = mbedtls_x509_crt_verify_with_ca_cb( - chain, - ssl->conf->f_ca_cb, - ssl->conf->p_ca_cb, - ssl->conf->cert_profile, - ssl->hostname, - &ssl->session_negotiate->verify_result, - f_vrfy, p_vrfy); - } else -#endif /* MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK */ - { - mbedtls_x509_crt *ca_chain; - mbedtls_x509_crl *ca_crl; - -#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) - if (ssl->handshake->sni_ca_chain != NULL) { - ca_chain = ssl->handshake->sni_ca_chain; - ca_crl = ssl->handshake->sni_ca_crl; - } else -#endif - { - ca_chain = ssl->conf->ca_chain; - ca_crl = ssl->conf->ca_crl; - } - - if (ca_chain != NULL) { - have_ca_chain = 1; - } - - ret = mbedtls_x509_crt_verify_restartable( - chain, - ca_chain, ca_crl, - ssl->conf->cert_profile, - ssl->hostname, - &ssl->session_negotiate->verify_result, - f_vrfy, p_vrfy, rs_ctx); - } - - if (ret != 0) { - MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret); - } - -#if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED) - if (ret == MBEDTLS_ERR_ECP_IN_PROGRESS) { - return MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS; - } -#endif - - /* - * Secondary checks: always done, but change 'ret' only if it was 0 - */ - -#if defined(MBEDTLS_PK_HAVE_ECC_KEYS) - { - const mbedtls_pk_context *pk = &chain->pk; - - /* If certificate uses an EC key, make sure the curve is OK. - * This is a public key, so it can't be opaque, so can_do() is a good - * enough check to ensure pk_ec() is safe to use here. */ - if (mbedtls_pk_can_do(pk, MBEDTLS_PK_ECKEY)) { - /* and in the unlikely case the above assumption no longer holds - * we are making sure that pk_ec() here does not return a NULL - */ - mbedtls_ecp_group_id grp_id = mbedtls_pk_get_ec_group_id(pk); - if (grp_id == MBEDTLS_ECP_DP_NONE) { - MBEDTLS_SSL_DEBUG_MSG(1, ("invalid group ID")); - return MBEDTLS_ERR_SSL_INTERNAL_ERROR; - } - if (mbedtls_ssl_check_curve(ssl, grp_id) != 0) { - ssl->session_negotiate->verify_result |= - MBEDTLS_X509_BADCERT_BAD_KEY; - - MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (EC key curve)")); - if (ret == 0) { - ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE; - } - } - } - } -#endif /* MBEDTLS_PK_HAVE_ECC_KEYS */ - - if (mbedtls_ssl_check_cert_usage(chain, - ciphersuite_info, - !ssl->conf->endpoint, - &ssl->session_negotiate->verify_result) != 0) { - MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)")); - if (ret == 0) { - ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE; - } - } - - /* mbedtls_x509_crt_verify_with_profile is supposed to report a - * verification failure through MBEDTLS_ERR_X509_CERT_VERIFY_FAILED, - * with details encoded in the verification flags. All other kinds - * of error codes, including those from the user provided f_vrfy - * functions, are treated as fatal and lead to a failure of - * ssl_parse_certificate even if verification was optional. */ - if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL && - (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED || - ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) { - ret = 0; - } - - if (have_ca_chain == 0 && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) { - MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain")); - ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED; - } - - if (ret != 0) { - uint8_t alert; - - /* The certificate may have been rejected for several reasons. - Pick one and send the corresponding alert. Which alert to send - may be a subject of debate in some cases. */ - if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_OTHER) { - alert = MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED; - } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) { - alert = MBEDTLS_SSL_ALERT_MSG_BAD_CERT; - } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_KEY_USAGE) { - alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT; - } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXT_KEY_USAGE) { - alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT; - } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NS_CERT_TYPE) { - alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT; - } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_PK) { - alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT; - } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_KEY) { - alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT; - } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXPIRED) { - alert = MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED; - } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_REVOKED) { - alert = MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED; - } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) { - alert = MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA; - } else { - alert = MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN; - } - mbedtls_ssl_send_alert_message(ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL, - alert); - } - -#if defined(MBEDTLS_DEBUG_C) - if (ssl->session_negotiate->verify_result != 0) { - MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x", - (unsigned int) ssl->session_negotiate->verify_result)); - } else { - MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear")); - } -#endif /* MBEDTLS_DEBUG_C */ - - return ret; -} - #if !defined(MBEDTLS_SSL_KEEP_PEER_CERTIFICATE) MBEDTLS_CHECK_RETURN_CRITICAL static int ssl_remember_peer_crt_digest(mbedtls_ssl_context *ssl, @@ -8173,6 +7948,7 @@ int mbedtls_ssl_parse_certificate(mbedtls_ssl_context *ssl) { int ret = 0; int crt_expected; + /* Authmode: precedence order is SNI if used else configuration */ #if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) const int authmode = ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET ? ssl->handshake->sni_authmode @@ -8252,8 +8028,9 @@ crt_verify: } #endif - ret = ssl_parse_certificate_verify(ssl, authmode, - chain, rs_ctx); + ret = mbedtls_ssl_verify_certificate(ssl, authmode, chain, + ssl->handshake->ciphersuite_info, + rs_ctx); if (ret != 0) { goto exit; } @@ -9919,4 +9696,274 @@ int mbedtls_ssl_session_set_ticket_alpn(mbedtls_ssl_session *session, return 0; } #endif /* MBEDTLS_SSL_SRV_C && MBEDTLS_SSL_EARLY_DATA && MBEDTLS_SSL_ALPN */ + +/* + * The following functions are used by 1.2 and 1.3, client and server. + */ +#if defined(MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED) +int mbedtls_ssl_check_cert_usage(const mbedtls_x509_crt *cert, + const mbedtls_ssl_ciphersuite_t *ciphersuite, + int recv_endpoint, + mbedtls_ssl_protocol_version tls_version, + uint32_t *flags) +{ + int ret = 0; + unsigned int usage = 0; + const char *ext_oid; + size_t ext_len; + + /* + * keyUsage + */ + + /* Note: don't guard this with MBEDTLS_SSL_CLI_C because the server wants + * to check what a compliant client will think while choosing which cert + * to send to the client. */ +#if defined(MBEDTLS_SSL_PROTO_TLS1_2) + if (tls_version == MBEDTLS_SSL_VERSION_TLS1_2 && + recv_endpoint == MBEDTLS_SSL_IS_CLIENT) { + /* TLS 1.2 server part of the key exchange */ + switch (ciphersuite->key_exchange) { + case MBEDTLS_KEY_EXCHANGE_RSA: + case MBEDTLS_KEY_EXCHANGE_RSA_PSK: + usage = MBEDTLS_X509_KU_KEY_ENCIPHERMENT; + break; + + case MBEDTLS_KEY_EXCHANGE_DHE_RSA: + case MBEDTLS_KEY_EXCHANGE_ECDHE_RSA: + case MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA: + usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE; + break; + + case MBEDTLS_KEY_EXCHANGE_ECDH_RSA: + case MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA: + usage = MBEDTLS_X509_KU_KEY_AGREEMENT; + break; + + /* Don't use default: we want warnings when adding new values */ + case MBEDTLS_KEY_EXCHANGE_NONE: + case MBEDTLS_KEY_EXCHANGE_PSK: + case MBEDTLS_KEY_EXCHANGE_DHE_PSK: + case MBEDTLS_KEY_EXCHANGE_ECDHE_PSK: + case MBEDTLS_KEY_EXCHANGE_ECJPAKE: + usage = 0; + } + } else +#endif + { + /* This is either TLS 1.3 authentication, which always uses signatures, + * or 1.2 client auth: rsa_sign and mbedtls_ecdsa_sign are the only + * options we implement, both using signatures. */ + (void) tls_version; + (void) ciphersuite; + usage = MBEDTLS_X509_KU_DIGITAL_SIGNATURE; + } + + if (mbedtls_x509_crt_check_key_usage(cert, usage) != 0) { + *flags |= MBEDTLS_X509_BADCERT_KEY_USAGE; + ret = -1; + } + + /* + * extKeyUsage + */ + + if (recv_endpoint == MBEDTLS_SSL_IS_CLIENT) { + ext_oid = MBEDTLS_OID_SERVER_AUTH; + ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH); + } else { + ext_oid = MBEDTLS_OID_CLIENT_AUTH; + ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH); + } + + if (mbedtls_x509_crt_check_extended_key_usage(cert, ext_oid, ext_len) != 0) { + *flags |= MBEDTLS_X509_BADCERT_EXT_KEY_USAGE; + ret = -1; + } + + return ret; +} + +int mbedtls_ssl_verify_certificate(mbedtls_ssl_context *ssl, + int authmode, + mbedtls_x509_crt *chain, + const mbedtls_ssl_ciphersuite_t *ciphersuite_info, + void *rs_ctx) +{ + if (authmode == MBEDTLS_SSL_VERIFY_NONE) { + return 0; + } + + /* + * Primary check: use the appropriate X.509 verification function + */ + int (*f_vrfy)(void *, mbedtls_x509_crt *, int, uint32_t *); + void *p_vrfy; + if (ssl->f_vrfy != NULL) { + MBEDTLS_SSL_DEBUG_MSG(3, ("Use context-specific verification callback")); + f_vrfy = ssl->f_vrfy; + p_vrfy = ssl->p_vrfy; + } else { + MBEDTLS_SSL_DEBUG_MSG(3, ("Use configuration-specific verification callback")); + f_vrfy = ssl->conf->f_vrfy; + p_vrfy = ssl->conf->p_vrfy; + } + + int ret = 0; + int have_ca_chain_or_callback = 0; +#if defined(MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK) + if (ssl->conf->f_ca_cb != NULL) { + ((void) rs_ctx); + have_ca_chain_or_callback = 1; + + MBEDTLS_SSL_DEBUG_MSG(3, ("use CA callback for X.509 CRT verification")); + ret = mbedtls_x509_crt_verify_with_ca_cb( + chain, + ssl->conf->f_ca_cb, + ssl->conf->p_ca_cb, + ssl->conf->cert_profile, + ssl->hostname, + &ssl->session_negotiate->verify_result, + f_vrfy, p_vrfy); + } else +#endif /* MBEDTLS_X509_TRUSTED_CERTIFICATE_CALLBACK */ + { + mbedtls_x509_crt *ca_chain; + mbedtls_x509_crl *ca_crl; +#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) + if (ssl->handshake->sni_ca_chain != NULL) { + ca_chain = ssl->handshake->sni_ca_chain; + ca_crl = ssl->handshake->sni_ca_crl; + } else +#endif + { + ca_chain = ssl->conf->ca_chain; + ca_crl = ssl->conf->ca_crl; + } + + if (ca_chain != NULL) { + have_ca_chain_or_callback = 1; + } + + ret = mbedtls_x509_crt_verify_restartable( + chain, + ca_chain, ca_crl, + ssl->conf->cert_profile, + ssl->hostname, + &ssl->session_negotiate->verify_result, + f_vrfy, p_vrfy, rs_ctx); + } + + if (ret != 0) { + MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret); + } + +#if defined(MBEDTLS_SSL_ECP_RESTARTABLE_ENABLED) + if (ret == MBEDTLS_ERR_ECP_IN_PROGRESS) { + return MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS; + } +#endif + + /* + * Secondary checks: always done, but change 'ret' only if it was 0 + */ + + /* With TLS 1.2 and ECC certs, check that the curve used by the + * certificate is on our list of acceptable curves. + * + * With TLS 1.3 this is not needed because the curve is part of the + * signature algorithm (eg ecdsa_secp256r1_sha256) which is checked when + * we validate the signature made with the key associated to this cert. + */ +#if defined(MBEDTLS_SSL_PROTO_TLS1_2) && \ + defined(MBEDTLS_PK_HAVE_ECC_KEYS) + if (ssl->tls_version == MBEDTLS_SSL_VERSION_TLS1_2 && + mbedtls_pk_can_do(&chain->pk, MBEDTLS_PK_ECKEY)) { + if (mbedtls_ssl_check_curve(ssl, mbedtls_pk_get_ec_group_id(&chain->pk)) != 0) { + MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (EC key curve)")); + ssl->session_negotiate->verify_result |= MBEDTLS_X509_BADCERT_BAD_KEY; + if (ret == 0) { + ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE; + } + } + } +#endif /* MBEDTLS_SSL_PROTO_TLS1_2 && MBEDTLS_PK_HAVE_ECC_KEYS */ + + /* Check X.509 usage extensions (keyUsage, extKeyUsage) */ + if (mbedtls_ssl_check_cert_usage(chain, + ciphersuite_info, + ssl->conf->endpoint, + ssl->tls_version, + &ssl->session_negotiate->verify_result) != 0) { + MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)")); + if (ret == 0) { + ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE; + } + } + + /* With authmode optional, we want to keep going if the certificate was + * unacceptable, but still fail on other errors (out of memory etc), + * including fatal errors from the f_vrfy callback. + * + * The only acceptable errors are: + * - MBEDTLS_ERR_X509_CERT_VERIFY_FAILED: cert rejected by primary check; + * - MBEDTLS_ERR_SSL_BAD_CERTIFICATE: cert rejected by secondary checks. + * Anything else is a fatal error. */ + if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL && + (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED || + ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) { + ret = 0; + } + + /* Return a specific error as this is a user error: inconsistent + * configuration - can't verify without trust anchors. */ + if (have_ca_chain_or_callback == 0 && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) { + MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain")); + ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED; + } + + if (ret != 0) { + uint8_t alert; + + /* The certificate may have been rejected for several reasons. + Pick one and send the corresponding alert. Which alert to send + may be a subject of debate in some cases. */ + if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_OTHER) { + alert = MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED; + } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) { + alert = MBEDTLS_SSL_ALERT_MSG_BAD_CERT; + } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_KEY_USAGE) { + alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT; + } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXT_KEY_USAGE) { + alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT; + } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_PK) { + alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT; + } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_BAD_KEY) { + alert = MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT; + } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_EXPIRED) { + alert = MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED; + } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_REVOKED) { + alert = MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED; + } else if (ssl->session_negotiate->verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) { + alert = MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA; + } else { + alert = MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN; + } + mbedtls_ssl_send_alert_message(ssl, MBEDTLS_SSL_ALERT_LEVEL_FATAL, + alert); + } + +#if defined(MBEDTLS_DEBUG_C) + if (ssl->session_negotiate->verify_result != 0) { + MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x", + (unsigned int) ssl->session_negotiate->verify_result)); + } else { + MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear")); + } +#endif /* MBEDTLS_DEBUG_C */ + + return ret; +} +#endif /* MBEDTLS_SSL_HANDSHAKE_WITH_CERT_ENABLED */ + #endif /* MBEDTLS_SSL_TLS_C */ diff --git a/thirdparty/mbedtls/library/ssl_tls12_client.c b/thirdparty/mbedtls/library/ssl_tls12_client.c index eac6a3aadd..9b2da5a39d 100644 --- a/thirdparty/mbedtls/library/ssl_tls12_client.c +++ b/thirdparty/mbedtls/library/ssl_tls12_client.c @@ -364,7 +364,8 @@ static int ssl_write_session_ticket_ext(mbedtls_ssl_context *ssl, *olen = 0; - if (ssl->conf->session_tickets == MBEDTLS_SSL_SESSION_TICKETS_DISABLED) { + if (mbedtls_ssl_conf_get_session_tickets(ssl->conf) == + MBEDTLS_SSL_SESSION_TICKETS_DISABLED) { return 0; } @@ -787,7 +788,8 @@ static int ssl_parse_session_ticket_ext(mbedtls_ssl_context *ssl, const unsigned char *buf, size_t len) { - if (ssl->conf->session_tickets == MBEDTLS_SSL_SESSION_TICKETS_DISABLED || + if ((mbedtls_ssl_conf_get_session_tickets(ssl->conf) == + MBEDTLS_SSL_SESSION_TICKETS_DISABLED) || len != 0) { MBEDTLS_SSL_DEBUG_MSG(1, ("non-matching session ticket extension")); diff --git a/thirdparty/mbedtls/library/ssl_tls12_server.c b/thirdparty/mbedtls/library/ssl_tls12_server.c index b49a8ae6a6..03722ac33c 100644 --- a/thirdparty/mbedtls/library/ssl_tls12_server.c +++ b/thirdparty/mbedtls/library/ssl_tls12_server.c @@ -756,7 +756,9 @@ static int ssl_pick_cert(mbedtls_ssl_context *ssl, * and decrypting with the same RSA key. */ if (mbedtls_ssl_check_cert_usage(cur->cert, ciphersuite_info, - MBEDTLS_SSL_IS_SERVER, &flags) != 0) { + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_VERSION_TLS1_2, + &flags) != 0) { MBEDTLS_SSL_DEBUG_MSG(3, ("certificate mismatch: " "(extended) key usage extension")); continue; @@ -2631,13 +2633,8 @@ static int ssl_get_ecdh_params_from_cert(mbedtls_ssl_context *ssl) ssl->handshake->xxdh_psa_type = psa_get_key_type(&key_attributes); ssl->handshake->xxdh_psa_bits = psa_get_key_bits(&key_attributes); - if (pk_type == MBEDTLS_PK_OPAQUE) { - /* Opaque key is created by the user (externally from Mbed TLS) - * so we assume it already has the right algorithm and flags - * set. Just copy its ID as reference. */ - ssl->handshake->xxdh_psa_privkey = pk->priv_id; - ssl->handshake->xxdh_psa_privkey_is_external = 1; - } else { +#if defined(MBEDTLS_PK_USE_PSA_EC_DATA) + if (pk_type != MBEDTLS_PK_OPAQUE) { /* PK_ECKEY[_DH] and PK_ECDSA instead as parsed from the PK * module and only have ECDSA capabilities. Since we need * them for ECDH later, we export and then re-import them with @@ -2665,10 +2662,20 @@ static int ssl_get_ecdh_params_from_cert(mbedtls_ssl_context *ssl) /* Set this key as owned by the TLS library: it will be its duty * to clear it exit. */ ssl->handshake->xxdh_psa_privkey_is_external = 0; + + ret = 0; + break; } +#endif /* MBEDTLS_PK_USE_PSA_EC_DATA */ + /* Opaque key is created by the user (externally from Mbed TLS) + * so we assume it already has the right algorithm and flags + * set. Just copy its ID as reference. */ + ssl->handshake->xxdh_psa_privkey = pk->priv_id; + ssl->handshake->xxdh_psa_privkey_is_external = 1; ret = 0; break; + #if !defined(MBEDTLS_PK_USE_PSA_EC_DATA) case MBEDTLS_PK_ECKEY: case MBEDTLS_PK_ECKEY_DH: @@ -3916,7 +3923,7 @@ static int ssl_parse_client_key_exchange(mbedtls_ssl_context *ssl) #if defined(MBEDTLS_USE_PSA_CRYPTO) psa_status_t status = PSA_ERROR_CORRUPTION_DETECTED; psa_status_t destruction_status = PSA_ERROR_CORRUPTION_DETECTED; - uint8_t ecpoint_len; + size_t ecpoint_len; mbedtls_ssl_handshake_params *handshake = ssl->handshake; diff --git a/thirdparty/mbedtls/library/ssl_tls13_client.c b/thirdparty/mbedtls/library/ssl_tls13_client.c index 7fcc394319..b63b5e63c5 100644 --- a/thirdparty/mbedtls/library/ssl_tls13_client.c +++ b/thirdparty/mbedtls/library/ssl_tls13_client.c @@ -666,6 +666,7 @@ static int ssl_tls13_write_psk_key_exchange_modes_ext(mbedtls_ssl_context *ssl, return 0; } +#if defined(MBEDTLS_SSL_SESSION_TICKETS) static psa_algorithm_t ssl_tls13_get_ciphersuite_hash_alg(int ciphersuite) { const mbedtls_ssl_ciphersuite_t *ciphersuite_info = NULL; @@ -678,7 +679,6 @@ static psa_algorithm_t ssl_tls13_get_ciphersuite_hash_alg(int ciphersuite) return PSA_ALG_NONE; } -#if defined(MBEDTLS_SSL_SESSION_TICKETS) static int ssl_tls13_has_configured_ticket(mbedtls_ssl_context *ssl) { mbedtls_ssl_session *session = ssl->session_negotiate; @@ -1141,6 +1141,11 @@ int mbedtls_ssl_tls13_write_client_hello_exts(mbedtls_ssl_context *ssl, *out_len = 0; + ret = mbedtls_ssl_tls13_crypto_init(ssl); + if (ret != 0) { + return ret; + } + /* Write supported_versions extension * * Supported Versions Extension is mandatory with TLS 1.3. diff --git a/thirdparty/mbedtls/library/ssl_tls13_generic.c b/thirdparty/mbedtls/library/ssl_tls13_generic.c index d448a054a9..b6d09788ba 100644 --- a/thirdparty/mbedtls/library/ssl_tls13_generic.c +++ b/thirdparty/mbedtls/library/ssl_tls13_generic.c @@ -27,7 +27,6 @@ #include "psa/crypto.h" #include "psa_util_internal.h" -#if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_EPHEMERAL_ENABLED) /* Define a local translating function to save code size by not using too many * arguments in each translating place. */ static int local_err_translation(psa_status_t status) @@ -37,7 +36,16 @@ static int local_err_translation(psa_status_t status) psa_generic_status_to_mbedtls); } #define PSA_TO_MBEDTLS_ERR(status) local_err_translation(status) -#endif + +int mbedtls_ssl_tls13_crypto_init(mbedtls_ssl_context *ssl) +{ + psa_status_t status = psa_crypto_init(); + if (status != PSA_SUCCESS) { + (void) ssl; // unused when debugging is disabled + MBEDTLS_SSL_DEBUG_RET(1, "psa_crypto_init", status); + } + return PSA_TO_MBEDTLS_ERR(status); +} const uint8_t mbedtls_ssl_tls13_hello_retry_request_magic[ MBEDTLS_SERVER_HELLO_RANDOM_LEN] = @@ -193,10 +201,12 @@ static void ssl_tls13_create_verify_structure(const unsigned char *transcript_ha idx = 64; if (from == MBEDTLS_SSL_IS_CLIENT) { - memcpy(verify_buffer + idx, MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN(client_cv)); + memcpy(verify_buffer + idx, mbedtls_ssl_tls13_labels.client_cv, + MBEDTLS_SSL_TLS1_3_LBL_LEN(client_cv)); idx += MBEDTLS_SSL_TLS1_3_LBL_LEN(client_cv); } else { /* from == MBEDTLS_SSL_IS_SERVER */ - memcpy(verify_buffer + idx, MBEDTLS_SSL_TLS1_3_LBL_WITH_LEN(server_cv)); + memcpy(verify_buffer + idx, mbedtls_ssl_tls13_labels.server_cv, + MBEDTLS_SSL_TLS1_3_LBL_LEN(server_cv)); idx += MBEDTLS_SSL_TLS1_3_LBL_LEN(server_cv); } @@ -470,6 +480,7 @@ int mbedtls_ssl_tls13_parse_certificate(mbedtls_ssl_context *ssl, mbedtls_free(ssl->session_negotiate->peer_cert); } + /* This is used by ssl_tls13_validate_certificate() */ if (certificate_list_len == 0) { ssl->session_negotiate->peer_cert = NULL; ret = 0; @@ -625,25 +636,13 @@ int mbedtls_ssl_tls13_parse_certificate(mbedtls_ssl_context *ssl, MBEDTLS_CHECK_RETURN_CRITICAL static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl) { - int ret = 0; - int authmode = MBEDTLS_SSL_VERIFY_REQUIRED; - mbedtls_x509_crt *ca_chain; - mbedtls_x509_crl *ca_crl; - const char *ext_oid; - size_t ext_len; - uint32_t verify_result = 0; - - /* If SNI was used, overwrite authentication mode - * from the configuration. */ -#if defined(MBEDTLS_SSL_SRV_C) - if (ssl->conf->endpoint == MBEDTLS_SSL_IS_SERVER) { -#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) - if (ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET) { - authmode = ssl->handshake->sni_authmode; - } else -#endif - authmode = ssl->conf->authmode; - } + /* Authmode: precedence order is SNI if used else configuration */ +#if defined(MBEDTLS_SSL_SRV_C) && defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) + const int authmode = ssl->handshake->sni_authmode != MBEDTLS_SSL_VERIFY_UNSET + ? ssl->handshake->sni_authmode + : ssl->conf->authmode; +#else + const int authmode = ssl->conf->authmode; #endif /* @@ -675,6 +674,11 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl) #endif /* MBEDTLS_SSL_SRV_C */ #if defined(MBEDTLS_SSL_CLI_C) + /* Regardless of authmode, the server is not allowed to send an empty + * certificate chain. (Last paragraph before 4.4.2.1 in RFC 8446: "The + * server's certificate_list MUST always be non-empty.") With authmode + * optional/none, we continue the handshake if we can't validate the + * server's cert, but we still break it if no certificate was sent. */ if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) { MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_NO_CERT, MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE); @@ -683,114 +687,9 @@ static int ssl_tls13_validate_certificate(mbedtls_ssl_context *ssl) #endif /* MBEDTLS_SSL_CLI_C */ } -#if defined(MBEDTLS_SSL_SERVER_NAME_INDICATION) - if (ssl->handshake->sni_ca_chain != NULL) { - ca_chain = ssl->handshake->sni_ca_chain; - ca_crl = ssl->handshake->sni_ca_crl; - } else -#endif /* MBEDTLS_SSL_SERVER_NAME_INDICATION */ - { - ca_chain = ssl->conf->ca_chain; - ca_crl = ssl->conf->ca_crl; - } - - /* - * Main check: verify certificate - */ - ret = mbedtls_x509_crt_verify_with_profile( - ssl->session_negotiate->peer_cert, - ca_chain, ca_crl, - ssl->conf->cert_profile, - ssl->hostname, - &verify_result, - ssl->conf->f_vrfy, ssl->conf->p_vrfy); - - if (ret != 0) { - MBEDTLS_SSL_DEBUG_RET(1, "x509_verify_cert", ret); - } - - /* - * Secondary checks: always done, but change 'ret' only if it was 0 - */ - if (ssl->conf->endpoint == MBEDTLS_SSL_IS_CLIENT) { - ext_oid = MBEDTLS_OID_SERVER_AUTH; - ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_SERVER_AUTH); - } else { - ext_oid = MBEDTLS_OID_CLIENT_AUTH; - ext_len = MBEDTLS_OID_SIZE(MBEDTLS_OID_CLIENT_AUTH); - } - - if ((mbedtls_x509_crt_check_key_usage( - ssl->session_negotiate->peer_cert, - MBEDTLS_X509_KU_DIGITAL_SIGNATURE) != 0) || - (mbedtls_x509_crt_check_extended_key_usage( - ssl->session_negotiate->peer_cert, - ext_oid, ext_len) != 0)) { - MBEDTLS_SSL_DEBUG_MSG(1, ("bad certificate (usage extensions)")); - if (ret == 0) { - ret = MBEDTLS_ERR_SSL_BAD_CERTIFICATE; - } - } - - /* mbedtls_x509_crt_verify_with_profile is supposed to report a - * verification failure through MBEDTLS_ERR_X509_CERT_VERIFY_FAILED, - * with details encoded in the verification flags. All other kinds - * of error codes, including those from the user provided f_vrfy - * functions, are treated as fatal and lead to a failure of - * mbedtls_ssl_tls13_parse_certificate even if verification was optional. - */ - if (authmode == MBEDTLS_SSL_VERIFY_OPTIONAL && - (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED || - ret == MBEDTLS_ERR_SSL_BAD_CERTIFICATE)) { - ret = 0; - } - - if (ca_chain == NULL && authmode == MBEDTLS_SSL_VERIFY_REQUIRED) { - MBEDTLS_SSL_DEBUG_MSG(1, ("got no CA chain")); - ret = MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED; - } - - if (ret != 0) { - /* The certificate may have been rejected for several reasons. - Pick one and send the corresponding alert. Which alert to send - may be a subject of debate in some cases. */ - if (verify_result & MBEDTLS_X509_BADCERT_OTHER) { - MBEDTLS_SSL_PEND_FATAL_ALERT( - MBEDTLS_SSL_ALERT_MSG_ACCESS_DENIED, ret); - } else if (verify_result & MBEDTLS_X509_BADCERT_CN_MISMATCH) { - MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_BAD_CERT, ret); - } else if (verify_result & (MBEDTLS_X509_BADCERT_KEY_USAGE | - MBEDTLS_X509_BADCERT_EXT_KEY_USAGE | - MBEDTLS_X509_BADCERT_NS_CERT_TYPE | - MBEDTLS_X509_BADCERT_BAD_PK | - MBEDTLS_X509_BADCERT_BAD_KEY)) { - MBEDTLS_SSL_PEND_FATAL_ALERT( - MBEDTLS_SSL_ALERT_MSG_UNSUPPORTED_CERT, ret); - } else if (verify_result & MBEDTLS_X509_BADCERT_EXPIRED) { - MBEDTLS_SSL_PEND_FATAL_ALERT( - MBEDTLS_SSL_ALERT_MSG_CERT_EXPIRED, ret); - } else if (verify_result & MBEDTLS_X509_BADCERT_REVOKED) { - MBEDTLS_SSL_PEND_FATAL_ALERT( - MBEDTLS_SSL_ALERT_MSG_CERT_REVOKED, ret); - } else if (verify_result & MBEDTLS_X509_BADCERT_NOT_TRUSTED) { - MBEDTLS_SSL_PEND_FATAL_ALERT(MBEDTLS_SSL_ALERT_MSG_UNKNOWN_CA, ret); - } else { - MBEDTLS_SSL_PEND_FATAL_ALERT( - MBEDTLS_SSL_ALERT_MSG_CERT_UNKNOWN, ret); - } - } - -#if defined(MBEDTLS_DEBUG_C) - if (verify_result != 0) { - MBEDTLS_SSL_DEBUG_MSG(3, ("! Certificate verification flags %08x", - (unsigned int) verify_result)); - } else { - MBEDTLS_SSL_DEBUG_MSG(3, ("Certificate verification flags clear")); - } -#endif /* MBEDTLS_DEBUG_C */ - - ssl->session_negotiate->verify_result = verify_result; - return ret; + return mbedtls_ssl_verify_certificate(ssl, authmode, + ssl->session_negotiate->peer_cert, + NULL, NULL); } #else /* MBEDTLS_SSL_KEEP_PEER_CERTIFICATE */ MBEDTLS_CHECK_RETURN_CRITICAL @@ -1482,9 +1381,11 @@ int mbedtls_ssl_tls13_check_early_data_len(mbedtls_ssl_context *ssl, ssl->total_early_data_size)) { MBEDTLS_SSL_DEBUG_MSG( - 2, ("EarlyData: Too much early data received, %u + %" MBEDTLS_PRINTF_SIZET " > %u", - ssl->total_early_data_size, early_data_len, - ssl->session_negotiate->max_early_data_size)); + 2, ("EarlyData: Too much early data received, " + "%lu + %" MBEDTLS_PRINTF_SIZET " > %lu", + (unsigned long) ssl->total_early_data_size, + early_data_len, + (unsigned long) ssl->session_negotiate->max_early_data_size)); MBEDTLS_SSL_PEND_FATAL_ALERT( MBEDTLS_SSL_ALERT_MSG_UNEXPECTED_MESSAGE, diff --git a/thirdparty/mbedtls/library/ssl_tls13_server.c b/thirdparty/mbedtls/library/ssl_tls13_server.c index 2760d76a5d..693edc7b0b 100644 --- a/thirdparty/mbedtls/library/ssl_tls13_server.c +++ b/thirdparty/mbedtls/library/ssl_tls13_server.c @@ -92,8 +92,9 @@ static void ssl_tls13_select_ciphersuite( return; } - MBEDTLS_SSL_DEBUG_MSG(2, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%x", - (unsigned) psk_ciphersuite_id, psk_hash_alg)); + MBEDTLS_SSL_DEBUG_MSG(2, ("No matched ciphersuite, psk_ciphersuite_id=%x, psk_hash_alg=%lx", + (unsigned) psk_ciphersuite_id, + (unsigned long) psk_hash_alg)); } #if defined(MBEDTLS_SSL_TLS1_3_KEY_EXCHANGE_MODE_SOME_PSK_ENABLED) @@ -172,12 +173,12 @@ static int ssl_tls13_parse_key_exchange_modes_ext(mbedtls_ssl_context *ssl, #define SSL_TLS1_3_PSK_IDENTITY_MATCH_BUT_PSK_NOT_USABLE 1 #define SSL_TLS1_3_PSK_IDENTITY_MATCH 0 -#if defined(MBEDTLS_SSL_SESSION_TICKETS) MBEDTLS_CHECK_RETURN_CRITICAL static int ssl_tls13_key_exchange_is_psk_available(mbedtls_ssl_context *ssl); MBEDTLS_CHECK_RETURN_CRITICAL static int ssl_tls13_key_exchange_is_psk_ephemeral_available(mbedtls_ssl_context *ssl); +#if defined(MBEDTLS_SSL_SESSION_TICKETS) MBEDTLS_CHECK_RETURN_CRITICAL static int ssl_tls13_offered_psks_check_identity_match_ticket( mbedtls_ssl_context *ssl, @@ -575,10 +576,8 @@ static int ssl_tls13_parse_pre_shared_key_ext( psa_algorithm_t psk_hash_alg; int allowed_key_exchange_modes; -#if defined(MBEDTLS_SSL_SESSION_TICKETS) mbedtls_ssl_session session; mbedtls_ssl_session_init(&session); -#endif MBEDTLS_SSL_CHK_BUF_READ_PTR(p_identity_len, identities_end, 2 + 1 + 4); identity_len = MBEDTLS_GET_UINT16_BE(p_identity_len, 0); @@ -1356,19 +1355,23 @@ static int ssl_tls13_parse_client_hello(mbedtls_ssl_context *ssl, * compression methods and the length of the extensions. * * cipher_suites cipher_suites_len bytes - * legacy_compression_methods 2 bytes - * extensions_len 2 bytes + * legacy_compression_methods length 1 byte */ - MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, cipher_suites_len + 2 + 2); + MBEDTLS_SSL_CHK_BUF_READ_PTR(p, end, cipher_suites_len + 1); p += cipher_suites_len; cipher_suites_end = p; + /* Check if we have enough data for legacy_compression_methods + * and the length of the extensions (2 bytes). + */ + MBEDTLS_SSL_CHK_BUF_READ_PTR(p + 1, end, p[0] + 2); + /* * Search for the supported versions extension and parse it to determine * if the client supports TLS 1.3. */ ret = mbedtls_ssl_tls13_is_supported_versions_ext_present_in_exts( - ssl, p + 2, end, + ssl, p + 1 + p[0], end, &supported_versions_data, &supported_versions_data_end); if (ret < 0) { MBEDTLS_SSL_DEBUG_RET(1, @@ -1409,6 +1412,12 @@ static int ssl_tls13_parse_client_hello(mbedtls_ssl_context *ssl, ssl->session_negotiate->tls_version = MBEDTLS_SSL_VERSION_TLS1_3; ssl->session_negotiate->endpoint = ssl->conf->endpoint; + /* Before doing any crypto, make sure we can. */ + ret = mbedtls_ssl_tls13_crypto_init(ssl); + if (ret != 0) { + return ret; + } + /* * We are negotiating the version 1.3 of the protocol. Do what we have * postponed: copy of the client random bytes, copy of the legacy session @@ -3109,6 +3118,7 @@ static int ssl_tls13_handshake_wrapup(mbedtls_ssl_context *ssl) return 0; } +#if defined(MBEDTLS_SSL_SESSION_TICKETS) /* * Handler for MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET */ @@ -3138,7 +3148,6 @@ static int ssl_tls13_write_new_session_ticket_coordinate(mbedtls_ssl_context *ss return SSL_NEW_SESSION_TICKET_WRITE; } -#if defined(MBEDTLS_SSL_SESSION_TICKETS) MBEDTLS_CHECK_RETURN_CRITICAL static int ssl_tls13_prepare_new_session_ticket(mbedtls_ssl_context *ssl, unsigned char *ticket_nonce, diff --git a/thirdparty/mbedtls/library/version_features.c b/thirdparty/mbedtls/library/version_features.c index 406161d4c7..f542d9808f 100644 --- a/thirdparty/mbedtls/library/version_features.c +++ b/thirdparty/mbedtls/library/version_features.c @@ -423,6 +423,9 @@ static const char * const features[] = { #if defined(MBEDTLS_PSA_CRYPTO_SPM) "PSA_CRYPTO_SPM", //no-check-names #endif /* MBEDTLS_PSA_CRYPTO_SPM */ +#if defined(MBEDTLS_PSA_KEY_STORE_DYNAMIC) + "PSA_KEY_STORE_DYNAMIC", //no-check-names +#endif /* MBEDTLS_PSA_KEY_STORE_DYNAMIC */ #if defined(MBEDTLS_PSA_P256M_DRIVER_ENABLED) "PSA_P256M_DRIVER_ENABLED", //no-check-names #endif /* MBEDTLS_PSA_P256M_DRIVER_ENABLED */ diff --git a/thirdparty/mbedtls/library/x509_crt.c b/thirdparty/mbedtls/library/x509_crt.c index 2fd56fbd79..53cdcf0266 100644 --- a/thirdparty/mbedtls/library/x509_crt.c +++ b/thirdparty/mbedtls/library/x509_crt.c @@ -48,7 +48,9 @@ #if defined(MBEDTLS_HAVE_TIME) #if defined(_WIN32) && !defined(EFIX64) && !defined(EFI32) +#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN +#endif #include <windows.h> #else #include <time.h> diff --git a/thirdparty/mbedtls/library/x509write_crt.c b/thirdparty/mbedtls/library/x509write_crt.c index 72f5a10a17..56f23c9fab 100644 --- a/thirdparty/mbedtls/library/x509write_crt.c +++ b/thirdparty/mbedtls/library/x509write_crt.c @@ -46,6 +46,10 @@ void mbedtls_x509write_crt_init(mbedtls_x509write_cert *ctx) void mbedtls_x509write_crt_free(mbedtls_x509write_cert *ctx) { + if (ctx == NULL) { + return; + } + mbedtls_asn1_free_named_data_list(&ctx->subject); mbedtls_asn1_free_named_data_list(&ctx->issuer); mbedtls_asn1_free_named_data_list(&ctx->extensions); diff --git a/thirdparty/mbedtls/library/x509write_csr.c b/thirdparty/mbedtls/library/x509write_csr.c index d3ddbcc03d..0d6f6bb1d3 100644 --- a/thirdparty/mbedtls/library/x509write_csr.c +++ b/thirdparty/mbedtls/library/x509write_csr.c @@ -43,6 +43,10 @@ void mbedtls_x509write_csr_init(mbedtls_x509write_csr *ctx) void mbedtls_x509write_csr_free(mbedtls_x509write_csr *ctx) { + if (ctx == NULL) { + return; + } + mbedtls_asn1_free_named_data_list(&ctx->subject); mbedtls_asn1_free_named_data_list(&ctx->extensions); diff --git a/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff b/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff index c5f1970223..3a15928fe1 100644 --- a/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff +++ b/thirdparty/mbedtls/patches/msvc-redeclaration-bug.diff @@ -1,5 +1,5 @@ diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h -index 92f9c824e9..1cc2e7e729 100644 +index 2bbcea3ee0..96baf8f3ed 100644 --- a/thirdparty/mbedtls/include/psa/crypto.h +++ b/thirdparty/mbedtls/include/psa/crypto.h @@ -107,7 +107,9 @@ psa_status_t psa_crypto_init(void); @@ -12,7 +12,7 @@ index 92f9c824e9..1cc2e7e729 100644 /** Declare a key as persistent and set its key identifier. * -@@ -333,7 +335,9 @@ static void psa_set_key_bits(psa_key_attributes_t *attributes, +@@ -336,7 +338,9 @@ static void psa_set_key_bits(psa_key_attributes_t *attributes, * * \return The key type stored in the attribute structure. */ @@ -22,7 +22,7 @@ index 92f9c824e9..1cc2e7e729 100644 /** Retrieve the key size from key attributes. * -@@ -936,7 +940,9 @@ typedef struct psa_hash_operation_s psa_hash_operation_t; +@@ -939,7 +943,9 @@ typedef struct psa_hash_operation_s psa_hash_operation_t; /** Return an initial value for a hash operation object. */ @@ -32,7 +32,7 @@ index 92f9c824e9..1cc2e7e729 100644 /** Set up a multipart hash operation. * -@@ -1295,7 +1301,9 @@ typedef struct psa_mac_operation_s psa_mac_operation_t; +@@ -1298,7 +1304,9 @@ typedef struct psa_mac_operation_s psa_mac_operation_t; /** Return an initial value for a MAC operation object. */ @@ -42,7 +42,7 @@ index 92f9c824e9..1cc2e7e729 100644 /** Set up a multipart MAC calculation operation. * -@@ -1708,7 +1716,9 @@ typedef struct psa_cipher_operation_s psa_cipher_operation_t; +@@ -1711,7 +1719,9 @@ typedef struct psa_cipher_operation_s psa_cipher_operation_t; /** Return an initial value for a cipher operation object. */ @@ -52,7 +52,7 @@ index 92f9c824e9..1cc2e7e729 100644 /** Set the key for a multipart symmetric encryption operation. * -@@ -2226,7 +2236,9 @@ typedef struct psa_aead_operation_s psa_aead_operation_t; +@@ -2229,7 +2239,9 @@ typedef struct psa_aead_operation_s psa_aead_operation_t; /** Return an initial value for an AEAD operation object. */ @@ -62,7 +62,7 @@ index 92f9c824e9..1cc2e7e729 100644 /** Set the key for a multipart authenticated encryption operation. * -@@ -3213,7 +3225,9 @@ typedef struct psa_key_derivation_s psa_key_derivation_operation_t; +@@ -3216,7 +3228,9 @@ typedef struct psa_key_derivation_s psa_key_derivation_operation_t; /** Return an initial value for a key derivation operation object. */ @@ -73,10 +73,10 @@ index 92f9c824e9..1cc2e7e729 100644 /** Set up a key derivation operation. * diff --git a/thirdparty/mbedtls/include/psa/crypto_extra.h b/thirdparty/mbedtls/include/psa/crypto_extra.h -index 6ed1f6c43a..2686b9d74d 100644 +index 0cf42c6055..d276cd4c7f 100644 --- a/thirdparty/mbedtls/include/psa/crypto_extra.h +++ b/thirdparty/mbedtls/include/psa/crypto_extra.h -@@ -915,7 +915,9 @@ typedef struct psa_pake_cipher_suite_s psa_pake_cipher_suite_t; +@@ -923,7 +923,9 @@ typedef struct psa_pake_cipher_suite_s psa_pake_cipher_suite_t; /** Return an initial value for a PAKE cipher suite object. */ @@ -86,7 +86,7 @@ index 6ed1f6c43a..2686b9d74d 100644 /** Retrieve the PAKE algorithm from a PAKE cipher suite. * -@@ -1048,7 +1050,9 @@ typedef struct psa_jpake_computation_stage_s psa_jpake_computation_stage_t; +@@ -1056,7 +1058,9 @@ typedef struct psa_jpake_computation_stage_s psa_jpake_computation_stage_t; /** Return an initial value for a PAKE operation object. */ diff --git a/thirdparty/mbedtls/patches/no-flexible-arrays.diff b/thirdparty/mbedtls/patches/no-flexible-arrays.diff deleted file mode 100644 index 87fd06f1e3..0000000000 --- a/thirdparty/mbedtls/patches/no-flexible-arrays.diff +++ /dev/null @@ -1,132 +0,0 @@ -diff --git a/thirdparty/mbedtls/include/psa/crypto.h b/thirdparty/mbedtls/include/psa/crypto.h -index 7083bd911b..92f9c824e9 100644 ---- a/thirdparty/mbedtls/include/psa/crypto.h -+++ b/thirdparty/mbedtls/include/psa/crypto.h -@@ -3834,12 +3834,14 @@ psa_status_t psa_key_derivation_output_key( - * It is implementation-dependent whether a failure to initialize - * results in this error code. - */ -+#ifndef __cplusplus - psa_status_t psa_key_derivation_output_key_ext( - const psa_key_attributes_t *attributes, - psa_key_derivation_operation_t *operation, - const psa_key_production_parameters_t *params, - size_t params_data_length, - mbedtls_svc_key_id_t *key); -+#endif - - /** Compare output data from a key derivation operation to an expected value. - * -@@ -4180,10 +4182,12 @@ psa_status_t psa_generate_key(const psa_key_attributes_t *attributes, - * It is implementation-dependent whether a failure to initialize - * results in this error code. - */ -+#ifndef __cplusplus - psa_status_t psa_generate_key_ext(const psa_key_attributes_t *attributes, - const psa_key_production_parameters_t *params, - size_t params_data_length, - mbedtls_svc_key_id_t *key); -+#endif - - /**@}*/ - -diff --git a/thirdparty/mbedtls/include/psa/crypto_struct.h b/thirdparty/mbedtls/include/psa/crypto_struct.h -index 3913551aa8..e2c227b2eb 100644 ---- a/thirdparty/mbedtls/include/psa/crypto_struct.h -+++ b/thirdparty/mbedtls/include/psa/crypto_struct.h -@@ -223,11 +223,13 @@ static inline struct psa_key_derivation_s psa_key_derivation_operation_init( - return v; - } - -+#ifndef __cplusplus - struct psa_key_production_parameters_s { - /* Future versions may add other fields in this structure. */ - uint32_t flags; - uint8_t data[]; - }; -+#endif - - /** The default production parameters for key generation or key derivation. - * -diff --git a/thirdparty/mbedtls/include/psa/crypto_types.h b/thirdparty/mbedtls/include/psa/crypto_types.h -index c21bad86cc..a36b6ee65d 100644 ---- a/thirdparty/mbedtls/include/psa/crypto_types.h -+++ b/thirdparty/mbedtls/include/psa/crypto_types.h -@@ -477,7 +477,9 @@ typedef uint16_t psa_key_derivation_step_t; - * - Other key types: reserved for future use. \c flags must be 0. - * - */ -+#ifndef __cplusplus - typedef struct psa_key_production_parameters_s psa_key_production_parameters_t; -+#endif - - /**@}*/ - -diff --git a/thirdparty/mbedtls/library/psa_crypto_core.h b/thirdparty/mbedtls/library/psa_crypto_core.h -index 9462d2e8be..c059162efe 100644 ---- a/thirdparty/mbedtls/library/psa_crypto_core.h -+++ b/thirdparty/mbedtls/library/psa_crypto_core.h -@@ -351,9 +351,11 @@ psa_status_t psa_export_public_key_internal( - * \param[in] params The key production parameters to check. - * \param params_data_length Size of `params->data` in bytes. - */ -+#ifndef __cplusplus - int psa_key_production_parameters_are_default( - const psa_key_production_parameters_t *params, - size_t params_data_length); -+#endif - - /** - * \brief Generate a key. -@@ -378,12 +380,14 @@ int psa_key_production_parameters_are_default( - * \retval #PSA_ERROR_BUFFER_TOO_SMALL - * The size of \p key_buffer is too small. - */ -+#ifndef __cplusplus - psa_status_t psa_generate_key_internal(const psa_key_attributes_t *attributes, - const psa_key_production_parameters_t *params, - size_t params_data_length, - uint8_t *key_buffer, - size_t key_buffer_size, - size_t *key_buffer_length); -+#endif - - /** Sign a message with a private key. For hash-and-sign algorithms, - * this includes the hashing step. -diff --git a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h -index ea6aee32eb..6919971aca 100644 ---- a/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h -+++ b/thirdparty/mbedtls/library/psa_crypto_driver_wrappers.h -@@ -728,6 +728,7 @@ static inline psa_status_t psa_driver_wrapper_get_key_buffer_size_from_key_data( - } - } - -+#ifndef __cplusplus - static inline psa_status_t psa_driver_wrapper_generate_key( - const psa_key_attributes_t *attributes, - const psa_key_production_parameters_t *params, size_t params_data_length, -@@ -832,6 +833,7 @@ static inline psa_status_t psa_driver_wrapper_generate_key( - - return( status ); - } -+#endif - - static inline psa_status_t psa_driver_wrapper_import_key( - const psa_key_attributes_t *attributes, -diff --git a/thirdparty/mbedtls/library/psa_crypto_rsa.h b/thirdparty/mbedtls/library/psa_crypto_rsa.h -index ffeef26be1..6d695ddf50 100644 ---- a/thirdparty/mbedtls/library/psa_crypto_rsa.h -+++ b/thirdparty/mbedtls/library/psa_crypto_rsa.h -@@ -130,10 +130,12 @@ psa_status_t mbedtls_psa_rsa_export_public_key( - * \retval #PSA_ERROR_BUFFER_TOO_SMALL - * The size of \p key_buffer is too small. - */ -+#ifndef __cplusplus - psa_status_t mbedtls_psa_rsa_generate_key( - const psa_key_attributes_t *attributes, - const psa_key_production_parameters_t *params, size_t params_data_length, - uint8_t *key_buffer, size_t key_buffer_size, size_t *key_buffer_length); -+#endif - - /** Sign an already-calculated hash with an RSA private key. - * diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 67ebc9381e..654d70f918 100644 --- a/thirdparty/thorvg/inc/config.h +++ b/thirdparty/thorvg/inc/config.h @@ -15,5 +15,5 @@ // For internal debugging: //#define THORVG_LOG_ENABLED -#define THORVG_VERSION_STRING "0.14.7" +#define THORVG_VERSION_STRING "0.14.9" #endif diff --git a/thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch b/thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch new file mode 100644 index 0000000000..c79f7c63d6 --- /dev/null +++ b/thirdparty/thorvg/patches/pr2716-text-drawing-reliability.patch @@ -0,0 +1,25 @@ +From 41d67213607e7ff20b7a3ca833f1cfde9780da65 Mon Sep 17 00:00:00 2001 +From: Hermet Park <hermet@lottiefiles.com> +Date: Sat, 7 Sep 2024 01:35:09 +0900 +Subject: [PATCH] renderer: ++reliability in text drawing + +Allow the canvas to pass through +even if text elements are not properly supported. + +issue: https://github.com/thorvg/thorvg/issues/2715 +--- + src/renderer/tvgText.h | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h +index 746b85bea6..55d33ffd4b 100644 +--- a/thirdparty/thorvg/src/renderer/tvgText.h ++++ b/thirdparty/thorvg/src/renderer/tvgText.h +@@ -89,6 +89,7 @@ struct Text::Impl + + bool render(RenderMethod* renderer) + { ++ if (!loader) return true; + renderer->blend(paint->blend(), true); + return PP(shape)->render(renderer); + } diff --git a/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch b/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch new file mode 100644 index 0000000000..dd6c8ba5e7 --- /dev/null +++ b/thirdparty/thorvg/patches/revert-tvgLines-bezier-precision-change.patch @@ -0,0 +1,13 @@ +diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp +index 49d992f127..9d704900a5 100644 +--- a/thirdparty/thorvg/src/common/tvgLines.cpp ++++ b/thirdparty/thorvg/src/common/tvgLines.cpp +@@ -79,7 +79,7 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc + Bezier left; + bezSplitLeft(right, t, left); + length = _bezLength(left, lineLengthFunc); +- if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) { ++ if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) { + break; + } + if (length < at) { diff --git a/thirdparty/thorvg/src/common/tvgLines.cpp b/thirdparty/thorvg/src/common/tvgLines.cpp index 49d992f127..9d704900a5 100644 --- a/thirdparty/thorvg/src/common/tvgLines.cpp +++ b/thirdparty/thorvg/src/common/tvgLines.cpp @@ -79,7 +79,7 @@ float _bezAt(const Bezier& bz, float at, float length, LengthFunc lineLengthFunc Bezier left; bezSplitLeft(right, t, left); length = _bezLength(left, lineLengthFunc); - if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < 1e-3f) { + if (fabsf(length - at) < BEZIER_EPSILON || fabsf(smallest - biggest) < BEZIER_EPSILON) { break; } if (length < at) { diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp index c56b32249f..c03b54e5f8 100644 --- a/thirdparty/thorvg/src/common/tvgMath.cpp +++ b/thirdparty/thorvg/src/common/tvgMath.cpp @@ -43,9 +43,8 @@ bool mathInverse(const Matrix* m, Matrix* out) m->e12 * (m->e21 * m->e33 - m->e23 * m->e31) + m->e13 * (m->e21 * m->e32 - m->e22 * m->e31); - if (mathZero(det)) return false; - - auto invDet = 1 / det; + auto invDet = 1.0f / det; + if (std::isinf(invDet)) return false; out->e11 = (m->e22 * m->e33 - m->e32 * m->e23) * invDet; out->e12 = (m->e13 * m->e32 - m->e12 * m->e33) * invDet; @@ -133,3 +132,10 @@ Point operator*(const Point& pt, const Matrix& m) auto ty = pt.x * m.e21 + pt.y * m.e22 + m.e23; return {tx, ty}; } + +uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t) +{ + auto result = static_cast<int>(start + (end - start) * t); + mathClamp(result, 0, 255); + return static_cast<uint8_t>(result); +} diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h index 556ed410ff..50786754a1 100644 --- a/thirdparty/thorvg/src/common/tvgMath.h +++ b/thirdparty/thorvg/src/common/tvgMath.h @@ -26,7 +26,7 @@ #define _USE_MATH_DEFINES #include <float.h> -#include <math.h> +#include <cmath> #include "tvgCommon.h" #define MATH_PI 3.14159265358979323846f @@ -68,6 +68,13 @@ static inline bool mathEqual(float a, float b) } +template <typename T> +static inline void mathClamp(T& v, const T& min, const T& max) +{ + if (v < min) v = min; + else if (v > max) v = max; +} + /************************************************************************/ /* Matrix functions */ /************************************************************************/ @@ -259,5 +266,6 @@ static inline T mathLerp(const T &start, const T &end, float t) return static_cast<T>(start + (end - start) * t); } +uint8_t mathLerp(const uint8_t &start, const uint8_t &end, float t); #endif //_TVG_MATH_H_ diff --git a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp index 197d3cc13a..cccd056a13 100644 --- a/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp +++ b/thirdparty/thorvg/src/loaders/svg/tvgSvgLoader.cpp @@ -588,6 +588,11 @@ static bool _hslToRgb(float hue, float saturation, float brightness, uint8_t* re float _red = 0, _green = 0, _blue = 0; uint32_t i = 0; + while (hue < 0) hue += 360.0f; + hue = fmod(hue, 360.0f); + saturation = saturation > 0 ? std::min(saturation, 1.0f) : 0.0f; + brightness = brightness > 0 ? std::min(brightness, 1.0f) : 0.0f; + if (mathZero(saturation)) _red = _green = _blue = brightness; else { if (mathEqual(hue, 360.0)) hue = 0.0f; @@ -714,7 +719,6 @@ static bool _toColor(const char* str, uint8_t* r, uint8_t* g, uint8_t* b, char** const char* hue = nullptr; if (_parseNumber(&content, &hue, &th) && hue) { const char* saturation = nullptr; - th = float(uint32_t(th) % 360); hue = _skipSpace(hue, nullptr); hue = (char*)_skipComma(hue); hue = _skipSpace(hue, nullptr); @@ -3284,6 +3288,7 @@ static void _svgLoaderParserXmlClose(SvgLoaderData* loader, const char* content, for (unsigned int i = 0; i < sizeof(graphicsTags) / sizeof(graphicsTags[0]); i++) { if (!strncmp(tagName, graphicsTags[i].tag, sz)) { loader->currentGraphicsNode = nullptr; + if (!strncmp(tagName, "text", 4)) loader->openedTag = OpenedTagType::Other; loader->stack.pop(); break; } @@ -3357,11 +3362,9 @@ static void _svgLoaderParserXmlOpen(SvgLoaderData* loader, const char* content, node = method(loader, parent, attrs, attrsLength, simpleXmlParseAttributes); if (node && !empty) { if (!strcmp(tagName, "text")) loader->openedTag = OpenedTagType::Text; - else { - auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr); - loader->stack.push(defs); - loader->currentGraphicsNode = node; - } + auto defs = _createDefsNode(loader, nullptr, nullptr, 0, nullptr); + loader->stack.push(defs); + loader->currentGraphicsNode = node; } } else if ((gradientMethod = _findGradientFactory(tagName))) { SvgStyleGradient* gradient; @@ -3399,7 +3402,6 @@ static void _svgLoaderParserText(SvgLoaderData* loader, const char* content, uns auto text = &loader->svgParse->node->node.text; if (text->text) free(text->text); text->text = strDuplicate(content, length); - loader->openedTag = OpenedTagType::Other; } diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h index 3229eb39ba..20442c1c20 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h @@ -367,7 +367,7 @@ static inline uint32_t opBlendSrcOver(uint32_t s, TVG_UNUSED uint32_t d, TVG_UNU } //TODO: BlendMethod could remove the alpha parameter. -static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +static inline uint32_t opBlendDifference(uint32_t s, uint32_t d, uint8_t a) { //if (s > d) => s - d //else => d - s @@ -404,8 +404,7 @@ static inline uint32_t opBlendScreen(uint32_t s, uint32_t d, TVG_UNUSED uint8_t return JOIN(255, c1, c2, c3); } - -static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) +static inline uint32_t opBlendDirectMultiply(uint32_t s, uint32_t d, uint8_t a) { // s * d auto c1 = MULTIPLY(C1(s), C1(d)); @@ -414,6 +413,10 @@ static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, TVG_UNUSED uint8_ return JOIN(255, c1, c2, c3); } +static inline uint32_t opBlendMultiply(uint32_t s, uint32_t d, uint8_t a) +{ + return opBlendDirectMultiply(s, d, a) + ALPHA_BLEND(d, IA(s)); +} static inline uint32_t opBlendOverlay(uint32_t s, uint32_t d, TVG_UNUSED uint8_t a) { diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp index a6f9d8745a..90d91e17e0 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRaster.cpp @@ -1383,7 +1383,7 @@ static bool _rasterDirectBlendingImage(SwSurface* surface, const SwImage* image, *dst = INTERPOLATE(tmp, *dst, A(*src)); } } else { - for (auto x = region.min.x; x < region.max.x; ++x, ++dst, ++src) { + for (auto x = region.min.x; x < region.max.x; x++, dst++, src++) { auto tmp = ALPHA_BLEND(*src, opacity); auto tmp2 = surface->blender(tmp, *dst, 255); *dst = INTERPOLATE(tmp2, *dst, A(tmp)); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp index 47a7166115..6470089c7c 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -100,7 +100,6 @@ struct SwShapeTask : SwTask return (width * sqrt(transform.e11 * transform.e11 + transform.e12 * transform.e12)); } - bool clip(SwRleData* target) override { if (shape.fastTrack) rleClipRect(target, &bbox); @@ -447,7 +446,7 @@ bool SwRenderer::renderShape(RenderData data) } -bool SwRenderer::blend(BlendMethod method) +bool SwRenderer::blend(BlendMethod method, bool direct) { if (surface->blendMethod == method) return true; surface->blendMethod = method; @@ -460,7 +459,7 @@ bool SwRenderer::blend(BlendMethod method) surface->blender = opBlendScreen; break; case BlendMethod::Multiply: - surface->blender = opBlendMultiply; + surface->blender = direct ? opBlendDirectMultiply : opBlendMultiply; break; case BlendMethod::Overlay: surface->blender = opBlendOverlay; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h index fcd8ad4620..7aa6d89aaa 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwRenderer.h @@ -46,7 +46,7 @@ public: RenderRegion region(RenderData data) override; RenderRegion viewport() override; bool viewport(const RenderRegion& vp) override; - bool blend(BlendMethod method) override; + bool blend(BlendMethod method, bool direct) override; ColorSpace colorSpace() override; const Surface* mainSurface() override; diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp index 96c3bc28b9..24c4a9e372 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwShape.cpp @@ -210,7 +210,7 @@ static void _dashCubicTo(SwDashStroke& dash, const Point* ctrl1, const Point* ct } _outlineCubicTo(*dash.outline, &cur.ctrl1, &cur.ctrl2, &cur.end, transform); } - if (dash.curLen < 1 && TO_SWCOORD(len) > 1) { + if (dash.curLen < 0.1f && TO_SWCOORD(len) > 1) { //move to next dash dash.curIdx = (dash.curIdx + 1) % dash.cnt; dash.curLen = dash.pattern[dash.curIdx]; diff --git a/thirdparty/thorvg/src/renderer/tvgLoadModule.h b/thirdparty/thorvg/src/renderer/tvgLoadModule.h index c750683771..1b81d81a4f 100644 --- a/thirdparty/thorvg/src/renderer/tvgLoadModule.h +++ b/thirdparty/thorvg/src/renderer/tvgLoadModule.h @@ -101,7 +101,8 @@ struct FontLoader : LoadModule FontLoader(FileType type) : LoadModule(type) {} - virtual bool request(Shape* shape, char* text, bool italic = false) = 0; + virtual bool request(Shape* shape, char* text) = 0; + virtual bool transform(Paint* paint, float fontSize, bool italic) = 0; }; #endif //_TVG_LOAD_MODULE_H_ diff --git a/thirdparty/thorvg/src/renderer/tvgPaint.cpp b/thirdparty/thorvg/src/renderer/tvgPaint.cpp index 11cee0c54a..37813b19ef 100644 --- a/thirdparty/thorvg/src/renderer/tvgPaint.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPaint.cpp @@ -225,8 +225,6 @@ bool Paint::Impl::render(RenderMethod* renderer) if (cmp) renderer->beginComposite(cmp, compData->method, compData->target->pImpl->opacity); - renderer->blend(blendMethod); - bool ret; PAINT_METHOD(ret, render(renderer)); diff --git a/thirdparty/thorvg/src/renderer/tvgPicture.cpp b/thirdparty/thorvg/src/renderer/tvgPicture.cpp index 68c7a41032..e6653b1c99 100644 --- a/thirdparty/thorvg/src/renderer/tvgPicture.cpp +++ b/thirdparty/thorvg/src/renderer/tvgPicture.cpp @@ -73,6 +73,8 @@ bool Picture::Impl::needComposition(uint8_t opacity) bool Picture::Impl::render(RenderMethod* renderer) { bool ret = false; + renderer->blend(picture->blend(), true); + if (surface) return renderer->renderImage(rd); else if (paint) { Compositor* cmp = nullptr; diff --git a/thirdparty/thorvg/src/renderer/tvgRender.h b/thirdparty/thorvg/src/renderer/tvgRender.h index b0ee42db8d..f1042884ec 100644 --- a/thirdparty/thorvg/src/renderer/tvgRender.h +++ b/thirdparty/thorvg/src/renderer/tvgRender.h @@ -296,7 +296,7 @@ public: virtual RenderRegion region(RenderData data) = 0; virtual RenderRegion viewport() = 0; virtual bool viewport(const RenderRegion& vp) = 0; - virtual bool blend(BlendMethod method) = 0; + virtual bool blend(BlendMethod method, bool direct = false) = 0; virtual ColorSpace colorSpace() = 0; virtual const Surface* mainSurface() = 0; diff --git a/thirdparty/thorvg/src/renderer/tvgScene.h b/thirdparty/thorvg/src/renderer/tvgScene.h index cb6d179326..190ecd31b9 100644 --- a/thirdparty/thorvg/src/renderer/tvgScene.h +++ b/thirdparty/thorvg/src/renderer/tvgScene.h @@ -120,6 +120,8 @@ struct Scene::Impl Compositor* cmp = nullptr; auto ret = true; + renderer->blend(scene->blend()); + if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); renderer->beginComposite(cmp, CompositeMethod::None, opacity); diff --git a/thirdparty/thorvg/src/renderer/tvgShape.h b/thirdparty/thorvg/src/renderer/tvgShape.h index b76fde7ced..94704aee67 100644 --- a/thirdparty/thorvg/src/renderer/tvgShape.h +++ b/thirdparty/thorvg/src/renderer/tvgShape.h @@ -54,10 +54,13 @@ struct Shape::Impl Compositor* cmp = nullptr; bool ret; + renderer->blend(shape->blend(), !needComp); + if (needComp) { cmp = renderer->target(bounds(renderer), renderer->colorSpace()); renderer->beginComposite(cmp, CompositeMethod::None, opacity); } + ret = renderer->renderShape(rd); if (cmp) renderer->endComposite(cmp); return ret; diff --git a/thirdparty/thorvg/src/renderer/tvgText.h b/thirdparty/thorvg/src/renderer/tvgText.h index f6faec2d53..55d33ffd4b 100644 --- a/thirdparty/thorvg/src/renderer/tvgText.h +++ b/thirdparty/thorvg/src/renderer/tvgText.h @@ -26,12 +26,7 @@ #include <cstring> #include "tvgShape.h" #include "tvgFill.h" - -#ifdef THORVG_TTF_LOADER_SUPPORT - #include "tvgTtfLoader.h" -#else - #include "tvgLoader.h" -#endif +#include "tvgLoader.h" struct Text::Impl { @@ -69,6 +64,11 @@ struct Text::Impl auto loader = LoaderMgr::loader(name); if (!loader) return Result::InsufficientCondition; + if (style && strstr(style, "italic")) italic = true; + else italic = false; + + fontSize = size; + //Same resource has been loaded. if (this->loader == loader) { this->loader->sharing--; //make it sure the reference counting. @@ -78,8 +78,6 @@ struct Text::Impl } this->loader = static_cast<FontLoader*>(loader); - fontSize = size; - if (style && strstr(style, "italic")) italic = true; changed = true; return Result::Success; } @@ -91,6 +89,8 @@ struct Text::Impl bool render(RenderMethod* renderer) { + if (!loader) return true; + renderer->blend(paint->blend(), true); return PP(shape)->render(renderer); } @@ -98,13 +98,13 @@ struct Text::Impl { if (!loader) return false; + loader->request(shape, utf8); //reload if (changed) { - loader->request(shape, utf8, italic); loader->read(); changed = false; } - return loader->resize(shape, fontSize, fontSize); + return loader->transform(shape, fontSize, italic); } RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& clips, uint8_t opacity, RenderUpdateFlag pFlag, TVG_UNUSED bool clipper) diff --git a/thirdparty/thorvg/update-thorvg.sh b/thirdparty/thorvg/update-thorvg.sh index 7ff07fe4c2..1ec21f3dd8 100755 --- a/thirdparty/thorvg/update-thorvg.sh +++ b/thirdparty/thorvg/update-thorvg.sh @@ -1,16 +1,22 @@ #!/bin/bash -e -VERSION=0.14.7 +VERSION=0.14.9 +# Uncomment and set a git hash to use specific commit instead of tag. +#GIT_COMMIT= -cd thirdparty/thorvg/ || true +pushd "$(dirname "$0")" rm -rf AUTHORS LICENSE inc/ src/ *.zip *.tar.gz tmp/ mkdir tmp/ && pushd tmp/ # Release -curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz -# Current Github main branch tip -#curl -L -O https://github.com/thorvg/thorvg/archive/refs/heads/main.tar.gz +if [ ! -z "$GIT_COMMIT" ]; then + echo "Updating ThorVG to commit:" $GIT_COMMIT + curl -L -O https://github.com/thorvg/thorvg/archive/$GIT_COMMIT.tar.gz +else + echo "Updating ThorVG to tagged release:" $VERSION + curl -L -O https://github.com/thorvg/thorvg/archive/v$VERSION.tar.gz +fi tar --strip-components=1 -xvf *.tar.gz rm *.tar.gz @@ -70,4 +76,4 @@ cp -rv src/loaders/jpg ../src/loaders/ popd rm -rf tmp - +popd |