diff options
265 files changed, 6507 insertions, 2935 deletions
diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml index 90629204e6..0c21576517 100644 --- a/.github/workflows/windows_builds.yml +++ b/.github/workflows/windows_builds.yml @@ -30,6 +30,14 @@ jobs: # Skip debug symbols, they're way too big with MSVC. sconsflags: debug_symbols=no vsproj=yes vsproj_gen_only=no windows_subsystem=console bin: "./bin/godot.windows.editor.x86_64.exe" + artifact: true + + - name: Editor w/ clang-cl (target=editor, tests=yes, use_llvm=yes) + cache-name: windows-editor-clang + target: editor + tests: true + sconsflags: debug_symbols=no windows_subsystem=console use_llvm=yes + bin: ./bin/godot.windows.editor.x86_64.llvm.exe - name: Template (target=template_release) cache-name: windows-template @@ -37,6 +45,7 @@ jobs: tests: true sconsflags: debug_symbols=no tests=yes bin: "./bin/godot.windows.template_release.x86_64.console.exe" + artifact: true steps: - uses: actions/checkout@v4 @@ -84,10 +93,12 @@ jobs: continue-on-error: true - name: Prepare artifact + if: ${{ matrix.artifact }} run: | Remove-Item bin/* -Include *.exp,*.lib,*.pdb -Force - name: Upload artifact + if: ${{ matrix.artifact }} uses: ./.github/actions/upload-artifact with: name: ${{ matrix.cache-name }} diff --git a/SConstruct b/SConstruct index e1c943bd45..0297cd6e61 100644 --- a/SConstruct +++ b/SConstruct @@ -803,7 +803,7 @@ elif env.msvc: env.Append(CXXFLAGS=["/EHsc"]) # Configure compiler warnings -if env.msvc: # MSVC +if env.msvc and not methods.using_clang(env): # MSVC if env["warnings"] == "no": env.Append(CCFLAGS=["/w"]) else: @@ -853,8 +853,11 @@ else: # GCC, Clang # for putting them in `Set` or `Map`. We don't mind about unreliable ordering. common_warnings += ["-Wno-ordered-compare-function-pointers"] + # clang-cl will interpret `-Wall` as `-Weverything`, workaround with compatibility cast + W_ALL = "-Wall" if not env.msvc else "-W3" + if env["warnings"] == "extra": - env.Append(CCFLAGS=["-Wall", "-Wextra", "-Wwrite-strings", "-Wno-unused-parameter"] + common_warnings) + env.Append(CCFLAGS=[W_ALL, "-Wextra", "-Wwrite-strings", "-Wno-unused-parameter"] + common_warnings) env.Append(CXXFLAGS=["-Wctor-dtor-privacy", "-Wnon-virtual-dtor"]) if methods.using_gcc(env): env.Append( @@ -876,9 +879,9 @@ else: # GCC, Clang elif methods.using_clang(env) or methods.using_emcc(env): env.Append(CCFLAGS=["-Wimplicit-fallthrough"]) elif env["warnings"] == "all": - env.Append(CCFLAGS=["-Wall"] + common_warnings) + env.Append(CCFLAGS=[W_ALL] + common_warnings) elif env["warnings"] == "moderate": - env.Append(CCFLAGS=["-Wall", "-Wno-unused"] + common_warnings) + env.Append(CCFLAGS=[W_ALL, "-Wno-unused"] + common_warnings) else: # 'no' env.Append(CCFLAGS=["-w"]) @@ -1032,7 +1035,9 @@ if env["vsproj"]: if env["compiledb"]: if env.scons_version < (4, 0, 0): # Generating the compilation DB (`compile_commands.json`) requires SCons 4.0.0 or later. - print_error("The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version) + print_error( + "The `compiledb=yes` option requires SCons 4.0 or later, but your version is %s." % scons_raw_version + ) Exit(255) env.Tool("compilation_db") diff --git a/core/SCsub b/core/SCsub index 1bd4eae16c..c8267ae960 100644 --- a/core/SCsub +++ b/core/SCsub @@ -140,7 +140,7 @@ if env["builtin_zstd"]: "decompress/zstd_decompress_block.c", "decompress/zstd_decompress.c", ] - if env["platform"] in ["android", "ios", "linuxbsd", "macos"]: + if env["platform"] in ["android", "ios", "linuxbsd", "macos"] and env["arch"] == "x86_64": # Match platforms with ZSTD_ASM_SUPPORTED in common/portability_macros.h thirdparty_zstd_sources.append("decompress/huf_decompress_amd64.S") thirdparty_zstd_sources = [thirdparty_zstd_dir + file for file in thirdparty_zstd_sources] diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp index 21a9014626..9dca47a0b4 100644 --- a/core/debugger/remote_debugger_peer.cpp +++ b/core/debugger/remote_debugger_peer.cpp @@ -144,9 +144,8 @@ void RemoteDebuggerPeerTCP::_read_in() { Error err = decode_variant(var, buf, in_pos, &read); ERR_CONTINUE(read != in_pos || err != OK); ERR_CONTINUE_MSG(var.get_type() != Variant::ARRAY, "Malformed packet received, not an Array."); - mutex.lock(); + MutexLock lock(mutex); in_queue.push_back(var); - mutex.unlock(); } } } diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index c9e609cddc..e764b9c112 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -678,12 +678,10 @@ Error GDExtension::open_library(const String &p_path, const Ref<GDExtensionLoade ERR_FAIL_NULL_V_MSG(p_loader, FAILED, "Can't open GDExtension without a loader."); loader = p_loader; - String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path); + Error err = loader->open_library(p_path); - Error err = loader->open_library(abs_path); - - ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path); - ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path); + ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + p_path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + p_path); err = loader->initialize(&gdextension_get_proc_address, this, &initialization); diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index a5a0fc906a..0ebe86d0a7 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -1299,7 +1299,7 @@ static void gdextension_object_call_script_method(GDExtensionObjectPtr p_object, const StringName method = *reinterpret_cast<const StringName *>(p_method); const Variant **args = (const Variant **)p_args; - Callable::CallError error; + Callable::CallError error; // TODO: Check `error`? memnew_placement(r_return, Variant); *(Variant *)r_return = o->callp(method, args, p_argument_count, error); diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index eeae6b1996..01efe0d96e 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -32,14 +32,18 @@ #include "core/extension/gdextension_compat_hashes.h" #include "core/extension/gdextension_library_loader.h" +#include "core/io/dir_access.h" #include "core/io/file_access.h" #include "core/object/script_language.h" -GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension) { +GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) { if (level >= 0) { // Already initialized up to some level. - int32_t minimum_level = p_extension->get_minimum_library_initialization_level(); - if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) { - return LOAD_STATUS_NEEDS_RESTART; + int32_t minimum_level = 0; + if (!p_first_load) { + minimum_level = p_extension->get_minimum_library_initialization_level(); + if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) { + return LOAD_STATUS_NEEDS_RESTART; + } } // Initialize up to current level. for (int32_t i = minimum_level; i <= level; i++) { @@ -51,10 +55,20 @@ GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(cons gdextension_class_icon_paths[kv.key] = kv.value; } +#ifdef TOOLS_ENABLED + // Signals that a new extension is loaded so GDScript can register new class names. + emit_signal("extension_loaded", p_extension); +#endif + return LOAD_STATUS_OK; } GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) { +#ifdef TOOLS_ENABLED + // Signals that a new extension is unloading so GDScript can unregister class names. + emit_signal("extension_unloading", p_extension); +#endif + if (level >= 0) { // Already initialized up to some level. // Deinitialize down from current level. for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) { @@ -89,7 +103,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(co return LOAD_STATUS_FAILED; } - LoadStatus status = _load_extension_internal(extension); + LoadStatus status = _load_extension_internal(extension, true); if (status != LOAD_STATUS_OK) { return status; } @@ -135,7 +149,7 @@ GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String return LOAD_STATUS_FAILED; } - status = _load_extension_internal(extension); + status = _load_extension_internal(extension, false); if (status != LOAD_STATUS_OK) { return status; } @@ -274,6 +288,71 @@ void GDExtensionManager::reload_extensions() { #endif } +bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) { + Vector<String> extensions_added; + Vector<String> extensions_removed; + + for (const String &E : p_extensions) { + if (!is_extension_loaded(E)) { + extensions_added.push_back(E); + } + } + + Vector<String> loaded_extensions = get_loaded_extensions(); + for (const String &loaded_extension : loaded_extensions) { + if (!p_extensions.has(loaded_extension)) { + // The extension may not have a .gdextension file. + if (!FileAccess::exists(loaded_extension)) { + extensions_removed.push_back(loaded_extension); + } + } + } + + String extension_list_config_file = GDExtension::get_extension_list_config_file(); + if (p_extensions.size()) { + if (extensions_added.size() || extensions_removed.size()) { + // Extensions were added or removed. + Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE); + for (const String &E : p_extensions) { + f->store_line(E); + } + } + } else { + if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { + // Extensions were removed. + Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); + da->remove(extension_list_config_file); + } + } + + bool needs_restart = false; + for (const String &extension : extensions_added) { + GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension); + if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { + needs_restart = true; + } + } + + for (const String &extension : extensions_removed) { + GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension); + if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { + needs_restart = true; + } + } + +#ifdef TOOLS_ENABLED + if (extensions_added.size() || extensions_removed.size()) { + // Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation. + emit_signal("extensions_reloaded"); + + // Reload all scripts to clear out old references. + callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred(); + } +#endif + + return needs_restart; +} + GDExtensionManager *GDExtensionManager::get_singleton() { return singleton; } @@ -294,6 +373,8 @@ void GDExtensionManager::_bind_methods() { BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART); ADD_SIGNAL(MethodInfo("extensions_reloaded")); + ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension"))); + ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension"))); } GDExtensionManager *GDExtensionManager::singleton = nullptr; diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h index b488189604..39a600474c 100644 --- a/core/extension/gdextension_manager.h +++ b/core/extension/gdextension_manager.h @@ -54,7 +54,7 @@ public: }; private: - LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension); + LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load); LoadStatus _unload_extension_internal(const Ref<GDExtension> &p_extension); #ifdef TOOLS_ENABLED @@ -85,6 +85,7 @@ public: void load_extensions(); void reload_extensions(); + bool ensure_extensions_loaded(const HashSet<String> &p_extensions); GDExtensionManager(); ~GDExtensionManager(); diff --git a/core/io/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..605c5b2f6f 100644 --- a/core/io/file_access_encrypted.cpp +++ b/core/io/file_access_encrypted.cpp @@ -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/json.cpp b/core/io/json.cpp index 61051727c1..664ff7857b 100644 --- a/core/io/json.cpp +++ b/core/io/json.cpp @@ -588,10 +588,756 @@ void JSON::_bind_methods() { ClassDB::bind_method(D_METHOD("get_error_line"), &JSON::get_error_line); ClassDB::bind_method(D_METHOD("get_error_message"), &JSON::get_error_message); + ClassDB::bind_static_method("JSON", D_METHOD("to_native", "json", "allow_classes", "allow_scripts"), &JSON::to_native, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_static_method("JSON", D_METHOD("from_native", "variant", "allow_classes", "allow_scripts"), &JSON::from_native, DEFVAL(false), DEFVAL(false)); + ADD_PROPERTY(PropertyInfo(Variant::NIL, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT), "set_data", "get_data"); // Ensures that it can be serialized as binary. } -//// +#define GDTYPE "__gdtype" +#define VALUES "values" +#define PASS_ARG p_allow_classes, p_allow_scripts + +Variant JSON::from_native(const Variant &p_variant, bool p_allow_classes, bool p_allow_scripts) { + switch (p_variant.get_type()) { + case Variant::NIL: { + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; + } break; + case Variant::BOOL: { + return p_variant; + } break; + case Variant::INT: { + return p_variant; + } break; + case Variant::FLOAT: { + return p_variant; + } break; + case Variant::STRING: { + return p_variant; + } break; + case Variant::VECTOR2: { + Dictionary d; + Vector2 v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR2I: { + Dictionary d; + Vector2i v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::RECT2: { + Dictionary d; + Rect2 r = p_variant; + d["position"] = from_native(r.position); + d["size"] = from_native(r.size); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::RECT2I: { + Dictionary d; + Rect2i r = p_variant; + d["position"] = from_native(r.position); + d["size"] = from_native(r.size); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR3: { + Dictionary d; + Vector3 v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR3I: { + Dictionary d; + Vector3i v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::TRANSFORM2D: { + Dictionary d; + Transform2D t = p_variant; + d["x"] = from_native(t[0]); + d["y"] = from_native(t[1]); + d["origin"] = from_native(t[2]); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR4: { + Dictionary d; + Vector4 v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + values.push_back(v.w); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::VECTOR4I: { + Dictionary d; + Vector4i v = p_variant; + Array values; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + values.push_back(v.w); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PLANE: { + Dictionary d; + Plane p = p_variant; + d["normal"] = from_native(p.normal); + d["d"] = p.d; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::QUATERNION: { + Dictionary d; + Quaternion q = p_variant; + Array values; + values.push_back(q.x); + values.push_back(q.y); + values.push_back(q.z); + values.push_back(q.w); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::AABB: { + Dictionary d; + AABB aabb = p_variant; + d["position"] = from_native(aabb.position); + d["size"] = from_native(aabb.size); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::BASIS: { + Dictionary d; + Basis t = p_variant; + d["x"] = from_native(t.get_column(0)); + d["y"] = from_native(t.get_column(1)); + d["z"] = from_native(t.get_column(2)); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::TRANSFORM3D: { + Dictionary d; + Transform3D t = p_variant; + d["basis"] = from_native(t.basis); + d["origin"] = from_native(t.origin); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PROJECTION: { + Dictionary d; + Projection t = p_variant; + d["x"] = from_native(t[0]); + d["y"] = from_native(t[1]); + d["z"] = from_native(t[2]); + d["w"] = from_native(t[3]); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::COLOR: { + Dictionary d; + Color c = p_variant; + Array values; + values.push_back(c.r); + values.push_back(c.g); + values.push_back(c.b); + values.push_back(c.a); + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::STRING_NAME: { + Dictionary d; + d["name"] = String(p_variant); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::NODE_PATH: { + Dictionary d; + d["path"] = String(p_variant); + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::RID: { + Dictionary d; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::OBJECT: { + Object *obj = p_variant.get_validated_object(); + + if (p_allow_classes && obj) { + Dictionary d; + List<PropertyInfo> property_list; + obj->get_property_list(&property_list); + + d["type"] = obj->get_class(); + Dictionary p; + for (const PropertyInfo &P : property_list) { + if (P.usage & PROPERTY_USAGE_STORAGE) { + if (P.name == "script" && !p_allow_scripts) { + continue; + } + p[P.name] = from_native(obj->get(P.name), PASS_ARG); + } + } + d["properties"] = p; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } else { + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; + } + } break; + case Variant::CALLABLE: + case Variant::SIGNAL: { + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; + } break; + case Variant::DICTIONARY: { + Dictionary d = p_variant; + List<Variant> keys; + d.get_key_list(&keys); + bool all_strings = true; + for (const Variant &K : keys) { + if (K.get_type() != Variant::STRING) { + all_strings = false; + break; + } + } + + if (all_strings) { + Dictionary ret_dict; + for (const Variant &K : keys) { + ret_dict[K] = from_native(d[K], PASS_ARG); + } + return ret_dict; + } else { + Dictionary ret; + Array pairs; + for (const Variant &K : keys) { + Dictionary pair; + pair["key"] = from_native(K, PASS_ARG); + pair["value"] = from_native(d[K], PASS_ARG); + pairs.push_back(pair); + } + ret["pairs"] = pairs; + ret[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return ret; + } + } break; + case Variant::ARRAY: { + Array arr = p_variant; + Array ret; + for (int i = 0; i < arr.size(); i++) { + ret.push_back(from_native(arr[i], PASS_ARG)); + } + return ret; + } break; + case Variant::PACKED_BYTE_ARRAY: { + Dictionary d; + PackedByteArray arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_INT32_ARRAY: { + Dictionary d; + PackedInt32Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + + } break; + case Variant::PACKED_INT64_ARRAY: { + Dictionary d; + PackedInt64Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_FLOAT32_ARRAY: { + Dictionary d; + PackedFloat32Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_FLOAT64_ARRAY: { + Dictionary d; + PackedFloat64Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_STRING_ARRAY: { + Dictionary d; + PackedStringArray arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + values.push_back(arr[i]); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_VECTOR2_ARRAY: { + Dictionary d; + PackedVector2Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Vector2 v = arr[i]; + values.push_back(v.x); + values.push_back(v.y); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_VECTOR3_ARRAY: { + Dictionary d; + PackedVector3Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Vector3 v = arr[i]; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_COLOR_ARRAY: { + Dictionary d; + PackedColorArray arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Color v = arr[i]; + values.push_back(v.r); + values.push_back(v.g); + values.push_back(v.b); + values.push_back(v.a); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + case Variant::PACKED_VECTOR4_ARRAY: { + Dictionary d; + PackedVector4Array arr = p_variant; + Array values; + for (int i = 0; i < arr.size(); i++) { + Vector4 v = arr[i]; + values.push_back(v.x); + values.push_back(v.y); + values.push_back(v.z); + values.push_back(v.w); + } + d[VALUES] = values; + d[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return d; + } break; + default: { + ERR_PRINT(vformat("Unhandled conversion from native Variant type '%s' to JSON.", Variant::get_type_name(p_variant.get_type()))); + } break; + } + + Dictionary nil; + nil[GDTYPE] = Variant::get_type_name(p_variant.get_type()); + return nil; +} + +Variant JSON::to_native(const Variant &p_json, bool p_allow_classes, bool p_allow_scripts) { + switch (p_json.get_type()) { + case Variant::BOOL: { + return p_json; + } break; + case Variant::INT: { + return p_json; + } break; + case Variant::FLOAT: { + return p_json; + } break; + case Variant::STRING: { + return p_json; + } break; + case Variant::STRING_NAME: { + return p_json; + } break; + case Variant::CALLABLE: { + return p_json; + } break; + case Variant::DICTIONARY: { + Dictionary d = p_json; + if (d.has(GDTYPE)) { + // Specific Godot Variant types serialized to JSON. + String type = d[GDTYPE]; + if (type == Variant::get_type_name(Variant::VECTOR2)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 2, Variant()); + Vector2 v; + v.x = values[0]; + v.y = values[1]; + return v; + } else if (type == Variant::get_type_name(Variant::VECTOR2I)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 2, Variant()); + Vector2i v; + v.x = values[0]; + v.y = values[1]; + return v; + } else if (type == Variant::get_type_name(Variant::RECT2)) { + ERR_FAIL_COND_V(!d.has("position"), Variant()); + ERR_FAIL_COND_V(!d.has("size"), Variant()); + Rect2 r; + r.position = to_native(d["position"]); + r.size = to_native(d["size"]); + return r; + } else if (type == Variant::get_type_name(Variant::RECT2I)) { + ERR_FAIL_COND_V(!d.has("position"), Variant()); + ERR_FAIL_COND_V(!d.has("size"), Variant()); + Rect2i r; + r.position = to_native(d["position"]); + r.size = to_native(d["size"]); + return r; + } else if (type == Variant::get_type_name(Variant::VECTOR3)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 3, Variant()); + Vector3 v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + return v; + } else if (type == Variant::get_type_name(Variant::VECTOR3I)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 3, Variant()); + Vector3i v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + return v; + } else if (type == Variant::get_type_name(Variant::TRANSFORM2D)) { + ERR_FAIL_COND_V(!d.has("x"), Variant()); + ERR_FAIL_COND_V(!d.has("y"), Variant()); + ERR_FAIL_COND_V(!d.has("origin"), Variant()); + Transform2D t; + t[0] = to_native(d["x"]); + t[1] = to_native(d["y"]); + t[2] = to_native(d["origin"]); + return t; + } else if (type == Variant::get_type_name(Variant::VECTOR4)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Vector4 v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + v.w = values[3]; + return v; + } else if (type == Variant::get_type_name(Variant::VECTOR4I)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Vector4i v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + v.w = values[3]; + return v; + } else if (type == Variant::get_type_name(Variant::PLANE)) { + ERR_FAIL_COND_V(!d.has("normal"), Variant()); + ERR_FAIL_COND_V(!d.has("d"), Variant()); + Plane p; + p.normal = to_native(d["normal"]); + p.d = d["d"]; + return p; + } else if (type == Variant::get_type_name(Variant::QUATERNION)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Quaternion v; + v.x = values[0]; + v.y = values[1]; + v.z = values[2]; + v.w = values[3]; + return v; + } else if (type == Variant::get_type_name(Variant::AABB)) { + ERR_FAIL_COND_V(!d.has("position"), Variant()); + ERR_FAIL_COND_V(!d.has("size"), Variant()); + AABB r; + r.position = to_native(d["position"]); + r.size = to_native(d["size"]); + return r; + } else if (type == Variant::get_type_name(Variant::BASIS)) { + ERR_FAIL_COND_V(!d.has("x"), Variant()); + ERR_FAIL_COND_V(!d.has("y"), Variant()); + ERR_FAIL_COND_V(!d.has("z"), Variant()); + Basis b; + b.set_column(0, to_native(d["x"])); + b.set_column(1, to_native(d["y"])); + b.set_column(2, to_native(d["z"])); + return b; + } else if (type == Variant::get_type_name(Variant::TRANSFORM3D)) { + ERR_FAIL_COND_V(!d.has("basis"), Variant()); + ERR_FAIL_COND_V(!d.has("origin"), Variant()); + Transform3D t; + t.basis = to_native(d["basis"]); + t.origin = to_native(d["origin"]); + return t; + } else if (type == Variant::get_type_name(Variant::PROJECTION)) { + ERR_FAIL_COND_V(!d.has("x"), Variant()); + ERR_FAIL_COND_V(!d.has("y"), Variant()); + ERR_FAIL_COND_V(!d.has("z"), Variant()); + ERR_FAIL_COND_V(!d.has("w"), Variant()); + Projection p; + p[0] = to_native(d["x"]); + p[1] = to_native(d["y"]); + p[2] = to_native(d["z"]); + p[3] = to_native(d["w"]); + return p; + } else if (type == Variant::get_type_name(Variant::COLOR)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() != 4, Variant()); + Color c; + c.r = values[0]; + c.g = values[1]; + c.b = values[2]; + c.a = values[3]; + return c; + } else if (type == Variant::get_type_name(Variant::NODE_PATH)) { + ERR_FAIL_COND_V(!d.has("path"), Variant()); + NodePath np = d["path"]; + return np; + } else if (type == Variant::get_type_name(Variant::STRING_NAME)) { + ERR_FAIL_COND_V(!d.has("name"), Variant()); + StringName s = d["name"]; + return s; + } else if (type == Variant::get_type_name(Variant::OBJECT)) { + ERR_FAIL_COND_V(!d.has("type"), Variant()); + ERR_FAIL_COND_V(!d.has("properties"), Variant()); + + ERR_FAIL_COND_V(!p_allow_classes, Variant()); + + String obj_type = d["type"]; + bool is_script = obj_type == "Script" || ClassDB::is_parent_class(obj_type, "Script"); + ERR_FAIL_COND_V(!p_allow_scripts && is_script, Variant()); + Object *obj = ClassDB::instantiate(obj_type); + ERR_FAIL_NULL_V(obj, Variant()); + + Dictionary p = d["properties"]; + + List<Variant> keys; + p.get_key_list(&keys); + + for (const Variant &K : keys) { + String property = K; + Variant value = to_native(p[K], PASS_ARG); + obj->set(property, value); + } + + Variant v(obj); + + return v; + } else if (type == Variant::get_type_name(Variant::DICTIONARY)) { + ERR_FAIL_COND_V(!d.has("pairs"), Variant()); + Array pairs = d["pairs"]; + Dictionary r; + for (int i = 0; i < pairs.size(); i++) { + Dictionary p = pairs[i]; + ERR_CONTINUE(!p.has("key")); + ERR_CONTINUE(!p.has("value")); + r[to_native(p["key"], PASS_ARG)] = to_native(p["value"]); + } + return r; + } else if (type == Variant::get_type_name(Variant::ARRAY)) { + ERR_PRINT(vformat("Unexpected Array with '%s' key. Arrays are supported natively.", GDTYPE)); + } else if (type == Variant::get_type_name(Variant::PACKED_BYTE_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedByteArray pbarr; + pbarr.resize(values.size()); + for (int i = 0; i < pbarr.size(); i++) { + pbarr.write[i] = values[i]; + } + return pbarr; + } else if (type == Variant::get_type_name(Variant::PACKED_INT32_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedInt32Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_INT64_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedInt64Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT32_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedFloat32Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_FLOAT64_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedFloat64Array arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_STRING_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + PackedStringArray arr; + arr.resize(values.size()); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = values[i]; + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR2_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 2 != 0, Variant()); + PackedVector2Array arr; + arr.resize(values.size() / 2); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Vector2(values[i * 2 + 0], values[i * 2 + 1]); + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR3_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 3 != 0, Variant()); + PackedVector3Array arr; + arr.resize(values.size() / 3); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Vector3(values[i * 3 + 0], values[i * 3 + 1], values[i * 3 + 2]); + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_COLOR_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 4 != 0, Variant()); + PackedColorArray arr; + arr.resize(values.size() / 4); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Color(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]); + } + return arr; + } else if (type == Variant::get_type_name(Variant::PACKED_VECTOR4_ARRAY)) { + ERR_FAIL_COND_V(!d.has(VALUES), Variant()); + Array values = d[VALUES]; + ERR_FAIL_COND_V(values.size() % 4 != 0, Variant()); + PackedVector4Array arr; + arr.resize(values.size() / 4); + for (int i = 0; i < arr.size(); i++) { + arr.write[i] = Vector4(values[i * 4 + 0], values[i * 4 + 1], values[i * 4 + 2], values[i * 4 + 3]); + } + return arr; + } else { + return Variant(); + } + } else { + // Regular dictionary with string keys. + List<Variant> keys; + d.get_key_list(&keys); + Dictionary r; + for (const Variant &K : keys) { + r[K] = to_native(d[K], PASS_ARG); + } + return r; + } + } break; + case Variant::ARRAY: { + Array arr = p_json; + Array ret; + ret.resize(arr.size()); + for (int i = 0; i < arr.size(); i++) { + ret[i] = to_native(arr[i], PASS_ARG); + } + return ret; + } break; + default: { + ERR_PRINT(vformat("Unhandled conversion from JSON type '%s' to native Variant type.", Variant::get_type_name(p_json.get_type()))); + return Variant(); + } + } + + return Variant(); +} + +#undef GDTYPE +#undef VALUES +#undef PASS_ARG //////////// diff --git a/core/io/json.h b/core/io/json.h index 801fa29b4b..67b5e09afa 100644 --- a/core/io/json.h +++ b/core/io/json.h @@ -94,6 +94,9 @@ public: void set_data(const Variant &p_data); inline int get_error_line() const { return err_line; } inline String get_error_message() const { return err_str; } + + static Variant from_native(const Variant &p_variant, bool p_allow_classes = false, bool p_allow_scripts = false); + static Variant to_native(const Variant &p_json, bool p_allow_classes = false, bool p_allow_scripts = false); }; class ResourceFormatLoaderJSON : public ResourceFormatLoader { diff --git a/core/io/packed_data_container.h b/core/io/packed_data_container.h index cc9996101e..f4ffa09022 100644 --- a/core/io/packed_data_container.h +++ b/core/io/packed_data_container.h @@ -36,7 +36,7 @@ class PackedDataContainer : public Resource { GDCLASS(PackedDataContainer, Resource); - enum { + enum : uint32_t { TYPE_DICT = 0xFFFFFFFF, TYPE_ARRAY = 0xFFFFFFFE, }; diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 598c99c188..ff12dc5851 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -60,32 +60,32 @@ void Resource::set_path(const String &p_path, bool p_take_over) { p_take_over = false; // Can't take over an empty path } - ResourceCache::lock.lock(); + { + MutexLock lock(ResourceCache::lock); - if (!path_cache.is_empty()) { - ResourceCache::resources.erase(path_cache); - } + if (!path_cache.is_empty()) { + ResourceCache::resources.erase(path_cache); + } - path_cache = ""; + path_cache = ""; - Ref<Resource> existing = ResourceCache::get_ref(p_path); + Ref<Resource> existing = ResourceCache::get_ref(p_path); - if (existing.is_valid()) { - if (p_take_over) { - existing->path_cache = String(); - ResourceCache::resources.erase(p_path); - } else { - ResourceCache::lock.unlock(); - ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + if (existing.is_valid()) { + if (p_take_over) { + existing->path_cache = String(); + ResourceCache::resources.erase(p_path); + } else { + ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion)."); + } } - } - path_cache = p_path; + path_cache = p_path; - if (!path_cache.is_empty()) { - ResourceCache::resources[path_cache] = this; + if (!path_cache.is_empty()) { + ResourceCache::resources[path_cache] = this; + } } - ResourceCache::lock.unlock(); _resource_path_changed(); } @@ -486,15 +486,13 @@ void Resource::set_as_translation_remapped(bool p_remapped) { return; } - ResourceCache::lock.lock(); + MutexLock lock(ResourceCache::lock); if (p_remapped) { ResourceLoader::remapped_list.add(&remapped_list); } else { ResourceLoader::remapped_list.remove(&remapped_list); } - - ResourceCache::lock.unlock(); } #ifdef TOOLS_ENABLED @@ -564,14 +562,13 @@ Resource::~Resource() { return; } - ResourceCache::lock.lock(); + MutexLock lock(ResourceCache::lock); // Only unregister from the cache if this is the actual resource listed there. // (Other resources can have the same value in `path_cache` if loaded with `CACHE_IGNORE`.) HashMap<String, Resource *>::Iterator E = ResourceCache::resources.find(path_cache); if (likely(E && E->value == this)) { ResourceCache::resources.remove(E); } - ResourceCache::lock.unlock(); } HashMap<String, Resource *> ResourceCache::resources; @@ -600,18 +597,20 @@ void ResourceCache::clear() { } bool ResourceCache::has(const String &p_path) { - lock.lock(); + Resource **res = nullptr; - Resource **res = resources.getptr(p_path); + { + MutexLock mutex_lock(lock); - if (res && (*res)->get_reference_count() == 0) { - // This resource is in the process of being deleted, ignore its existence. - (*res)->path_cache = String(); - resources.erase(p_path); - res = nullptr; - } + res = resources.getptr(p_path); - lock.unlock(); + if (res && (*res)->get_reference_count() == 0) { + // This resource is in the process of being deleted, ignore its existence. + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; + } + } if (!res) { return false; @@ -622,28 +621,27 @@ bool ResourceCache::has(const String &p_path) { Ref<Resource> ResourceCache::get_ref(const String &p_path) { Ref<Resource> ref; - lock.lock(); - - Resource **res = resources.getptr(p_path); + { + MutexLock mutex_lock(lock); + Resource **res = resources.getptr(p_path); - if (res) { - ref = Ref<Resource>(*res); - } + if (res) { + ref = Ref<Resource>(*res); + } - if (res && !ref.is_valid()) { - // This resource is in the process of being deleted, ignore its existence - (*res)->path_cache = String(); - resources.erase(p_path); - res = nullptr; + if (res && !ref.is_valid()) { + // This resource is in the process of being deleted, ignore its existence + (*res)->path_cache = String(); + resources.erase(p_path); + res = nullptr; + } } - lock.unlock(); - return ref; } void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) { - lock.lock(); + MutexLock mutex_lock(lock); LocalVector<String> to_remove; @@ -663,14 +661,9 @@ void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) { for (const String &E : to_remove) { resources.erase(E); } - - lock.unlock(); } int ResourceCache::get_cached_resource_count() { - lock.lock(); - int rc = resources.size(); - lock.unlock(); - - return rc; + MutexLock mutex_lock(lock); + return resources.size(); } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 3e809ae762..7cf101b0de 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -227,28 +227,27 @@ void ResourceFormatLoader::_bind_methods() { // This should be robust enough to be called redundantly without issues. void ResourceLoader::LoadToken::clear() { - thread_load_mutex.lock(); - WorkerThreadPool::TaskID task_to_await = 0; - // 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; + { + MutexLock thread_load_lock(thread_load_mutex); + // User-facing tokens shouldn't be deleted until completely claimed. + DEV_ASSERT(user_rc == 0 && user_path.is_empty()); + + if (!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; + } + // 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(); } - // 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(); } - thread_load_mutex.unlock(); - // If task is unused, await it here, locally, now the token data is consistent. if (task_to_await) { PREPARE_FOR_WTP_WAIT @@ -265,7 +264,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin const String &original_path = p_original_path.is_empty() ? p_path : p_original_path; load_nesting++; if (load_paths_stack.size()) { - thread_load_mutex.lock(); + MutexLock thread_load_lock(thread_load_mutex); const String &parent_task_path = load_paths_stack.get(load_paths_stack.size() - 1); HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(parent_task_path); // Avoid double-tracking, for progress reporting, resources that boil down to a remapped path containing the real payload (e.g., imported resources). @@ -273,7 +272,6 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin if (E && !is_remapped_load) { E->value.sub_tasks.insert(p_original_path); } - thread_load_mutex.unlock(); } load_paths_stack.push_back(original_path); @@ -318,13 +316,13 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin void ResourceLoader::_run_load_task(void *p_userdata) { ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata; - thread_load_mutex.lock(); - if (cleaning_tasks) { - load_task.status = THREAD_LOAD_FAILED; - thread_load_mutex.unlock(); - return; + { + MutexLock thread_load_lock(thread_load_mutex); + if (cleaning_tasks) { + load_task.status = THREAD_LOAD_FAILED; + return; + } } - thread_load_mutex.unlock(); // Thread-safe either if it's the current thread or a brand new one. CallQueue *own_mq_override = nullptr; @@ -1170,17 +1168,17 @@ String ResourceLoader::path_remap(const String &p_path) { } void ResourceLoader::reload_translation_remaps() { - ResourceCache::lock.lock(); - List<Resource *> to_reload; - SelfList<Resource> *E = remapped_list.first(); - while (E) { - to_reload.push_back(E->self()); - E = E->next(); - } + { + MutexLock lock(ResourceCache::lock); + SelfList<Resource> *E = remapped_list.first(); - ResourceCache::lock.unlock(); + while (E) { + to_reload.push_back(E->self()); + E = E->next(); + } + } //now just make sure to not delete any of these resources while changing locale.. while (to_reload.front()) { diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index fd53ed28fd..1afc5f4bbb 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -105,6 +105,9 @@ public: static _ALWAYS_INLINE_ double fmod(double p_x, double p_y) { return ::fmod(p_x, p_y); } static _ALWAYS_INLINE_ float fmod(float p_x, float p_y) { return ::fmodf(p_x, p_y); } + static _ALWAYS_INLINE_ double modf(double p_x, double *r_y) { return ::modf(p_x, r_y); } + static _ALWAYS_INLINE_ float modf(float p_x, float *r_y) { return ::modff(p_x, r_y); } + static _ALWAYS_INLINE_ double floor(double p_x) { return ::floor(p_x); } static _ALWAYS_INLINE_ float floor(float p_x) { return ::floorf(p_x); } diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 5c793a676f..b81130f43e 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -271,6 +271,22 @@ void ClassDB::get_extensions_class_list(List<StringName> *p_classes) { p_classes->sort_custom<StringName::AlphCompare>(); } + +void ClassDB::get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes) { + OBJTYPE_RLOCK; + + for (const KeyValue<StringName, ClassInfo> &E : classes) { + if (E.value.api != API_EXTENSION && E.value.api != API_EDITOR_EXTENSION) { + continue; + } + if (!E.value.gdextension || E.value.gdextension->library != p_extension.ptr()) { + continue; + } + p_classes->push_back(E.key); + } + + p_classes->sort_custom<StringName::AlphCompare>(); +} #endif void ClassDB::get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes) { @@ -1632,14 +1648,16 @@ bool ClassDB::get_property(Object *p_object, const StringName &p_property, Varia Variant index = psg->index; const Variant *arg[1] = { &index }; Callable::CallError ce; - r_value = p_object->callp(psg->getter, arg, 1, ce); + const Variant value = p_object->callp(psg->getter, arg, 1, ce); + r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant(); } else { Callable::CallError ce; if (psg->_getptr) { r_value = psg->_getptr->call(p_object, nullptr, 0, ce); } else { - r_value = p_object->callp(psg->getter, nullptr, 0, ce); + const Variant value = p_object->callp(psg->getter, nullptr, 0, ce); + r_value = (ce.error == Callable::CallError::CALL_OK) ? value : Variant(); } } return true; diff --git a/core/object/class_db.h b/core/object/class_db.h index d6a95b58e2..620092a6c4 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -285,6 +285,7 @@ public: static void get_class_list(List<StringName> *p_classes); #ifdef TOOLS_ENABLED static void get_extensions_class_list(List<StringName> *p_classes); + static void get_extension_class_list(const Ref<GDExtension> &p_extension, List<StringName> *p_classes); static ObjectGDExtension *get_placeholder_extension(const StringName &p_class); #endif static void get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes); diff --git a/core/object/object.cpp b/core/object/object.cpp index a2330ecd04..8b3ab40271 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -746,7 +746,7 @@ Variant Object::callv(const StringName &p_method, const Array &p_args) { } Callable::CallError ce; - Variant ret = callp(p_method, argptrs, p_args.size(), ce); + const Variant ret = callp(p_method, argptrs, p_args.size(), ce); if (ce.error != Callable::CallError::CALL_OK) { ERR_FAIL_V_MSG(Variant(), "Error calling method from 'callv': " + Variant::get_call_error_text(this, p_method, argptrs, p_args.size(), ce) + "."); } @@ -787,7 +787,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_ if (script_instance) { ret = script_instance->callp(p_method, p_args, p_argcount, r_error); - //force jumptable + // Force jump table. switch (r_error.error) { case Callable::CallError::CALL_OK: return ret; @@ -1023,6 +1023,14 @@ void Object::remove_meta(const StringName &p_name) { set_meta(p_name, Variant()); } +void Object::merge_meta_from(const Object *p_src) { + List<StringName> meta_keys; + p_src->get_meta_list(&meta_keys); + for (const StringName &key : meta_keys) { + set_meta(key, p_src->get_meta(key)); + } +} + TypedArray<Dictionary> Object::_get_property_list_bind() const { List<PropertyInfo> lpi; get_property_list(&lpi); @@ -1904,7 +1912,7 @@ void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtens void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks) { void *binding = nullptr; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].token == p_token) { binding = _instance_bindings[i].binding; @@ -1935,14 +1943,12 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi _instance_binding_count++; } - _instance_binding_mutex.unlock(); - return binding; } bool Object::has_instance_binding(void *p_token) { bool found = false; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].token == p_token) { found = true; @@ -1950,14 +1956,12 @@ bool Object::has_instance_binding(void *p_token) { } } - _instance_binding_mutex.unlock(); - return found; } void Object::free_instance_binding(void *p_token) { bool found = false; - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (!found && _instance_bindings[i].token == p_token) { if (_instance_bindings[i].free_callback) { @@ -1976,7 +1980,6 @@ void Object::free_instance_binding(void *p_token) { if (found) { _instance_binding_count--; } - _instance_binding_mutex.unlock(); } #ifdef TOOLS_ENABLED @@ -2294,7 +2297,7 @@ void ObjectDB::cleanup() { // Ensure calling the native classes because if a leaked instance has a script // that overrides any of those methods, it'd not be OK to call them at this point, // now the scripting languages have already been terminated. - MethodBind *node_get_name = ClassDB::get_method("Node", "get_name"); + MethodBind *node_get_path = ClassDB::get_method("Node", "get_path"); MethodBind *resource_get_path = ClassDB::get_method("Resource", "get_path"); Callable::CallError call_error; @@ -2304,7 +2307,7 @@ void ObjectDB::cleanup() { String extra_info; if (obj->is_class("Node")) { - extra_info = " - Node name: " + String(node_get_name->call(obj, nullptr, 0, call_error)); + extra_info = " - Node path: " + String(node_get_path->call(obj, nullptr, 0, call_error)); } if (obj->is_class("Resource")) { extra_info = " - Resource path: " + String(resource_get_path->call(obj, nullptr, 0, call_error)); diff --git a/core/object/object.h b/core/object/object.h index ba6b309542..bc3f663baf 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -667,7 +667,7 @@ protected: _FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) { bool can_die = true; if (_instance_bindings) { - _instance_binding_mutex.lock(); + MutexLock instance_binding_lock(_instance_binding_mutex); for (uint32_t i = 0; i < _instance_binding_count; i++) { if (_instance_bindings[i].reference_callback) { if (!_instance_bindings[i].reference_callback(_instance_bindings[i].token, _instance_bindings[i].binding, p_reference)) { @@ -675,7 +675,6 @@ protected: } } } - _instance_binding_mutex.unlock(); } return can_die; } @@ -868,7 +867,8 @@ public: argptrs[i] = &args[i]; } Callable::CallError cerr; - return callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr); + const Variant ret = callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr); + return (cerr.error == Callable::CallError::CALL_OK) ? ret : Variant(); } void notification(int p_notification, bool p_reversed = false); @@ -895,6 +895,7 @@ public: MTVIRTUAL void remove_meta(const StringName &p_name); MTVIRTUAL Variant get_meta(const StringName &p_name, const Variant &p_default = Variant()) const; MTVIRTUAL void get_meta_list(List<StringName> *p_list) const; + MTVIRTUAL void merge_meta_from(const Object *p_src); #ifdef TOOLS_ENABLED void set_edited(bool p_edited); diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index 25ad3bf964..fe7bbd474c 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -127,9 +127,8 @@ void WorkerThreadPool::_process_task(Task *p_task) { if (finished_users == max_users) { // Get rid of the group, because nobody else is using it. - task_mutex.lock(); + MutexLock task_lock(task_mutex); group_allocator.free(p_task->group); - task_mutex.unlock(); } // For groups, tasks get rid of themselves. @@ -349,17 +348,13 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_task(const Callable &p_action, bo } bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Task *const *taskp = tasks.getptr(p_task_id); if (!taskp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(false, "Invalid Task ID"); // Invalid task } - bool completed = (*taskp)->completed; - task_mutex.unlock(); - - return completed; + return (*taskp)->completed; } Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { @@ -522,10 +517,9 @@ void WorkerThreadPool::yield() { } void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { - task_mutex.lock(); + MutexLock task_lock(task_mutex); Task **taskp = tasks.getptr(p_task_id); if (!taskp) { - task_mutex.unlock(); ERR_FAIL_MSG("Invalid Task ID."); } Task *task = *taskp; @@ -534,7 +528,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { // This avoids a race condition where a task is created and yield-over called before it's processed. task->pending_notify_yield_over = true; } - task_mutex.unlock(); return; } @@ -542,8 +535,6 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { td.yield_is_over = true; td.signaled = true; td.cond_var.notify_one(); - - task_mutex.unlock(); } WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) { @@ -601,26 +592,20 @@ WorkerThreadPool::GroupID WorkerThreadPool::add_group_task(const Callable &p_act } uint32_t WorkerThreadPool::get_group_processed_element_count(GroupID p_group) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Group *const *groupp = groups.getptr(p_group); if (!groupp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(0, "Invalid Group ID"); } - uint32_t elements = (*groupp)->completed_index.get(); - task_mutex.unlock(); - return elements; + return (*groupp)->completed_index.get(); } bool WorkerThreadPool::is_group_task_completed(GroupID p_group) const { - task_mutex.lock(); + MutexLock task_lock(task_mutex); const Group *const *groupp = groups.getptr(p_group); if (!groupp) { - task_mutex.unlock(); ERR_FAIL_V_MSG(false, "Invalid Group ID"); } - bool completed = (*groupp)->completed.is_set(); - task_mutex.unlock(); - return completed; + return (*groupp)->completed.is_set(); } void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { @@ -644,15 +629,13 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { if (finished_users == max_users) { // All tasks using this group are gone (finished before the group), so clear the group too. - task_mutex.lock(); + MutexLock task_lock(task_mutex); group_allocator.free(group); - task_mutex.unlock(); } } - task_mutex.lock(); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. + MutexLock task_lock(task_mutex); // This mutex is needed when Physics 2D and/or 3D is selected to run on a separate thread. groups.erase(p_group); - task_mutex.unlock(); #endif } @@ -704,6 +687,8 @@ void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count - 1); + print_verbose(vformat("WorkerThreadPool: %d threads, %d max low-priority.", p_thread_count, max_low_priority_threads)); + threads.resize(p_thread_count); for (uint32_t i = 0; i < threads.size(); i++) { diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 5d59d65f92..28077fc8c5 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -191,11 +191,10 @@ StringName::StringName(const StringName &p_name) { } void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) { - mutex.lock(); + MutexLock lock(mutex); if (*ptr == StringName()) { *ptr = StringName(p_name, true); } - mutex.unlock(); } StringName::StringName(const char *p_name, bool p_static) { diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index 667aae879c..9dff5c1e91 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -112,7 +112,7 @@ Error Callable::rpcp(int p_id, const Variant **p_arguments, int p_argcount, Call argptrs[i + 2] = p_arguments[i]; } - CallError tmp; + CallError tmp; // TODO: Check `tmp`? Error err = (Error)obj->callp(SNAME("rpc_id"), argptrs, argcount, tmp).operator int64_t(); r_call_error.error = Callable::CallError::CALL_OK; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 24b30112bd..186643b024 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -2113,7 +2113,7 @@ Variant::operator ::RID() const { } #endif Callable::CallError ce; - Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce); + const Variant ret = _get_obj().obj->callp(CoreStringName(get_rid), nullptr, 0, ce); if (ce.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::RID) { return ret; } diff --git a/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/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml index eecbb05540..93680de21e 100644 --- a/doc/classes/AudioStreamPlayer.xml +++ b/doc/classes/AudioStreamPlayer.xml @@ -77,7 +77,7 @@ <member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental=""> The playback type of the stream player. If set other than to the default value, it will force that playback type. </member> - <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false"> + <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false"> If [code]true[/code], this node is playing sounds. Setting this property has the same effect as [method play] and [method stop]. </member> <member name="stream" type="AudioStream" setter="set_stream" getter="get_stream"> diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml index a3206ba1d6..71d2e1f0db 100644 --- a/doc/classes/AudioStreamPlayer2D.xml +++ b/doc/classes/AudioStreamPlayer2D.xml @@ -81,7 +81,7 @@ <member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental=""> The playback type of the stream player. If set other than to the default value, it will force that playback type. </member> - <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false"> + <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false"> If [code]true[/code], audio is playing or is queued to be played (see [method play]). </member> <member name="stream" type="AudioStream" setter="set_stream" getter="get_stream"> diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml index bf02caffb4..a14c53bbfb 100644 --- a/doc/classes/AudioStreamPlayer3D.xml +++ b/doc/classes/AudioStreamPlayer3D.xml @@ -102,7 +102,7 @@ <member name="playback_type" type="int" setter="set_playback_type" getter="get_playback_type" enum="AudioServer.PlaybackType" default="0" experimental=""> The playback type of the stream player. If set other than to the default value, it will force that playback type. </member> - <member name="playing" type="bool" setter="_set_playing" getter="is_playing" default="false"> + <member name="playing" type="bool" setter="set_playing" getter="is_playing" default="false"> If [code]true[/code], audio is playing or is queued to be played (see [method play]). </member> <member name="stream" type="AudioStream" setter="set_stream" getter="get_stream"> diff --git a/doc/classes/AudioStreamWAV.xml b/doc/classes/AudioStreamWAV.xml index 8a28514ed6..8d882deaee 100644 --- a/doc/classes/AudioStreamWAV.xml +++ b/doc/classes/AudioStreamWAV.xml @@ -15,7 +15,7 @@ <return type="int" enum="Error" /> <param index="0" name="path" type="String" /> <description> - Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or QOA formats can't be saved. + Saves the AudioStreamWAV as a WAV file to [param path]. Samples with IMA ADPCM or Quite OK Audio formats can't be saved. [b]Note:[/b] A [code].wav[/code] extension is automatically appended to [param path] if it is missing. </description> </method> @@ -23,19 +23,20 @@ <members> <member name="data" type="PackedByteArray" setter="set_data" getter="get_data" default="PackedByteArray()"> Contains the audio data in bytes. - [b]Note:[/b] This property expects signed PCM8 data. To convert unsigned PCM8 to signed PCM8, subtract 128 from each byte. + [b]Note:[/b] If [member format] is set to [constant FORMAT_8_BITS], this property expects signed 8-bit PCM data. To convert from unsigned 8-bit PCM, subtract 128 from each byte. + [b]Note:[/b] If [member format] is set to [constant FORMAT_QOA], this property expects data from a full QOA file. </member> <member name="format" type="int" setter="set_format" getter="get_format" enum="AudioStreamWAV.Format" default="0"> Audio format. See [enum Format] constants for values. </member> <member name="loop_begin" type="int" setter="set_loop_begin" getter="get_loop_begin" default="0"> - The loop start point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present. + The loop start point (in number of samples, relative to the beginning of the stream). </member> <member name="loop_end" type="int" setter="set_loop_end" getter="get_loop_end" default="0"> - The loop end point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present. + The loop end point (in number of samples, relative to the beginning of the stream). </member> <member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="AudioStreamWAV.LoopMode" default="0"> - The loop mode. This information will be imported automatically from the WAV file if present. See [enum LoopMode] constants for values. + The loop mode. See [enum LoopMode] constants for values. </member> <member name="mix_rate" type="int" setter="set_mix_rate" getter="get_mix_rate" default="44100"> The sample rate for mixing this audio. Higher values require more storage space, but result in better quality. @@ -48,16 +49,16 @@ </members> <constants> <constant name="FORMAT_8_BITS" value="0" enum="Format"> - 8-bit audio codec. + 8-bit PCM audio codec. </constant> <constant name="FORMAT_16_BITS" value="1" enum="Format"> - 16-bit audio codec. + 16-bit PCM audio codec. </constant> <constant name="FORMAT_IMA_ADPCM" value="2" enum="Format"> - Audio is compressed using IMA ADPCM. + Audio is lossily compressed as IMA ADPCM. </constant> <constant name="FORMAT_QOA" value="3" enum="Format"> - Audio is compressed as QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]). + Audio is lossily compressed as [url=https://qoaformat.org/]Quite OK Audio[/url]. </constant> <constant name="LOOP_DISABLED" value="0" enum="LoopMode"> Audio does not loop. diff --git a/doc/classes/EditorExportPlatform.xml b/doc/classes/EditorExportPlatform.xml index 0e5de79b25..d4084e84a0 100644 --- a/doc/classes/EditorExportPlatform.xml +++ b/doc/classes/EditorExportPlatform.xml @@ -11,11 +11,217 @@ <link title="Console support in Godot">$DOCS_URL/tutorials/platform/consoles.html</link> </tutorials> <methods> + <method name="add_message"> + <return type="void" /> + <param index="0" name="type" type="int" enum="EditorExportPlatform.ExportMessageType" /> + <param index="1" name="category" type="String" /> + <param index="2" name="message" type="String" /> + <description> + Adds a message to the export log that will be displayed when exporting ends. + </description> + </method> + <method name="clear_messages"> + <return type="void" /> + <description> + Clears the export log. + </description> + </method> + <method name="create_preset"> + <return type="EditorExportPreset" /> + <description> + Create a new preset for this platform. + </description> + </method> + <method name="export_pack"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" /> + <description> + Creates a PCK archive at [param path] for the specified [param preset]. + </description> + </method> + <method name="export_project"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" /> + <description> + Creates a full project at [param path] for the specified [param preset]. + </description> + </method> + <method name="export_project_files"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="save_cb" type="Callable" /> + <param index="3" name="shared_cb" type="Callable" default="Callable()" /> + <description> + Exports project files for the specified preset. This method can be used to implement custom export format, other than PCK and ZIP. One of the callbacks is called for each exported file. + [param save_cb] is called for all exported files and have the following arguments: [code]file_path: String[/code], [code]file_data: PackedByteArray[/code], [code]file_index: int[/code], [code]file_count: int[/code], [code]encryption_include_filters: PackedStringArray[/code], [code]encryption_exclude_filters: PackedStringArray[/code], [code]encryption_key: PackedByteArray[/code]. + [param shared_cb] is called for exported native shared/static libraries and have the following arguments: [code]file_path: String[/code], [code]tags: PackedStringArray[/code], [code]target_folder: String[/code]. + [b]Note:[/b] [code]file_index[/code] and [code]file_count[/code] are intended for progress tracking only and aren't necesserely unique and precise. + </description> + </method> + <method name="export_zip"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" default="0" /> + <description> + Create a ZIP archive at [param path] for the specified [param preset]. + </description> + </method> + <method name="find_export_template" qualifiers="const"> + <return type="Dictionary" /> + <param index="0" name="template_file_name" type="String" /> + <description> + Locates export template for the platform, and returns [Dictionary] with the following keys: [code]path: String[/code] and [code]error: String[/code]. This method is provided for convenience and custom export platforms aren't required to use it or keep export templates stored in the same way official templates are. + </description> + </method> + <method name="gen_export_flags"> + <return type="PackedStringArray" /> + <param index="0" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + Generates array of command line arguments for the default export templates for the debug flags and editor settings. + </description> + </method> + <method name="get_current_presets" qualifiers="const"> + <return type="Array" /> + <description> + Returns array of [EditorExportPreset]s for this platform. + </description> + </method> + <method name="get_forced_export_files" qualifiers="static"> + <return type="PackedStringArray" /> + <description> + Returns array of core file names that always should be exported regardless of preset config. + </description> + </method> + <method name="get_message_category" qualifiers="const"> + <return type="String" /> + <param index="0" name="index" type="int" /> + <description> + Returns message category, for the message with [param index]. + </description> + </method> + <method name="get_message_count" qualifiers="const"> + <return type="int" /> + <description> + Returns number of messages in the export log. + </description> + </method> + <method name="get_message_text" qualifiers="const"> + <return type="String" /> + <param index="0" name="index" type="int" /> + <description> + Returns message text, for the message with [param index]. + </description> + </method> + <method name="get_message_type" qualifiers="const"> + <return type="int" enum="EditorExportPlatform.ExportMessageType" /> + <param index="0" name="index" type="int" /> + <description> + Returns message type, for the message with [param index]. + </description> + </method> <method name="get_os_name" qualifiers="const"> <return type="String" /> <description> Returns the name of the export operating system handled by this [EditorExportPlatform] class, as a friendly string. Possible return values are [code]Windows[/code], [code]Linux[/code], [code]macOS[/code], [code]Android[/code], [code]iOS[/code], and [code]Web[/code]. </description> </method> + <method name="get_worst_message_type" qualifiers="const"> + <return type="int" enum="EditorExportPlatform.ExportMessageType" /> + <description> + Returns most severe message type currently present in the export log. + </description> + </method> + <method name="save_pack"> + <return type="Dictionary" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="embed" type="bool" default="false" /> + <description> + Saves PCK archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]). + If [param embed] is [code]true[/code], PCK content is appended to the end of [param path] file and return [Dictionary] additionally include following keys: [code]embedded_start: int[/code] (embedded PCK offset) and [code]embedded_size: int[/code] (embedded PCK size). + </description> + </method> + <method name="save_zip"> + <return type="Dictionary" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <description> + Saves ZIP archive and returns [Dictionary] with the following keys: [code]result: Error[/code], [code]so_files: Array[/code] (array of the shared/static objects which contains dictionaries with the following keys: [code]path: String[/code], [code]tags: PackedStringArray[/code], and [code]target_folder: String[/code]). + </description> + </method> + <method name="ssh_push_to_remote" qualifiers="const"> + <return type="int" enum="Error" /> + <param index="0" name="host" type="String" /> + <param index="1" name="port" type="String" /> + <param index="2" name="scp_args" type="PackedStringArray" /> + <param index="3" name="src_file" type="String" /> + <param index="4" name="dst_file" type="String" /> + <description> + Uploads specified file over SCP protocol to the remote host. + </description> + </method> + <method name="ssh_run_on_remote" qualifiers="const"> + <return type="int" enum="Error" /> + <param index="0" name="host" type="String" /> + <param index="1" name="port" type="String" /> + <param index="2" name="ssh_arg" type="PackedStringArray" /> + <param index="3" name="cmd_args" type="String" /> + <param index="4" name="output" type="Array" default="[]" /> + <param index="5" name="port_fwd" type="int" default="-1" /> + <description> + Executes specified command on the remote host via SSH protocol and returns command output in the [param output]. + </description> + </method> + <method name="ssh_run_on_remote_no_wait" qualifiers="const"> + <return type="int" /> + <param index="0" name="host" type="String" /> + <param index="1" name="port" type="String" /> + <param index="2" name="ssh_args" type="PackedStringArray" /> + <param index="3" name="cmd_args" type="String" /> + <param index="4" name="port_fwd" type="int" default="-1" /> + <description> + Executes specified command on the remote host via SSH protocol and returns process ID (on the remote host) without waiting for command to finish. + </description> + </method> </methods> + <constants> + <constant name="EXPORT_MESSAGE_NONE" value="0" enum="ExportMessageType"> + Invalid message type used as the default value when no type is specified. + </constant> + <constant name="EXPORT_MESSAGE_INFO" value="1" enum="ExportMessageType"> + Message type for informational messages that have no effect on the export. + </constant> + <constant name="EXPORT_MESSAGE_WARNING" value="2" enum="ExportMessageType"> + Message type for warning messages that should be addressed but still allow to complete the export. + </constant> + <constant name="EXPORT_MESSAGE_ERROR" value="3" enum="ExportMessageType"> + Message type for error messages that must be addressed and fail the export. + </constant> + <constant name="DEBUG_FLAG_DUMB_CLIENT" value="1" enum="DebugFlags" is_bitfield="true"> + Flag is set if remotely debugged project is expected to use remote file system. If set, [method gen_export_flags] will add [code]--remove-fs[/code] and [code]--remote-fs-password[/code] (if password is set in the editor settings) command line arguments to the list. + </constant> + <constant name="DEBUG_FLAG_REMOTE_DEBUG" value="2" enum="DebugFlags" is_bitfield="true"> + Flag is set if remote debug is enabled. If set, [method gen_export_flags] will add [code]--remote-debug[/code] and [code]--breakpoints[/code] (if breakpoints are selected in the script editor or added by the plugin) command line arguments to the list. + </constant> + <constant name="DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST" value="4" enum="DebugFlags" is_bitfield="true"> + Flag is set if remotely debugged project is running on the localhost. If set, [method gen_export_flags] will use [code]localhost[/code] instead of [member EditorSettings.network/debug/remote_host] as remote debugger host. + </constant> + <constant name="DEBUG_FLAG_VIEW_COLLISIONS" value="8" enum="DebugFlags" is_bitfield="true"> + Flag is set if "Visible Collision Shapes" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-collisions[/code] command line arguments to the list. + </constant> + <constant name="DEBUG_FLAG_VIEW_NAVIGATION" value="16" enum="DebugFlags" is_bitfield="true"> + Flag is set if Visible Navigation" remote debug option is enabled. If set, [method gen_export_flags] will add [code]--debug-navigation[/code] command line arguments to the list. + </constant> + </constants> </class> diff --git a/doc/classes/EditorExportPlatformExtension.xml b/doc/classes/EditorExportPlatformExtension.xml new file mode 100644 index 0000000000..ef589e2f58 --- /dev/null +++ b/doc/classes/EditorExportPlatformExtension.xml @@ -0,0 +1,282 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPlatformExtension" inherits="EditorExportPlatform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Base class for custom [EditorExportPlatform] implementations (plugins). + </brief_description> + <description> + External [EditorExportPlatform] implementations should inherit from this class. + To use [EditorExportPlatform], register it using the [method EditorPlugin.add_export_platform] method first. + </description> + <tutorials> + </tutorials> + <methods> + <method name="_can_export" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <description> + [b]Optional.[/b] + Returns [code]true[/code], if specified [param preset] is valid and can be exported. Use [method set_config_error] and [method set_config_missing_templates] to set error details. + Usual implementation can call [method _has_valid_export_configuration] and [method _has_valid_project_configuration] to determine if export is possible. + </description> + </method> + <method name="_cleanup" qualifiers="virtual"> + <return type="void" /> + <description> + [b]Optional.[/b] + Called by the editor before platform is unregistered. + </description> + </method> + <method name="_export_pack" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + [b]Optional.[/b] + Creates a PCK archive at [param path] for the specified [param preset]. + This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and PCK is selected as a file type. + </description> + </method> + <method name="_export_project" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + [b]Required.[/b] + Creates a full project at [param path] for the specified [param preset]. + This method is called when "Export" button is pressed in the export dialog. + This method implementation can call [method EditorExportPlatform.save_pack] or [method EditorExportPlatform.save_zip] to use default PCK/ZIP export process, or calls [method EditorExportPlatform.export_project_files] and implement custom callback for processing each exported file. + </description> + </method> + <method name="_export_zip" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <param index="2" name="path" type="String" /> + <param index="3" name="flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + [b]Optional.[/b] + Create a ZIP archive at [param path] for the specified [param preset]. + This method is called when "Export PCK/ZIP" button is pressed in the export dialog, and ZIP is selected as a file type. + </description> + </method> + <method name="_get_binary_extensions" qualifiers="virtual const"> + <return type="PackedStringArray" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <description> + [b]Required.[/b] + Returns array of supported binary extensions for the full project export. + </description> + </method> + <method name="_get_debug_protocol" qualifiers="virtual const"> + <return type="String" /> + <description> + [b]Optional.[/b] + Returns protocol used for remote debugging. Default implementation return [code]tcp://[/code]. + </description> + </method> + <method name="_get_device_architecture" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="device" type="int" /> + <description> + [b]Optional.[/b] + Returns device architecture for one-click deploy. + </description> + </method> + <method name="_get_export_option_visibility" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="option" type="String" /> + <description> + [b]Optional.[/b] + Validates [param option] and returns visibility for the specified [param preset]. Default implementation return [code]true[/code] for all options. + </description> + </method> + <method name="_get_export_option_warning" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="option" type="StringName" /> + <description> + [b]Optional.[/b] + Validates [param option] and returns warning message for the specified [param preset]. Default implementation return empty string for all options. + </description> + </method> + <method name="_get_export_options" qualifiers="virtual const"> + <return type="Dictionary[]" /> + <description> + [b]Optional.[/b] + Returns a property list, as an [Array] of dictionaries. Each [Dictionary] must at least contain the [code]name: StringName[/code] and [code]type: Variant.Type[/code] entries. + Additionally, the following keys are supported: + - [code]hint: PropertyHint[/code] + - [code]hint_string: String[/code] + - [code]usage: PropertyUsageFlags[/code] + - [code]class_name: StringName[/code] + - [code]default_value: Variant[/code], default value of the property. + - [code]update_visibility: bool[/code], if set to [code]true[/code], [method _get_export_option_visibility] is called for each property when this property is changed. + - [code]required: bool[/code], if set to [code]true[/code], this property warnings are critical, and should be resolved to make export possible. This value is a hint for the [method _has_valid_export_configuration] implementation, and not used by the engine directly. + See also [method Object._get_property_list]. + </description> + </method> + <method name="_get_logo" qualifiers="virtual const"> + <return type="Texture2D" /> + <description> + [b]Required.[/b] + Returns platform logo displayed in the export dialog, logo should be 32x32 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale]. + </description> + </method> + <method name="_get_name" qualifiers="virtual const"> + <return type="String" /> + <description> + [b]Required.[/b] + Returns export platform name. + </description> + </method> + <method name="_get_option_icon" qualifiers="virtual const"> + <return type="ImageTexture" /> + <param index="0" name="device" type="int" /> + <description> + [b]Optional.[/b] + Returns one-click deploy menu item icon for the specified [param device], icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale]. + </description> + </method> + <method name="_get_option_label" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="device" type="int" /> + <description> + [b]Optional.[/b] + Returns one-click deploy menu item label for the specified [param device]. + </description> + </method> + <method name="_get_option_tooltip" qualifiers="virtual const"> + <return type="String" /> + <param index="0" name="device" type="int" /> + <description> + [b]Optional.[/b] + Returns one-click deploy menu item tooltip for the specified [param device]. + </description> + </method> + <method name="_get_options_count" qualifiers="virtual const"> + <return type="int" /> + <description> + [b]Optional.[/b] + Returns number one-click deploy devices (or other one-click option displayed in the menu). + </description> + </method> + <method name="_get_options_tooltip" qualifiers="virtual const"> + <return type="String" /> + <description> + [b]Optional.[/b] + Returns tooltip of the one-click deploy menu button. + </description> + </method> + <method name="_get_os_name" qualifiers="virtual const"> + <return type="String" /> + <description> + [b]Required.[/b] + Returns target OS name. + </description> + </method> + <method name="_get_platform_features" qualifiers="virtual const"> + <return type="PackedStringArray" /> + <description> + [b]Required.[/b] + Returns array of platform specific features. + </description> + </method> + <method name="_get_preset_features" qualifiers="virtual const"> + <return type="PackedStringArray" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <description> + [b]Required.[/b] + Returns array of platform specific features for the specified [param preset]. + </description> + </method> + <method name="_get_run_icon" qualifiers="virtual const"> + <return type="Texture2D" /> + <description> + [b]Optional.[/b] + Returns icon of the one-click deploy menu button, icon should be 16x16 adjusted to the current editor scale, see [method EditorInterface.get_editor_scale]. + </description> + </method> + <method name="_has_valid_export_configuration" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="debug" type="bool" /> + <description> + [b]Required.[/b] + Returns [code]true[/code] if export configuration is valid. + </description> + </method> + <method name="_has_valid_project_configuration" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <description> + [b]Required.[/b] + Returns [code]true[/code] if project configuration is valid. + </description> + </method> + <method name="_is_executable" qualifiers="virtual const"> + <return type="bool" /> + <param index="0" name="path" type="String" /> + <description> + [b]Optional.[/b] + Returns [code]true[/code] if specified file is a valid executable (native executable or script) for the target platform. + </description> + </method> + <method name="_poll_export" qualifiers="virtual"> + <return type="bool" /> + <description> + [b]Optional.[/b] + Returns [code]true[/code] if one-click deploy options are changed and editor interface should be updated. + </description> + </method> + <method name="_run" qualifiers="virtual"> + <return type="int" enum="Error" /> + <param index="0" name="preset" type="EditorExportPreset" /> + <param index="1" name="device" type="int" /> + <param index="2" name="debug_flags" type="int" enum="EditorExportPlatform.DebugFlags" is_bitfield="true" /> + <description> + [b]Optional.[/b] + This method is called when [param device] one-click deploy menu option is selected. + Implementation should export project to a temporary location, upload and run it on the specific [param device], or perform another action associated with the menu item. + </description> + </method> + <method name="_should_update_export_options" qualifiers="virtual"> + <return type="bool" /> + <description> + [b]Optional.[/b] + Returns [code]true[/code] if export options list is changed and presets should be updated. + </description> + </method> + <method name="get_config_error" qualifiers="const"> + <return type="String" /> + <description> + Returns current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations. + </description> + </method> + <method name="get_config_missing_templates" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations. + </description> + </method> + <method name="set_config_error" qualifiers="const"> + <return type="void" /> + <param index="0" name="error_text" type="String" /> + <description> + Sets current configuration error message text. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations. + </description> + </method> + <method name="set_config_missing_templates" qualifiers="const"> + <return type="void" /> + <param index="0" name="missing_templates" type="bool" /> + <description> + Set to [code]true[/code] is export templates are missing from the current configuration. This method should be called only from the [method _can_export], [method _has_valid_export_configuration], or [method _has_valid_project_configuration] implementations. + </description> + </method> + </methods> +</class> diff --git a/doc/classes/EditorExportPlugin.xml b/doc/classes/EditorExportPlugin.xml index 9ef911a68d..42e1968eb0 100644 --- a/doc/classes/EditorExportPlugin.xml +++ b/doc/classes/EditorExportPlugin.xml @@ -305,6 +305,18 @@ In case of a directory code-sign will error if you place non code object in directory. </description> </method> + <method name="get_export_platform" qualifiers="const"> + <return type="EditorExportPlatform" /> + <description> + Returns currently used export platform. + </description> + </method> + <method name="get_export_preset" qualifiers="const"> + <return type="EditorExportPreset" /> + <description> + Returns currently used export preset. + </description> + </method> <method name="get_option" qualifiers="const"> <return type="Variant" /> <param index="0" name="name" type="StringName" /> diff --git a/doc/classes/EditorExportPreset.xml b/doc/classes/EditorExportPreset.xml new file mode 100644 index 0000000000..bba79364e4 --- /dev/null +++ b/doc/classes/EditorExportPreset.xml @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="EditorExportPreset" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + Export preset configuration. + </brief_description> + <description> + Export preset configuration. Instances of [EditorExportPreset] by editor UI and intended to be used a read-only configuration passed to the [EditorExportPlatform] methods when exporting the project. + </description> + <tutorials> + </tutorials> + <methods> + <method name="are_advanced_options_enabled" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code], is "Advanced" toggle is enabled in the export dialog. + </description> + </method> + <method name="get_custom_features" qualifiers="const"> + <return type="String" /> + <description> + Returns string with a comma separated list of custom features. + </description> + </method> + <method name="get_customized_files" qualifiers="const"> + <return type="Dictionary" /> + <description> + Returns [Dictionary] of files selected in the "Resources" tab of the export dialog. Dictionary keys are file names and values are export mode - [code]"strip[/code], [code]"keep"[/code], or [code]"remove"[/code]. See also [method get_file_export_mode]. + </description> + </method> + <method name="get_customized_files_count" qualifiers="const"> + <return type="int" /> + <description> + Returns number of files selected in the "Resources" tab of the export dialog. + </description> + </method> + <method name="get_encrypt_directory" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code], PCK directory encryption is enabled in the export dialog. + </description> + </method> + <method name="get_encrypt_pck" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code], PCK encryption is enabled in the export dialog. + </description> + </method> + <method name="get_encryption_ex_filter" qualifiers="const"> + <return type="String" /> + <description> + Returns file filters to exclude during PCK encryption. + </description> + </method> + <method name="get_encryption_in_filter" qualifiers="const"> + <return type="String" /> + <description> + Returns file filters to include during PCK encryption. + </description> + </method> + <method name="get_encryption_key" qualifiers="const"> + <return type="String" /> + <description> + Returns PCK encryption key. + </description> + </method> + <method name="get_exclude_filter" qualifiers="const"> + <return type="String" /> + <description> + Returns file filters to exclude during export. + </description> + </method> + <method name="get_export_filter" qualifiers="const"> + <return type="int" enum="EditorExportPreset.ExportFilter" /> + <description> + Returns export file filter mode selected in the "Resources" tab of the export dialog. + </description> + </method> + <method name="get_export_path" qualifiers="const"> + <return type="String" /> + <description> + Returns export target path. + </description> + </method> + <method name="get_file_export_mode" qualifiers="const"> + <return type="int" enum="EditorExportPreset.FileExportMode" /> + <param index="0" name="path" type="String" /> + <param index="1" name="default" type="int" enum="EditorExportPreset.FileExportMode" default="0" /> + <description> + Returns file export mode for the specified file. + </description> + </method> + <method name="get_files_to_export" qualifiers="const"> + <return type="PackedStringArray" /> + <description> + Returns array of files to export. + </description> + </method> + <method name="get_include_filter" qualifiers="const"> + <return type="String" /> + <description> + Returns file filters to include during export. + </description> + </method> + <method name="get_or_env" qualifiers="const"> + <return type="Variant" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="env_var" type="String" /> + <description> + Returns export option value or value of environment variable if it is set. + </description> + </method> + <method name="get_preset_name" qualifiers="const"> + <return type="String" /> + <description> + Returns export preset name. + </description> + </method> + <method name="get_script_export_mode" qualifiers="const"> + <return type="int" /> + <description> + Returns script export mode. + </description> + </method> + <method name="get_version" qualifiers="const"> + <return type="String" /> + <param index="0" name="name" type="StringName" /> + <param index="1" name="windows_version" type="bool" /> + <description> + Returns the preset's version number, or fall back to the [member ProjectSettings.application/config/version] project setting if set to an empty string. + If [param windows_version] is [code]true[/code], formats the returned version number to be compatible with Windows executable metadata. + </description> + </method> + <method name="has" qualifiers="const"> + <return type="bool" /> + <param index="0" name="property" type="StringName" /> + <description> + Returns [code]true[/code] if preset has specified property. + </description> + </method> + <method name="has_export_file"> + <return type="bool" /> + <param index="0" name="path" type="String" /> + <description> + Returns [code]true[/code] if specified file is exported. + </description> + </method> + <method name="is_dedicated_server" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if dedicated server export mode is selected in the export dialog. + </description> + </method> + <method name="is_runnable" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code] if "Runnable" toggle is enabled in the export dialog. + </description> + </method> + </methods> + <constants> + <constant name="EXPORT_ALL_RESOURCES" value="0" enum="ExportFilter"> + </constant> + <constant name="EXPORT_SELECTED_SCENES" value="1" enum="ExportFilter"> + </constant> + <constant name="EXPORT_SELECTED_RESOURCES" value="2" enum="ExportFilter"> + </constant> + <constant name="EXCLUDE_SELECTED_RESOURCES" value="3" enum="ExportFilter"> + </constant> + <constant name="EXPORT_CUSTOMIZED" value="4" enum="ExportFilter"> + </constant> + <constant name="MODE_FILE_NOT_CUSTOMIZED" value="0" enum="FileExportMode"> + </constant> + <constant name="MODE_FILE_STRIP" value="1" enum="FileExportMode"> + </constant> + <constant name="MODE_FILE_KEEP" value="2" enum="FileExportMode"> + </constant> + <constant name="MODE_FILE_REMOVE" value="3" enum="FileExportMode"> + </constant> + <constant name="MODE_SCRIPT_TEXT" value="0" enum="ScriptExportMode"> + </constant> + <constant name="MODE_SCRIPT_BINARY_TOKENS" value="1" enum="ScriptExportMode"> + </constant> + <constant name="MODE_SCRIPT_BINARY_TOKENS_COMPRESSED" value="2" enum="ScriptExportMode"> + </constant> + </constants> +</class> diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index 178be439c3..6809d4ac93 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -117,6 +117,12 @@ [b]Note:[/b] When creating custom editor UI, prefer accessing theme items directly from your GUI nodes using the [code]get_theme_*[/code] methods. </description> </method> + <method name="get_editor_undo_redo" qualifiers="const"> + <return type="EditorUndoRedoManager" /> + <description> + Returns the editor's [EditorUndoRedoManager]. + </description> + </method> <method name="get_editor_viewport_2d" qualifiers="const"> <return type="SubViewport" /> <description> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index ed3233b1ae..37f8b2213b 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -455,6 +455,13 @@ Adds a [Script] as debugger plugin to the Debugger. The script must extend [EditorDebuggerPlugin]. </description> </method> + <method name="add_export_platform"> + <return type="void" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <description> + Registers a new [EditorExportPlatform]. Export platforms provides functionality of exporting to the specific platform. + </description> + </method> <method name="add_export_plugin"> <return type="void" /> <param index="0" name="plugin" type="EditorExportPlugin" /> @@ -653,6 +660,13 @@ Removes the debugger plugin with given script from the Debugger. </description> </method> + <method name="remove_export_platform"> + <return type="void" /> + <param index="0" name="platform" type="EditorExportPlatform" /> + <description> + Removes an export platform registered by [method add_export_platform]. + </description> + </method> <method name="remove_export_plugin"> <return type="void" /> <param index="0" name="plugin" type="EditorExportPlugin" /> 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/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml index 5ac0d790a2..0f8c69a1bb 100644 --- a/doc/classes/EditorUndoRedoManager.xml +++ b/doc/classes/EditorUndoRedoManager.xml @@ -68,6 +68,21 @@ Register a reference for "undo" that will be erased if the "undo" history is lost. This is useful mostly for nodes removed with the "do" call (not the "undo" call!). </description> </method> + <method name="clear_history"> + <return type="void" /> + <param index="0" name="id" type="int" default="-99" /> + <param index="1" name="increase_version" type="bool" default="true" /> + <description> + Clears the given undo history. You can clear history for a specific scene, global history, or for all scenes at once if [param id] is [constant INVALID_HISTORY]. + If [param increase_version] is [code]true[/code], the undo history version will be increased, marking it as unsaved. Useful for operations that modify the scene, but don't support undo. + [codeblock] + var scene_root = EditorInterface.get_edited_scene_root() + var undo_redo = EditorInterface.get_editor_undo_redo() + undo_redo.clear_history(undo_redo.get_object_history_id(scene_root)) + [/codeblock] + [b]Note:[/b] If you want to mark an edited scene as unsaved without clearing its history, use [method EditorInterface.mark_scene_as_unsaved] instead. + </description> + </method> <method name="commit_action"> <return type="void" /> <param index="0" name="execute" type="bool" default="true" /> diff --git a/doc/classes/GDExtensionManager.xml b/doc/classes/GDExtensionManager.xml index 211bc023c0..97d2d08752 100644 --- a/doc/classes/GDExtensionManager.xml +++ b/doc/classes/GDExtensionManager.xml @@ -56,6 +56,20 @@ </method> </methods> <signals> + <signal name="extension_loaded"> + <param index="0" name="extension" type="GDExtension" /> + <description> + Emitted after the editor has finished loading a new extension. + [b]Note:[/b] This signal is only emitted in editor builds. + </description> + </signal> + <signal name="extension_unloading"> + <param index="0" name="extension" type="GDExtension" /> + <description> + Emitted before the editor starts unloading an extension. + [b]Note:[/b] This signal is only emitted in editor builds. + </description> + </signal> <signal name="extensions_reloaded"> <description> Emitted after the editor has finished reloading one or more extensions. diff --git a/doc/classes/JSON.xml b/doc/classes/JSON.xml index 8a19aa39bf..fe5fdfa89a 100644 --- a/doc/classes/JSON.xml +++ b/doc/classes/JSON.xml @@ -37,6 +37,16 @@ <tutorials> </tutorials> <methods> + <method name="from_native" qualifiers="static"> + <return type="Variant" /> + <param index="0" name="variant" type="Variant" /> + <param index="1" name="allow_classes" type="bool" default="false" /> + <param index="2" name="allow_scripts" type="bool" default="false" /> + <description> + Converts a native engine type to a JSON-compliant dictionary. + By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified. + </description> + </method> <method name="get_error_line" qualifiers="const"> <return type="int" /> <description> @@ -123,6 +133,16 @@ [/codeblock] </description> </method> + <method name="to_native" qualifiers="static"> + <return type="Variant" /> + <param index="0" name="json" type="Variant" /> + <param index="1" name="allow_classes" type="bool" default="false" /> + <param index="2" name="allow_scripts" type="bool" default="false" /> + <description> + Converts a JSON-compliant dictionary that was created with [method from_native] back to native engine types. + By default, classes and scripts are ignored for security reasons, unless [param allow_classes] or [param allow_scripts] are specified. + </description> + </method> </methods> <members> <member name="data" type="Variant" setter="set_data" getter="get_data" default="null"> diff --git a/doc/classes/PropertyTweener.xml b/doc/classes/PropertyTweener.xml index b7aa6947d9..76cf4cbfeb 100644 --- a/doc/classes/PropertyTweener.xml +++ b/doc/classes/PropertyTweener.xml @@ -5,6 +5,7 @@ </brief_description> <description> [PropertyTweener] is used to interpolate a property in an object. See [method Tween.tween_property] for more usage information. + The tweener will finish automatically if the target object is freed. [b]Note:[/b] [method Tween.tween_property] is the only correct way to create [PropertyTweener]. Any [PropertyTweener] created manually will not function correctly. </description> <tutorials> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index c81d5d4fab..4cdfba17e9 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -412,6 +412,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 8f118ace03..4362b38042 100644 --- a/doc/classes/ResourceImporterWAV.xml +++ b/doc/classes/ResourceImporterWAV.xml @@ -12,9 +12,9 @@ <members> <member name="compress/mode" type="int" setter="" getter="" default="0"> The compression mode to use on import. - [b]Disabled:[/b] Imports audio data without any compression. This results in the highest possible quality. - [b]RAM (Ima-ADPCM):[/b] Performs fast lossy compression on import. Low CPU cost, but quality is noticeably decreased compared to Ogg Vorbis or even MP3. - [b]QOA ([url=https://qoaformat.org/]Quite OK Audio[/url]):[/b] Performs lossy compression on import. CPU cost is slightly higher than IMA-ADPCM, but quality is much higher. + - [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. + - [b][url=https://qoaformat.org/]Quite OK Audio[/url]:[/b] Also applies lossy compression on import, having a slightly higher CPU cost compared to IMA ADPCM, but much higher quality and the lowest memory usage. </member> <member name="edit/loop_begin" type="int" setter="" getter="" default="0"> The begin loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b], or [b]Backward[/b]. This is set in samples after the beginning of the audio file. @@ -23,11 +23,12 @@ The end loop point to use when [member edit/loop_mode] is [b]Forward[/b], [b]Ping-Pong[/b], or [b]Backward[/b]. This is set in samples after the beginning of the audio file. A value of [code]-1[/code] uses the end of the audio file as the end loop point. </member> <member name="edit/loop_mode" type="int" setter="" getter="" default="0"> - Controls how audio should loop. This is automatically read from the WAV metadata on import. - [b]Disabled:[/b] Don't loop audio, even if metadata indicates the file should be played back looping. - [b]Forward:[/b] Standard audio looping. - [b]Ping-Pong:[/b] Play audio forward until it's done playing, then play it backward and repeat. This is similar to mirrored texture repeat, but for audio. - [b]Backward:[/b] Play audio in reverse and loop back to the end when done playing. + Controls how audio should loop. + - [b]Detect From WAV:[/b] Uses loop information from the WAV metadata. + - [b]Disabled:[/b] Don't loop audio, even if the metadata indicates the file playback should loop. + - [b]Forward:[/b] Standard audio looping. Plays the audio forward from the beginning to [member edit/loop_end], then returns to [member edit/loop_begin] and repeats. + - [b]Ping-Pong:[/b] Plays the audio forward until [member edit/loop_end], then backwards to [member edit/loop_begin], repeating this cycle. + - [b]Backward:[/b] Plays the audio backwards from [member edit/loop_end] to [member edit/loop_begin], then repeats. [b]Note:[/b] In [AudioStreamPlayer], the [signal AudioStreamPlayer.finished] signal won't be emitted for looping audio when it reaches the end of the audio file, as the audio will keep playing indefinitely. </member> <member name="edit/normalize" type="bool" setter="" getter="" default="false"> diff --git a/doc/classes/ScriptEditor.xml b/doc/classes/ScriptEditor.xml index 50adecccf5..5cf077c266 100644 --- a/doc/classes/ScriptEditor.xml +++ b/doc/classes/ScriptEditor.xml @@ -10,6 +10,12 @@ <tutorials> </tutorials> <methods> + <method name="get_breakpoints"> + <return type="PackedStringArray" /> + <description> + Returns array of breakpoints. + </description> + </method> <method name="get_current_editor" qualifiers="const"> <return type="ScriptEditorBase" /> <description> diff --git a/doc/classes/Tweener.xml b/doc/classes/Tweener.xml index 65148e875d..88f5f9978c 100644 --- a/doc/classes/Tweener.xml +++ b/doc/classes/Tweener.xml @@ -11,7 +11,7 @@ <signals> <signal name="finished"> <description> - Emitted when the [Tweener] has just finished its job. + Emitted when the [Tweener] has just finished its job or became invalid (e.g. due to a freed object). </description> </signal> </signals> diff --git a/doc/classes/VehicleWheel3D.xml b/doc/classes/VehicleWheel3D.xml index 92b2297bf4..7b4952580c 100644 --- a/doc/classes/VehicleWheel3D.xml +++ b/doc/classes/VehicleWheel3D.xml @@ -18,6 +18,18 @@ Returns [code]null[/code] if the wheel is not in contact with a surface, or the contact body is not a [PhysicsBody3D]. </description> </method> + <method name="get_contact_normal" qualifiers="const"> + <return type="Vector3" /> + <description> + Returns the normal of the suspension's collision in world space if the wheel is in contact. If the wheel isn't in contact with anything, returns a vector pointing directly along the suspension axis toward the vehicle in world space. + </description> + </method> + <method name="get_contact_point" qualifiers="const"> + <return type="Vector3" /> + <description> + Returns the point of the suspension's collision in world space if the wheel is in contact. If the wheel isn't in contact with anything, returns the maximum point of the wheel's ray cast in world space, which is defined by [code]wheel_rest_length + wheel_radius[/code]. + </description> + </method> <method name="get_rpm" qualifiers="const"> <return type="float" /> <description> diff --git a/drivers/gles3/storage/light_storage.h b/drivers/gles3/storage/light_storage.h index 81e7220439..ed00dd235f 100644 --- a/drivers/gles3/storage/light_storage.h +++ b/drivers/gles3/storage/light_storage.h @@ -203,7 +203,7 @@ struct LightmapInstance { class LightStorage : public RendererLightStorage { public: - enum ShadowAtlastQuadrant { + enum ShadowAtlastQuadrant : uint32_t { QUADRANT_SHIFT = 27, OMNI_LIGHT_FLAG = 1 << 26, SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1, 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/metal_objects.h b/drivers/metal/metal_objects.h index f0a3e85f88..97f33bb1e8 100644 --- a/drivers/metal/metal_objects.h +++ b/drivers/metal/metal_objects.h @@ -227,6 +227,7 @@ public: id<MTLRenderCommandEncoder> encoder = nil; id<MTLBuffer> __unsafe_unretained index_buffer = nil; // Buffer is owned by RDD. MTLIndexType index_type = MTLIndexTypeUInt16; + uint32_t index_offset = 0; LocalVector<id<MTLBuffer> __unsafe_unretained> vertex_buffers; LocalVector<NSUInteger> vertex_offsets; // clang-format off diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm index c66b683e23..abdcccf00c 100644 --- a/drivers/metal/metal_objects.mm +++ b/drivers/metal/metal_objects.mm @@ -717,6 +717,7 @@ void MDCommandBuffer::render_bind_index_buffer(RDD::BufferID p_buffer, RDD::Inde render.index_buffer = rid::get(p_buffer); render.index_type = p_format == RDD::IndexBufferFormat::INDEX_BUFFER_FORMAT_UINT16 ? MTLIndexTypeUInt16 : MTLIndexTypeUInt32; + render.index_offset = p_offset; } void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count, @@ -729,13 +730,16 @@ void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count, id<MTLRenderCommandEncoder> enc = render.encoder; + uint32_t index_offset = render.index_offset; + index_offset += p_first_index * (render.index_type == MTLIndexTypeUInt16 ? sizeof(uint16_t) : sizeof(uint32_t)); + [enc drawIndexedPrimitives:render.pipeline->raster_state.render_primitive indexCount:p_index_count indexType:render.index_type indexBuffer:render.index_buffer - indexBufferOffset:p_vertex_offset + indexBufferOffset:index_offset instanceCount:p_instance_count - baseVertex:p_first_index + baseVertex:p_vertex_offset baseInstance:p_first_instance]; } 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..34758e8c7d 100644 --- a/drivers/unix/file_access_unix_pipe.cpp +++ b/drivers/unix/file_access_unix_pipe.cpp @@ -125,22 +125,9 @@ 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) { @@ -155,18 +142,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..19acdb5a37 100644 --- a/drivers/unix/file_access_unix_pipe.h +++ b/drivers/unix/file_access_unix_pipe.h @@ -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/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/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index 1d1f2ce415..f7632842ed 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -71,7 +71,9 @@ struct DirAccessWindowsPrivate { String DirAccessWindows::fix_path(const String &p_path) const { String r_path = DirAccess::fix_path(p_path.trim_prefix(R"(\\?\)").replace("\\", "/")); - + if (r_path.ends_with(":")) { + r_path += "/"; + } if (r_path.is_relative_path()) { r_path = current_dir.trim_prefix(R"(\\?\)").replace("\\", "/").path_join(r_path); } else if (r_path == ".") { @@ -304,7 +306,7 @@ Error DirAccessWindows::rename(String p_path, String p_new_path) { } } - return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(p_new_path.utf16().get_data())) != 0 ? OK : FAILED; + return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED; } } 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..0c953b14aa 100644 --- a/drivers/windows/file_access_windows_pipe.cpp +++ b/drivers/windows/file_access_windows_pipe.cpp @@ -96,22 +96,9 @@ 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; if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) { @@ -126,15 +113,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..4e9bd036ae 100644 --- a/drivers/windows/file_access_windows_pipe.h +++ b/drivers/windows/file_access_windows_pipe.h @@ -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_track_editor.cpp b/editor/animation_track_editor.cpp index f4403780c2..80bf9f850e 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -41,6 +41,7 @@ #include "editor/gui/editor_spin_slider.h" #include "editor/gui/scene_tree_editor.h" #include "editor/inspector_dock.h" +#include "editor/multi_node_edit.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/3d/mesh_instance_3d.h" @@ -120,12 +121,18 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu float val = p_value; float prev_val = animation->track_get_key_transition(track, key); setting = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Animation Change Transition"), UndoRedo::MERGE_ENDS); undo_redo->add_do_method(animation.ptr(), "track_set_key_transition", track, key, val); undo_redo->add_undo_method(animation.ptr(), "track_set_key_transition", track, key, prev_val); undo_redo->add_do_method(this, "_update_obj", animation); undo_redo->add_undo_method(this, "_update_obj", animation); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); setting = false; @@ -177,12 +184,18 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu } setting = true; + undo_redo->create_action(TTR("Animation Change Keyframe Value"), UndoRedo::MERGE_ENDS); Variant prev = animation->track_get_key_value(track, key); undo_redo->add_do_method(animation.ptr(), "track_set_key_value", track, key, value); undo_redo->add_undo_method(animation.ptr(), "track_set_key_value", track, key, prev); undo_redo->add_do_method(this, "_update_obj", animation); undo_redo->add_undo_method(this, "_update_obj", animation); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); setting = false; @@ -3281,6 +3294,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) { undo_redo->create_action(TTR("Change Animation Update Mode")); undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", track, update_mode); undo_redo->add_undo_method(animation.ptr(), "value_track_set_update_mode", track, animation->value_track_get_update_mode(track)); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); queue_redraw(); @@ -3295,6 +3313,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) { undo_redo->create_action(TTR("Change Animation Interpolation Mode")); undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode); undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", track, animation->track_get_interpolation_type(track)); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); queue_redraw(); } break; @@ -3305,6 +3328,11 @@ void AnimationTrackEdit::_menu_selected(int p_index) { undo_redo->create_action(TTR("Change Animation Loop Mode")); undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, loop_wrap); undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_loop_wrap", track, animation->track_get_interpolation_loop_wrap(track)); + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } undo_redo->commit_action(); queue_redraw(); @@ -3661,7 +3689,7 @@ void AnimationTrackEditor::update_keying() { EditorSelectionHistory *editor_history = EditorNode::get_singleton()->get_editor_selection_history(); if (is_visible_in_tree() && animation.is_valid() && editor_history->get_path_size() > 0) { Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0)); - keying_enabled = Object::cast_to<Node>(obj) != nullptr; + keying_enabled = Object::cast_to<Node>(obj) != nullptr || Object::cast_to<MultiNodeEdit>(obj) != nullptr; } if (keying_enabled == keying) { @@ -4078,19 +4106,20 @@ void AnimationTrackEditor::_insert_animation_key(NodePath p_path, const Variant _query_insert(id); } -void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists) { +void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists, bool p_advance) { ERR_FAIL_NULL(root); // Let's build a node path. - Node *node = p_node; - String path = root->get_path_to(node, true); + String path = root->get_path_to(p_node, true); + + Variant value = p_node->get(p_property); - if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { - if (node == AnimationPlayerEditor::get_singleton()->get_player()) { + if (Object::cast_to<AnimationPlayer>(p_node) && p_property == "current_animation") { + if (p_node == AnimationPlayerEditor::get_singleton()->get_player()) { EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); return; } - _insert_animation_key(path, p_value); + _insert_animation_key(path, value); return; } @@ -4118,26 +4147,26 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p InsertData id; id.path = np; id.track_idx = i; - id.value = p_value; + id.value = value; id.type = Animation::TYPE_VALUE; // TRANSLATORS: This describes the target of new animation track, will be inserted into another string. id.query = vformat(TTR("property '%s'"), p_property); - id.advance = false; + id.advance = p_advance; // Dialog insert. _query_insert(id); inserted = true; } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { - Variant value; + Variant actual_value; String track_path = animation->track_get_path(i); if (track_path == np) { - value = p_value; // All good. + actual_value = value; // All good. } else { int sep = track_path.rfind(":"); if (sep != -1) { String base_path = track_path.substr(0, sep); if (base_path == np) { String value_name = track_path.substr(sep + 1); - value = p_value.get(value_name); + actual_value = value.get(value_name); } else { continue; } @@ -4149,10 +4178,10 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p InsertData id; id.path = animation->track_get_path(i); id.track_idx = i; - id.value = value; + id.value = actual_value; id.type = Animation::TYPE_BEZIER; id.query = vformat(TTR("property '%s'"), p_property); - id.advance = false; + id.advance = p_advance; // Dialog insert. _query_insert(id); inserted = true; @@ -4165,105 +4194,41 @@ void AnimationTrackEditor::insert_node_value_key(Node *p_node, const String &p_p InsertData id; id.path = np; id.track_idx = -1; - id.value = p_value; + id.value = value; id.type = Animation::TYPE_VALUE; id.query = vformat(TTR("property '%s'"), p_property); - id.advance = false; + id.advance = p_advance; // Dialog insert. _query_insert(id); } -void AnimationTrackEditor::insert_value_key(const String &p_property, const Variant &p_value, bool p_advance) { +void AnimationTrackEditor::insert_value_key(const String &p_property, bool p_advance) { EditorSelectionHistory *history = EditorNode::get_singleton()->get_editor_selection_history(); ERR_FAIL_NULL(root); ERR_FAIL_COND(history->get_path_size() == 0); Object *obj = ObjectDB::get_instance(history->get_path_object(0)); - ERR_FAIL_NULL(Object::cast_to<Node>(obj)); - - // Let's build a node path. - Node *node = Object::cast_to<Node>(obj); - String path = root->get_path_to(node, true); - - if (Object::cast_to<AnimationPlayer>(node) && p_property == "current_animation") { - if (node == AnimationPlayerEditor::get_singleton()->get_player()) { - EditorNode::get_singleton()->show_warning(TTR("AnimationPlayer can't animate itself, only other players.")); - return; - } - _insert_animation_key(path, p_value); - return; - } - - for (int i = 1; i < history->get_path_size(); i++) { - String prop = history->get_path_property(i); - ERR_FAIL_COND(prop.is_empty()); - path += ":" + prop; - } - - path += ":" + p_property; - NodePath np = path; + Ref<MultiNodeEdit> multi_node_edit(obj); + if (multi_node_edit.is_valid()) { + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + ERR_FAIL_NULL(edited_scene); - // Locate track. + make_insert_queue(); - bool inserted = false; - - make_insert_queue(); - for (int i = 0; i < animation->get_track_count(); i++) { - if (animation->track_get_type(i) == Animation::TYPE_VALUE) { - if (animation->track_get_path(i) != np) { - continue; - } - - InsertData id; - id.path = np; - id.track_idx = i; - id.value = p_value; - id.type = Animation::TYPE_VALUE; - id.query = vformat(TTR("property '%s'"), p_property); - id.advance = p_advance; - // Dialog insert. - _query_insert(id); - inserted = true; - } else if (animation->track_get_type(i) == Animation::TYPE_BEZIER) { - Variant value; - if (animation->track_get_path(i) == np) { - value = p_value; // All good. - } else { - String tpath = animation->track_get_path(i); - int index = tpath.rfind(":"); - if (NodePath(tpath.substr(0, index + 1)) == np) { - String subindex = tpath.substr(index + 1, tpath.length() - index); - value = p_value.get(subindex); - } else { - continue; - } - } - - InsertData id; - id.path = animation->track_get_path(i); - id.track_idx = i; - id.value = value; - id.type = Animation::TYPE_BEZIER; - id.query = vformat(TTR("property '%s'"), p_property); - id.advance = p_advance; - // Dialog insert. - _query_insert(id); - inserted = true; + for (int i = 0; i < multi_node_edit->get_node_count(); ++i) { + Node *node = edited_scene->get_node(multi_node_edit->get_node(i)); + insert_node_value_key(node, p_property, false, p_advance); } - } - commit_insert_queue(); - if (!inserted) { - InsertData id; - id.path = np; - id.track_idx = -1; - id.value = p_value; - id.type = Animation::TYPE_VALUE; - id.query = vformat(TTR("property '%s'"), p_property); - id.advance = p_advance; - // Dialog insert. - _query_insert(id); + commit_insert_queue(); + } else { + Node *node = Object::cast_to<Node>(obj); + ERR_FAIL_NULL(node); + + make_insert_queue(); + insert_node_value_key(node, p_property, false, p_advance); + commit_insert_queue(); } } @@ -4589,6 +4554,10 @@ bool AnimationTrackEditor::is_snap_enabled() const { return snap->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 { for (const KeyValue<SelectedKey, KeyInfo> &E : selection) { const Animation::TrackType track_type = animation->track_get_type(E.key.track); @@ -5650,6 +5619,14 @@ void AnimationTrackEditor::_move_selection_commit() { moving_selection = false; undo_redo->add_do_method(this, "_redraw_tracks"); undo_redo->add_undo_method(this, "_redraw_tracks"); + + // Update key frame. + AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton(); + if (ape) { + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); + } + undo_redo->commit_action(); } @@ -6887,8 +6864,8 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { _redraw_tracks(); _update_key_edit(); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr())); - undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this)); + undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr())); + undo_redo->clear_history(undo_redo->get_history_id_for_object(this)); } break; case EDIT_CLEAN_UP_ANIMATION: { @@ -7030,8 +7007,8 @@ void AnimationTrackEditor::_cleanup_animation(Ref<Animation> p_animation) { } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->clear_history(true, undo_redo->get_history_id_for_object(animation.ptr())); - undo_redo->clear_history(true, undo_redo->get_history_id_for_object(this)); + undo_redo->clear_history(undo_redo->get_history_id_for_object(animation.ptr())); + undo_redo->clear_history(undo_redo->get_history_id_for_object(this)); _update_tracks(); } @@ -7079,7 +7056,10 @@ void AnimationTrackEditor::_update_snap_unit() { if (timeline->is_using_fps()) { snap_unit = 1.0 / step->get_value(); } else { - snap_unit = 1.0 / Math::round(1.0 / step->get_value()); // Follow the snap behavior of the timeline editor. + double integer; + double fraction = Math::modf(step->get_value(), &integer); + fraction = 1.0 / Math::round(1.0 / fraction); + snap_unit = integer + fraction; } } @@ -7713,6 +7693,8 @@ void AnimationTrackKeyEditEditor::_time_edit_exited() { undo_redo->add_do_method(ate, "_select_at_anim", animation, track, new_time); undo_redo->add_undo_method(ate, "_select_at_anim", animation, track, key_data_cache.time); } + undo_redo->add_do_method(ape, "_animation_update_key_frame"); + undo_redo->add_undo_method(ape, "_animation_update_key_frame"); } undo_redo->commit_action(); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 59ee6535ac..516ddb0154 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -713,8 +713,8 @@ public: void cleanup(); void set_anim_pos(float p_pos); - void insert_node_value_key(Node *p_node, const String &p_property, const Variant &p_value, bool p_only_if_exists = false); - void insert_value_key(const String &p_property, const Variant &p_value, bool p_advance); + void insert_node_value_key(Node *p_node, const String &p_property, bool p_only_if_exists = false, bool p_advance = false); + void insert_value_key(const String &p_property, bool p_advance); void insert_transform_key(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type, const Variant &p_value); bool has_track(Node3D *p_node, const String &p_sub, const Animation::TrackType p_type); void make_insert_queue(); @@ -729,6 +729,7 @@ public: bool is_key_clipboard_active() const; bool is_moving_selection() const; bool is_snap_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/debugger/editor_debugger_node.cpp b/editor/debugger/editor_debugger_node.cpp index f01301384a..b4265f9fc0 100644 --- a/editor/debugger/editor_debugger_node.cpp +++ b/editor/debugger/editor_debugger_node.cpp @@ -303,7 +303,7 @@ void EditorDebuggerNode::stop(bool p_force) { }); _break_state_changed(); breakpoints.clear(); - EditorUndoRedoManager::get_singleton()->clear_history(false, EditorUndoRedoManager::REMOTE_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::REMOTE_HISTORY, false); set_process(false); } diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp index 3b337997e0..c076c99cd3 100644 --- a/editor/editor_audio_buses.cpp +++ b/editor/editor_audio_buses.cpp @@ -1262,7 +1262,7 @@ void EditorAudioBuses::_load_default_layout() { file->set_text(String(TTR("Layout:")) + " " + layout_path.get_file()); AudioServer::get_singleton()->set_bus_layout(state); _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); } @@ -1278,7 +1278,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) { file->set_text(String(TTR("Layout:")) + " " + p_string.get_file()); AudioServer::get_singleton()->set_bus_layout(state); _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); } else if (file_dialog->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) { @@ -1298,7 +1298,7 @@ void EditorAudioBuses::_file_dialog_callback(const String &p_string) { edited_path = p_string; file->set_text(String(TTR("Layout:")) + " " + p_string.get_file()); _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); } } @@ -1397,7 +1397,7 @@ void EditorAudioBuses::open_layout(const String &p_path) { file->set_text(p_path.get_file()); AudioServer::get_singleton()->set_bus_layout(state); _rebuild_buses(); - EditorUndoRedoManager::get_singleton()->clear_history(true, EditorUndoRedoManager::GLOBAL_HISTORY); + EditorUndoRedoManager::get_singleton()->clear_history(EditorUndoRedoManager::GLOBAL_HISTORY); callable_mp(this, &EditorAudioBuses::_select_layout).call_deferred(); } diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index 474a45cf2b..bae9062ff1 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -243,18 +243,27 @@ void EditorFileSystem::_first_scan_filesystem() { first_scan_root_dir = memnew(ScannedDirectory); first_scan_root_dir->full_path = "res://"; HashSet<String> existing_class_names; + HashSet<String> extensions; ep.step(TTR("Scanning file structure..."), 0, true); nb_files_total = _scan_new_dir(first_scan_root_dir, d); // This loads the global class names from the scripts and ensures that even if the // global_script_class_cache.cfg was missing or invalid, the global class names are valid in ScriptServer. + // At the same time, to prevent looping multiple times in all files, it looks for extensions. ep.step(TTR("Loading global class names..."), 1, true); - _first_scan_process_scripts(first_scan_root_dir, existing_class_names); + _first_scan_process_scripts(first_scan_root_dir, existing_class_names, extensions); // Removing invalid global class to prevent having invalid paths in ScriptServer. _remove_invalid_global_class_names(existing_class_names); + // Processing extensions to add new extensions or remove invalid ones. + // Important to do it in the first scan so custom types, new class names, custom importers, etc... + // from extensions are ready to go before plugins, autoloads and resources validation/importation. + // At this point, a restart of the editor should not be needed so we don't use the return value. + ep.step(TTR("Verifying GDExtensions..."), 2, true); + GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions); + // Now that all the global class names should be loaded, create autoloads and plugins. // This is done after loading the global class names because autoloads and plugins can use // global class names. @@ -267,9 +276,9 @@ void EditorFileSystem::_first_scan_filesystem() { ep.step(TTR("Starting file scan..."), 5, true); } -void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names) { +void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions) { for (ScannedDirectory *scan_sub_dir : p_scan_dir->subdirs) { - _first_scan_process_scripts(scan_sub_dir, p_existing_class_names); + _first_scan_process_scripts(scan_sub_dir, p_existing_class_names, p_extensions); } for (const String &scan_file : p_scan_dir->files) { @@ -285,6 +294,8 @@ void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_sca if (!script_class_name.is_empty()) { p_existing_class_names.insert(script_class_name); } + } else if (type == SNAME("GDExtension")) { + p_extensions.insert(path); } } } @@ -1855,25 +1866,26 @@ void EditorFileSystem::_update_script_classes() { return; } - update_script_mutex.lock(); + { + MutexLock update_script_lock(update_script_mutex); - EditorProgress *ep = nullptr; - if (update_script_paths.size() > 1) { - ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size())); - } + EditorProgress *ep = nullptr; + if (update_script_paths.size() > 1) { + ep = memnew(EditorProgress("update_scripts_classes", TTR("Registering global classes..."), update_script_paths.size())); + } - int step_count = 0; - for (const KeyValue<String, ScriptInfo> &E : update_script_paths) { - _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path); - if (ep) { - ep->step(E.value.script_class_name, step_count++, false); + int step_count = 0; + for (const KeyValue<String, ScriptInfo> &E : update_script_paths) { + _register_global_class_script(E.key, E.key, E.value.type, E.value.script_class_name, E.value.script_class_extends, E.value.script_class_icon_path); + if (ep) { + ep->step(E.value.script_class_name, step_count++, false); + } } - } - memdelete_notnull(ep); + memdelete_notnull(ep); - update_script_paths.clear(); - update_script_mutex.unlock(); + update_script_paths.clear(); + } ScriptServer::save_global_classes(); EditorNode::get_editor_data().script_class_save_icon_paths(); @@ -1894,7 +1906,7 @@ void EditorFileSystem::_update_script_documentation() { return; } - update_script_mutex.lock(); + MutexLock update_script_lock(update_script_mutex); EditorProgress *ep = nullptr; if (update_script_paths_documentation.size() > 1) { @@ -1933,7 +1945,6 @@ void EditorFileSystem::_update_script_documentation() { memdelete_notnull(ep); update_script_paths_documentation.clear(); - update_script_mutex.unlock(); } void EditorFileSystem::_process_update_pending() { @@ -1945,7 +1956,7 @@ void EditorFileSystem::_process_update_pending() { } void EditorFileSystem::_queue_update_script_class(const String &p_path, const String &p_type, const String &p_script_class_name, const String &p_script_class_extends, const String &p_script_class_icon_path) { - update_script_mutex.lock(); + MutexLock update_script_lock(update_script_mutex); ScriptInfo si; si.type = p_type; @@ -1955,8 +1966,6 @@ void EditorFileSystem::_queue_update_script_class(const String &p_path, const St update_script_paths.insert(p_path, si); update_script_paths_documentation.insert(p_path); - - update_script_mutex.unlock(); } void EditorFileSystem::_update_scene_groups() { @@ -1970,31 +1979,32 @@ void EditorFileSystem::_update_scene_groups() { } int step_count = 0; - update_scene_mutex.lock(); - for (const String &path : update_scene_paths) { - ProjectSettings::get_singleton()->remove_scene_groups_cache(path); + { + MutexLock update_scene_lock(update_scene_mutex); + for (const String &path : update_scene_paths) { + ProjectSettings::get_singleton()->remove_scene_groups_cache(path); - int index = -1; - EditorFileSystemDirectory *efd = find_file(path, &index); + int index = -1; + EditorFileSystemDirectory *efd = find_file(path, &index); - if (!efd || index < 0) { - // The file was removed. - continue; - } + if (!efd || index < 0) { + // The file was removed. + continue; + } - const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path); - if (!scene_groups.is_empty()) { - ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups); - } + const HashSet<StringName> scene_groups = PackedScene::get_scene_groups(path); + if (!scene_groups.is_empty()) { + ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups); + } - if (ep) { - ep->step(efd->files[index]->file, step_count++, false); + if (ep) { + ep->step(efd->files[index]->file, step_count++, false); + } } - } - memdelete_notnull(ep); - update_scene_paths.clear(); - update_scene_mutex.unlock(); + memdelete_notnull(ep); + update_scene_paths.clear(); + } ProjectSettings::get_singleton()->save_scene_groups_cache(); } @@ -2009,9 +2019,8 @@ void EditorFileSystem::_update_pending_scene_groups() { } void EditorFileSystem::_queue_update_scene_groups(const String &p_path) { - update_scene_mutex.lock(); + MutexLock update_scene_lock(update_scene_mutex); update_scene_paths.insert(p_path); - update_scene_mutex.unlock(); } void EditorFileSystem::_get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list) { @@ -3016,57 +3025,7 @@ bool EditorFileSystem::_scan_extensions() { _scan_extensions_dir(d, extensions); - //verify against loaded extensions - - Vector<String> extensions_added; - Vector<String> extensions_removed; - - for (const String &E : extensions) { - if (!GDExtensionManager::get_singleton()->is_extension_loaded(E)) { - extensions_added.push_back(E); - } - } - - Vector<String> loaded_extensions = GDExtensionManager::get_singleton()->get_loaded_extensions(); - for (int i = 0; i < loaded_extensions.size(); i++) { - if (!extensions.has(loaded_extensions[i])) { - // The extension may not have a .gdextension file. - if (!FileAccess::exists(loaded_extensions[i])) { - extensions_removed.push_back(loaded_extensions[i]); - } - } - } - - String extension_list_config_file = GDExtension::get_extension_list_config_file(); - if (extensions.size()) { - if (extensions_added.size() || extensions_removed.size()) { //extensions were added or removed - Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE); - for (const String &E : extensions) { - f->store_line(E); - } - } - } else { - if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) { //extensions were removed - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - da->remove(extension_list_config_file); - } - } - - bool needs_restart = false; - for (int i = 0; i < extensions_added.size(); i++) { - GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extensions_added[i]); - if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { - needs_restart = true; - } - } - for (int i = 0; i < extensions_removed.size(); i++) { - GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extensions_removed[i]); - if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) { - needs_restart = true; - } - } - - return needs_restart; + return GDExtensionManager::get_singleton()->ensure_extensions_loaded(extensions); } void EditorFileSystem::_bind_methods() { diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h index 1bc24416eb..ca4a64bfac 100644 --- a/editor/editor_file_system.h +++ b/editor/editor_file_system.h @@ -187,7 +187,7 @@ class EditorFileSystem : public Node { void _scan_filesystem(); void _first_scan_filesystem(); - void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names); + void _first_scan_process_scripts(const ScannedDirectory *p_scan_dir, HashSet<String> &p_existing_class_names, HashSet<String> &p_extensions); HashSet<String> late_update_files; diff --git a/editor/editor_help_search.cpp b/editor/editor_help_search.cpp index ff5bc6ba87..eb97337b37 100644 --- a/editor/editor_help_search.cpp +++ b/editor/editor_help_search.cpp @@ -134,18 +134,38 @@ void EditorHelpSearch::_native_action_cb(const String &p_item_string) { } void EditorHelpSearch::_update_results() { - String term = search_box->get_text(); + const String term = search_box->get_text().strip_edges(); int search_flags = filter_combo->get_selected_id(); - if (case_sensitive_button->is_pressed()) { - search_flags |= SEARCH_CASE_SENSITIVE; - } - if (hierarchy_button->is_pressed()) { - search_flags |= SEARCH_SHOW_HIERARCHY; - } - search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags))); - set_process(true); + // Process separately if term is not short, or is "@" for annotations. + if (term.length() > 1 || term == "@") { + case_sensitive_button->set_disabled(false); + hierarchy_button->set_disabled(false); + + if (case_sensitive_button->is_pressed()) { + search_flags |= SEARCH_CASE_SENSITIVE; + } + if (hierarchy_button->is_pressed()) { + search_flags |= SEARCH_SHOW_HIERARCHY; + } + + search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags))); + + // Clear old search flags to force rebuild on short term. + old_search_flags = 0; + set_process(true); + } else { + // Disable hierarchy and case sensitive options, not used for short searches. + case_sensitive_button->set_disabled(true); + hierarchy_button->set_disabled(true); + + // Always show hierarchy for short searches. + search = Ref<Runner>(memnew(Runner(results_tree, results_tree, &tree_cache, term, search_flags | SEARCH_SHOW_HIERARCHY))); + + old_search_flags = search_flags; + set_process(true); + } } void EditorHelpSearch::_search_box_gui_input(const Ref<InputEvent> &p_event) { @@ -205,6 +225,8 @@ void EditorHelpSearch::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible()) { tree_cache.clear(); + results_tree->get_vscroll_bar()->set_value(0); + search = Ref<Runner>(); callable_mp(results_tree, &Tree::clear).call_deferred(); // Wait for the Tree's mouse event propagation. get_ok_button()->set_disabled(true); EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "search_help", Rect2(get_position(), get_size())); @@ -278,6 +300,7 @@ void EditorHelpSearch::popup_dialog(const String &p_term) { popup_centered_ratio(0.5F); } + old_search_flags = 0; if (p_term.is_empty()) { search_box->clear(); } else { @@ -401,6 +424,237 @@ bool EditorHelpSearch::Runner::_is_class_disabled_by_feature_profile(const Strin return false; } +bool EditorHelpSearch::Runner::_fill() { + bool phase_done = false; + switch (phase) { + case PHASE_MATCH_CLASSES_INIT: + phase_done = _phase_fill_classes_init(); + break; + case PHASE_MATCH_CLASSES: + phase_done = _phase_fill_classes(); + break; + case PHASE_CLASS_ITEMS_INIT: + case PHASE_CLASS_ITEMS: + phase_done = true; + break; + case PHASE_MEMBER_ITEMS_INIT: + phase_done = _phase_fill_member_items_init(); + break; + case PHASE_MEMBER_ITEMS: + phase_done = _phase_fill_member_items(); + break; + case PHASE_SELECT_MATCH: + phase_done = _phase_select_match(); + break; + case PHASE_MAX: + return true; + default: + WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search."); + return true; + } + + if (phase_done) { + phase++; + } + return false; +} + +bool EditorHelpSearch::Runner::_phase_fill_classes_init() { + // Initialize fill. + iterator_stack.clear(); + matched_classes.clear(); + matched_item = nullptr; + match_highest_score = 0; + + // Initialize stack of iterators to fill, in reverse. + iterator_stack.push_back(EditorHelp::get_doc_data()->inheriting[""].back()); + + return true; +} + +bool EditorHelpSearch::Runner::_phase_fill_classes() { + if (iterator_stack.is_empty()) { + return true; + } + + if (iterator_stack[iterator_stack.size() - 1]) { + DocData::ClassDoc *class_doc = EditorHelp::get_doc_data()->class_list.getptr(iterator_stack[iterator_stack.size() - 1]->get()); + + // Decrement stack. + iterator_stack[iterator_stack.size() - 1] = iterator_stack[iterator_stack.size() - 1]->prev(); + + // Drop last element of stack if empty. + if (!iterator_stack[iterator_stack.size() - 1]) { + iterator_stack.resize(iterator_stack.size() - 1); + } + + if (!class_doc || class_doc->name.is_empty()) { + return false; + } + + // If class matches the flags, add it to the matched stack. + const bool class_matched = + (search_flags & SEARCH_CLASSES) || + ((search_flags & SEARCH_CONSTRUCTORS) && !class_doc->constructors.is_empty()) || + ((search_flags & SEARCH_METHODS) && !class_doc->methods.is_empty()) || + ((search_flags & SEARCH_OPERATORS) && !class_doc->operators.is_empty()) || + ((search_flags & SEARCH_SIGNALS) && !class_doc->signals.is_empty()) || + ((search_flags & SEARCH_CONSTANTS) && !class_doc->constants.is_empty()) || + ((search_flags & SEARCH_PROPERTIES) && !class_doc->properties.is_empty()) || + ((search_flags & SEARCH_THEME_ITEMS) && !class_doc->theme_properties.is_empty()) || + ((search_flags & SEARCH_ANNOTATIONS) && !class_doc->annotations.is_empty()); + + if (class_matched) { + if (term.is_empty() || class_doc->name.containsn(term)) { + matched_classes.push_back(Pair<DocData::ClassDoc *, String>(class_doc, String())); + } else if (String keyword = _match_keywords(term, class_doc->keywords); !keyword.is_empty()) { + matched_classes.push_back(Pair<DocData::ClassDoc *, String>(class_doc, keyword)); + } + } + + // Add inheriting classes, in reverse. + if (class_doc && EditorHelp::get_doc_data()->inheriting.has(class_doc->name)) { + iterator_stack.push_back(EditorHelp::get_doc_data()->inheriting[class_doc->name].back()); + } + + return false; + } + + // Drop last element of stack if empty. + if (!iterator_stack[iterator_stack.size() - 1]) { + iterator_stack.resize(iterator_stack.size() - 1); + } + + return iterator_stack.is_empty(); +} + +bool EditorHelpSearch::Runner::_phase_fill_member_items_init() { + // Prepare tree. + class_items.clear(); + _populate_cache(); + + return true; +} + +TreeItem *EditorHelpSearch::Runner::_create_category_item(TreeItem *p_parent, const String &p_class, const StringName &p_icon, const String &p_metatype, const String &p_text) { + const String item_meta = "class_" + p_metatype + ":" + p_class; + + TreeItem *item = nullptr; + if (_find_or_create_item(p_parent, item_meta, item)) { + item->set_icon(0, ui_service->get_editor_theme_icon(p_icon)); + item->set_text(0, p_text); + item->set_metadata(0, item_meta); + } + item->set_collapsed(true); + + return item; +} + +bool EditorHelpSearch::Runner::_phase_fill_member_items() { + if (matched_classes.is_empty()) { + return true; + } + + // Pop working item from stack. + Pair<DocData::ClassDoc *, String> match = matched_classes[matched_classes.size() - 1]; + DocData::ClassDoc *class_doc = match.first; + const String &keyword = match.second; + matched_classes.resize(matched_classes.size() - 1); + + if (class_doc) { + TreeItem *item = _create_class_hierarchy(class_doc, keyword, !(search_flags & SEARCH_CLASSES)); + + // If the class has no inheriting classes, fold its item. + item->set_collapsed(!item->get_first_child()); + + if (search_flags & SEARCH_CLASSES) { + item->clear_custom_color(0); + item->clear_custom_color(1); + } else { + item->set_custom_color(0, disabled_color); + item->set_custom_color(1, disabled_color); + } + + // Create common header if required. + const bool search_all = (search_flags & SEARCH_ALL) == SEARCH_ALL; + + if ((search_flags & SEARCH_CONSTRUCTORS) && !class_doc->constructors.is_empty()) { + TreeItem *parent_item = item; + if (search_all) { + parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberConstructor"), TTRC("Constructors"), "constructors"); + } + for (const DocData::MethodDoc &constructor_doc : class_doc->constructors) { + _create_constructor_item(parent_item, class_doc, &constructor_doc); + } + } + if ((search_flags & SEARCH_METHODS) && !class_doc->methods.is_empty()) { + TreeItem *parent_item = item; + if (search_all) { + parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberMethod"), TTRC("Methods"), "methods"); + } + for (const DocData::MethodDoc &method_doc : class_doc->methods) { + _create_method_item(parent_item, class_doc, &method_doc); + } + } + if ((search_flags & SEARCH_OPERATORS) && !class_doc->operators.is_empty()) { + TreeItem *parent_item = item; + if (search_all) { + parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberOperator"), TTRC("Operators"), "operators"); + } + for (const DocData::MethodDoc &operator_doc : class_doc->operators) { + _create_operator_item(parent_item, class_doc, &operator_doc); + } + } + if ((search_flags & SEARCH_SIGNALS) && !class_doc->signals.is_empty()) { + TreeItem *parent_item = item; + if (search_all) { + parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberSignal"), TTRC("Signals"), "signals"); + } + for (const DocData::MethodDoc &signal_doc : class_doc->signals) { + _create_signal_item(parent_item, class_doc, &signal_doc); + } + } + if ((search_flags & SEARCH_CONSTANTS) && !class_doc->constants.is_empty()) { + TreeItem *parent_item = item; + if (search_all) { + parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberConstant"), TTRC("Constants"), "constants"); + } + for (const DocData::ConstantDoc &constant_doc : class_doc->constants) { + _create_constant_item(parent_item, class_doc, &constant_doc); + } + } + if ((search_flags & SEARCH_PROPERTIES) && !class_doc->properties.is_empty()) { + TreeItem *parent_item = item; + if (search_all) { + parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberProperty"), TTRC("Prtoperties"), "propertiess"); + } + for (const DocData::PropertyDoc &property_doc : class_doc->properties) { + _create_property_item(parent_item, class_doc, &property_doc); + } + } + if ((search_flags & SEARCH_THEME_ITEMS) && !class_doc->theme_properties.is_empty()) { + TreeItem *parent_item = item; + if (search_all) { + parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberTheme"), TTRC("Theme Properties"), "theme_items"); + } + for (const DocData::ThemeItemDoc &theme_property_doc : class_doc->theme_properties) { + _create_theme_property_item(parent_item, class_doc, &theme_property_doc); + } + } + if ((search_flags & SEARCH_ANNOTATIONS) && !class_doc->annotations.is_empty()) { + TreeItem *parent_item = item; + if (search_all) { + parent_item = _create_category_item(parent_item, class_doc->name, SNAME("MemberAnnotation"), TTRC("Annotations"), "annotations"); + } + for (const DocData::MethodDoc &annotation_doc : class_doc->annotations) { + _create_annotation_item(parent_item, class_doc, &annotation_doc); + } + } + } + + return matched_classes.is_empty(); +} + bool EditorHelpSearch::Runner::_slice() { bool phase_done = false; switch (phase) { @@ -430,7 +684,7 @@ bool EditorHelpSearch::Runner::_slice() { default: WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search."); return true; - }; + } if (phase_done) { phase++; @@ -450,9 +704,11 @@ bool EditorHelpSearch::Runner::_phase_match_classes_init() { matched_item = nullptr; match_highest_score = 0; - terms = term.split_spaces(); - if (terms.is_empty()) { - terms.append(term); + if (!term.is_empty()) { + terms = term.split_spaces(); + if (terms.is_empty()) { + terms.append(term); + } } return true; @@ -480,78 +736,71 @@ bool EditorHelpSearch::Runner::_phase_match_classes() { // Match class name. if (search_flags & SEARCH_CLASSES) { - // If the search term is empty, add any classes which are not script docs or which don't start with - // a double-quotation. This will ensure that only C++ classes and explicitly named classes will - // be added. - match.name = (term.is_empty() && (!class_doc->is_script_doc || class_doc->name[0] != '\"')) || _match_string(term, class_doc->name); + match.name = _match_string(term, class_doc->name); match.keyword = _match_keywords(term, class_doc->keywords); } - // Match members only if the term is long enough, to avoid slow performance from building a large tree. - // Make an exception for annotations, since there are not that many of them. - if (term.length() > 1 || term == "@") { - if (search_flags & SEARCH_CONSTRUCTORS) { - _match_method_name_and_push_back(class_doc->constructors, &match.constructors); - } - if (search_flags & SEARCH_METHODS) { - _match_method_name_and_push_back(class_doc->methods, &match.methods); - } - if (search_flags & SEARCH_OPERATORS) { - _match_method_name_and_push_back(class_doc->operators, &match.operators); - } - if (search_flags & SEARCH_SIGNALS) { - for (int i = 0; i < class_doc->signals.size(); i++) { - MemberMatch<DocData::MethodDoc> signal; - signal.name = _all_terms_in_name(class_doc->signals[i].name); - signal.keyword = _match_keywords_in_all_terms(class_doc->signals[i].keywords); - if (signal.name || !signal.keyword.is_empty()) { - signal.doc = const_cast<DocData::MethodDoc *>(&class_doc->signals[i]); - match.signals.push_back(signal); - } + if (search_flags & SEARCH_CONSTRUCTORS) { + _match_method_name_and_push_back(class_doc->constructors, &match.constructors); + } + if (search_flags & SEARCH_METHODS) { + _match_method_name_and_push_back(class_doc->methods, &match.methods); + } + if (search_flags & SEARCH_OPERATORS) { + _match_method_name_and_push_back(class_doc->operators, &match.operators); + } + if (search_flags & SEARCH_SIGNALS) { + for (const DocData::MethodDoc &signal_doc : class_doc->signals) { + MemberMatch<DocData::MethodDoc> signal; + signal.name = _all_terms_in_name(signal_doc.name); + signal.keyword = _match_keywords_in_all_terms(signal_doc.keywords); + if (signal.name || !signal.keyword.is_empty()) { + signal.doc = &signal_doc; + match.signals.push_back(signal); } } - if (search_flags & SEARCH_CONSTANTS) { - for (int i = 0; i < class_doc->constants.size(); i++) { - MemberMatch<DocData::ConstantDoc> constant; - constant.name = _all_terms_in_name(class_doc->constants[i].name); - constant.keyword = _match_keywords_in_all_terms(class_doc->constants[i].keywords); - if (constant.name || !constant.keyword.is_empty()) { - constant.doc = const_cast<DocData::ConstantDoc *>(&class_doc->constants[i]); - match.constants.push_back(constant); - } + } + if (search_flags & SEARCH_CONSTANTS) { + for (const DocData::ConstantDoc &constant_doc : class_doc->constants) { + MemberMatch<DocData::ConstantDoc> constant; + constant.name = _all_terms_in_name(constant_doc.name); + constant.keyword = _match_keywords_in_all_terms(constant_doc.keywords); + if (constant.name || !constant.keyword.is_empty()) { + constant.doc = &constant_doc; + match.constants.push_back(constant); } } - if (search_flags & SEARCH_PROPERTIES) { - for (int i = 0; i < class_doc->properties.size(); i++) { - MemberMatch<DocData::PropertyDoc> property; - property.name = _all_terms_in_name(class_doc->properties[i].name); - property.keyword = _match_keywords_in_all_terms(class_doc->properties[i].keywords); - if (property.name || !property.keyword.is_empty()) { - property.doc = const_cast<DocData::PropertyDoc *>(&class_doc->properties[i]); - match.properties.push_back(property); - } + } + if (search_flags & SEARCH_PROPERTIES) { + for (const DocData::PropertyDoc &property_doc : class_doc->properties) { + MemberMatch<DocData::PropertyDoc> property; + property.name = _all_terms_in_name(property_doc.name); + property.keyword = _match_keywords_in_all_terms(property_doc.keywords); + if (property.name || !property.keyword.is_empty()) { + property.doc = &property_doc; + match.properties.push_back(property); } } - if (search_flags & SEARCH_THEME_ITEMS) { - for (int i = 0; i < class_doc->theme_properties.size(); i++) { - MemberMatch<DocData::ThemeItemDoc> theme_property; - theme_property.name = _all_terms_in_name(class_doc->theme_properties[i].name); - theme_property.keyword = _match_keywords_in_all_terms(class_doc->theme_properties[i].keywords); - if (theme_property.name || !theme_property.keyword.is_empty()) { - theme_property.doc = const_cast<DocData::ThemeItemDoc *>(&class_doc->theme_properties[i]); - match.theme_properties.push_back(theme_property); - } + } + if (search_flags & SEARCH_THEME_ITEMS) { + for (const DocData::ThemeItemDoc &theme_property_doc : class_doc->theme_properties) { + MemberMatch<DocData::ThemeItemDoc> theme_property; + theme_property.name = _all_terms_in_name(theme_property_doc.name); + theme_property.keyword = _match_keywords_in_all_terms(theme_property_doc.keywords); + if (theme_property.name || !theme_property.keyword.is_empty()) { + theme_property.doc = &theme_property_doc; + match.theme_properties.push_back(theme_property); } } - if (search_flags & SEARCH_ANNOTATIONS) { - for (int i = 0; i < class_doc->annotations.size(); i++) { - MemberMatch<DocData::MethodDoc> annotation; - annotation.name = _all_terms_in_name(class_doc->annotations[i].name); - annotation.keyword = _match_keywords_in_all_terms(class_doc->annotations[i].keywords); - if (annotation.name || !annotation.keyword.is_empty()) { - annotation.doc = const_cast<DocData::MethodDoc *>(&class_doc->annotations[i]); - match.annotations.push_back(annotation); - } + } + if (search_flags & SEARCH_ANNOTATIONS) { + for (const DocData::MethodDoc &annotation_doc : class_doc->annotations) { + MemberMatch<DocData::MethodDoc> annotation; + annotation.name = _all_terms_in_name(annotation_doc.name); + annotation.keyword = _match_keywords_in_all_terms(annotation_doc.keywords); + if (annotation.name || !annotation.keyword.is_empty()) { + annotation.doc = &annotation_doc; + match.annotations.push_back(annotation); } } } @@ -564,9 +813,11 @@ bool EditorHelpSearch::Runner::_phase_match_classes() { } if (!iterator_stack.is_empty()) { + // Iterate on stack. if (iterator_stack[iterator_stack.size() - 1]) { iterator_stack[iterator_stack.size() - 1] = iterator_stack[iterator_stack.size() - 1]->next(); } + // Drop last element of stack. if (!iterator_stack[iterator_stack.size() - 1]) { iterator_stack.resize(iterator_stack.size() - 1); } @@ -661,36 +912,32 @@ bool EditorHelpSearch::Runner::_phase_member_items() { return false; } + // Pick appropriate parent item if showing hierarchy, otherwise pick root. TreeItem *parent_item = (search_flags & SEARCH_SHOW_HIERARCHY) ? class_items[match.doc->name] : root_item; - bool constructor_created = false; - for (int i = 0; i < match.methods.size(); i++) { - String text = match.methods[i].doc->name; - if (!constructor_created) { - if (match.doc->name == match.methods[i].doc->name) { - text += " " + TTR("(constructors)"); - constructor_created = true; - } - } else { - if (match.doc->name == match.methods[i].doc->name) { - continue; - } - } - _create_method_item(parent_item, match.doc, text, match.methods[i]); + + for (const MemberMatch<DocData::MethodDoc> &constructor_item : match.constructors) { + _create_constructor_item(parent_item, match.doc, constructor_item); } - for (int i = 0; i < match.signals.size(); i++) { - _create_signal_item(parent_item, match.doc, match.signals[i]); + for (const MemberMatch<DocData::MethodDoc> &method_item : match.methods) { + _create_method_item(parent_item, match.doc, method_item); } - for (int i = 0; i < match.constants.size(); i++) { - _create_constant_item(parent_item, match.doc, match.constants[i]); + for (const MemberMatch<DocData::MethodDoc> &operator_item : match.operators) { + _create_operator_item(parent_item, match.doc, operator_item); } - for (int i = 0; i < match.properties.size(); i++) { - _create_property_item(parent_item, match.doc, match.properties[i]); + for (const MemberMatch<DocData::MethodDoc> &signal_item : match.signals) { + _create_signal_item(parent_item, match.doc, signal_item); } - for (int i = 0; i < match.theme_properties.size(); i++) { - _create_theme_property_item(parent_item, match.doc, match.theme_properties[i]); + for (const MemberMatch<DocData::ConstantDoc> &constant_item : match.constants) { + _create_constant_item(parent_item, match.doc, constant_item); } - for (int i = 0; i < match.annotations.size(); i++) { - _create_annotation_item(parent_item, match.doc, match.annotations[i]); + for (const MemberMatch<DocData::PropertyDoc> &property_item : match.properties) { + _create_property_item(parent_item, match.doc, property_item); + } + for (const MemberMatch<DocData::ThemeItemDoc> &theme_property_item : match.theme_properties) { + _create_theme_property_item(parent_item, match.doc, theme_property_item); + } + for (const MemberMatch<DocData::MethodDoc> &annotation_item : match.annotations) { + _create_annotation_item(parent_item, match.doc, annotation_item); } ++iterator_match; @@ -704,7 +951,7 @@ bool EditorHelpSearch::Runner::_phase_select_match() { return true; } -void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods) { +void EditorHelpSearch::Runner::_match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, LocalVector<MemberMatch<DocData::MethodDoc>> *r_match_methods) { // Constructors, Methods, Operators... for (int i = 0; i < p_methods.size(); i++) { String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? p_methods[i].name : p_methods[i].name.to_lower(); @@ -765,12 +1012,12 @@ void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_tex return; } - float inverse_length = 1.f / float(p_text.length()); + float inverse_length = 1.0f / float(p_text.length()); // Favor types where search term is a substring close to the start of the type. float w = 0.5f; int pos = p_text.findn(term); - float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w); + float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.0f, 0.9f - w); // Favor shorter items: they resemble the search term more. w = 0.1f; @@ -781,7 +1028,8 @@ void EditorHelpSearch::Runner::_match_item(TreeItem *p_item, const String &p_tex score *= 0.9f; } - if (match_highest_score == 0 || score > match_highest_score) { + // Replace current match if term is short as we are searching in reverse. + if (match_highest_score == 0 || score > match_highest_score || (score == match_highest_score && term.length() == 1)) { matched_item = p_item; match_highest_score = score; } @@ -820,6 +1068,29 @@ String EditorHelpSearch::Runner::_build_keywords_tooltip(const String &p_keyword return tooltip.left(-2); } +TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const DocData::ClassDoc *p_class_doc, const String &p_matching_keyword, bool p_gray) { + if (p_class_doc->name.is_empty()) { + return nullptr; + } + if (TreeItem **found = class_items.getptr(p_class_doc->name)) { + return *found; + } + + // Ensure parent nodes are created first. + TreeItem *parent_item = root_item; + if (!p_class_doc->inherits.is_empty()) { + if (class_items.has(p_class_doc->inherits)) { + parent_item = class_items[p_class_doc->inherits]; + } else if (const DocData::ClassDoc *found = EditorHelp::get_doc_data()->class_list.getptr(p_class_doc->inherits)) { + parent_item = _create_class_hierarchy(found, String(), true); + } + } + + TreeItem *class_item = _create_class_item(parent_item, p_class_doc, p_gray, p_matching_keyword); + class_items[p_class_doc->name] = class_item; + return class_item; +} + TreeItem *EditorHelpSearch::Runner::_create_class_hierarchy(const ClassMatch &p_match) { if (p_match.doc->name.is_empty()) { return nullptr; @@ -887,6 +1158,8 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const item->add_button(0, warning_icon, 0, false, TTR("This class is marked as experimental.")); } } + // Cached item might be collapsed. + item->set_collapsed(false); if (p_gray) { item->set_custom_color(0, disabled_color); @@ -902,7 +1175,9 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const item->set_text(0, p_doc->name + " - " + TTR(vformat("Matches the \"%s\" keyword.", p_matching_keyword))); } - _match_item(item, p_doc->name); + if (!term.is_empty()) { + _match_item(item, p_doc->name); + } for (const String &keyword : p_doc->keywords.split(",")) { _match_item(item, keyword.strip_edges(), true); } @@ -910,44 +1185,73 @@ TreeItem *EditorHelpSearch::Runner::_create_class_item(TreeItem *p_parent, const return item; } -TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match) { +TreeItem *EditorHelpSearch::Runner::_create_constructor_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) { + String tooltip = p_class_doc->name + "("; + String text = p_class_doc->name + "("; + for (int i = 0; i < p_match.doc->arguments.size(); i++) { + const DocData::ArgumentDoc &arg = p_match.doc->arguments[i]; + tooltip += arg.type + " " + arg.name; + text += arg.type; + if (!arg.default_value.is_empty()) { + tooltip += " = " + arg.default_value; + } + if (i < p_match.doc->arguments.size() - 1) { + tooltip += ", "; + text += ", "; + } + } + tooltip += ")"; + tooltip += _build_keywords_tooltip(p_match.doc->keywords); + text += ")"; + return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberConstructor"), p_match.doc->name, text, TTRC("Constructor"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); +} + +TreeItem *EditorHelpSearch::Runner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) { String tooltip = _build_method_tooltip(p_class_doc, p_match.doc); - return _create_member_item(p_parent, p_class_doc->name, "MemberMethod", p_match.doc->name, p_text, TTRC("Method"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); + return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberMethod"), p_match.doc->name, p_match.doc->name, TTRC("Method"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); +} + +TreeItem *EditorHelpSearch::Runner::_create_operator_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) { + String tooltip = _build_method_tooltip(p_class_doc, p_match.doc); + String text = p_match.doc->name; + if (!p_match.doc->arguments.is_empty()) { + text += "(" + p_match.doc->arguments[0].type + ")"; + } + return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberOperator"), p_match.doc->name, text, TTRC("Operator"), "method", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } TreeItem *EditorHelpSearch::Runner::_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) { String tooltip = _build_method_tooltip(p_class_doc, p_match.doc); - return _create_member_item(p_parent, p_class_doc->name, "MemberSignal", p_match.doc->name, p_match.doc->name, TTRC("Signal"), "signal", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); + return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberSignal"), p_match.doc->name, p_match.doc->name, TTRC("Signal"), "signal", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } TreeItem *EditorHelpSearch::Runner::_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match) { String tooltip = _build_method_tooltip(p_class_doc, p_match.doc); // Hide the redundant leading @ symbol. String text = p_match.doc->name.substr(1); - return _create_member_item(p_parent, p_class_doc->name, "MemberAnnotation", p_match.doc->name, text, TTRC("Annotation"), "annotation", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); + return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberAnnotation"), p_match.doc->name, text, TTRC("Annotation"), "annotation", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } TreeItem *EditorHelpSearch::Runner::_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match) { String tooltip = 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, "MemberConstant", p_match.doc->name, p_match.doc->name, TTRC("Constant"), "constant", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); + return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberConstant"), p_match.doc->name, p_match.doc->name, TTRC("Constant"), "constant", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } TreeItem *EditorHelpSearch::Runner::_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match) { String tooltip = p_match.doc->type + " " + p_class_doc->name + "." + p_match.doc->name; tooltip += "\n " + p_class_doc->name + "." + p_match.doc->setter + "(value) setter"; tooltip += "\n " + p_class_doc->name + "." + p_match.doc->getter + "() getter"; - tooltip += _build_keywords_tooltip(p_match.doc->keywords); - return _create_member_item(p_parent, p_class_doc->name, "MemberProperty", p_match.doc->name, p_match.doc->name, TTRC("Property"), "property", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); + return _create_member_item(p_parent, p_class_doc->name, SNAME("MemberProperty"), p_match.doc->name, p_match.doc->name, TTRC("Property"), "property", tooltip, p_match.doc->keywords, p_match.doc->is_deprecated, p_match.doc->is_experimental, p_match.name ? String() : p_match.keyword); } 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, "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, false, false, p_match.name ? String() : p_match.keyword); } -TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &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) { +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) { const String item_meta = "class_" + p_metatype + ":" + p_class_name + ":" + p_name; TreeItem *item = nullptr; @@ -978,7 +1282,10 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons } item->set_text(0, text); - _match_item(item, p_name); + // Don't match member items for short searches. + if (term.length() > 1 || term == "@") { + _match_item(item, p_name); + } for (const String &keyword : p_keywords.split(",")) { _match_item(item, keyword.strip_edges(), true); } @@ -989,9 +1296,17 @@ TreeItem *EditorHelpSearch::Runner::_create_member_item(TreeItem *p_parent, cons bool EditorHelpSearch::Runner::work(uint64_t slot) { // Return true when the search has been completed, otherwise false. const uint64_t until = OS::get_singleton()->get_ticks_usec() + slot; - while (!_slice()) { - if (OS::get_singleton()->get_ticks_usec() > until) { - return false; + if (term.length() > 1 || term == "@") { + while (!_slice()) { + if (OS::get_singleton()->get_ticks_usec() > until) { + return false; + } + } + } else { + while (!_fill()) { + if (OS::get_singleton()->get_ticks_usec() > until) { + return false; + } } } return true; @@ -1001,7 +1316,7 @@ EditorHelpSearch::Runner::Runner(Control *p_icon_service, Tree *p_results_tree, ui_service(p_icon_service), results_tree(p_results_tree), tree_cache(p_tree_cache), - term((p_search_flags & SEARCH_CASE_SENSITIVE) == 0 ? p_term.strip_edges().to_lower() : p_term.strip_edges()), + term((p_search_flags & SEARCH_CASE_SENSITIVE) == 0 ? p_term.to_lower() : p_term), search_flags(p_search_flags), disabled_color(ui_service->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor))) { } diff --git a/editor/editor_help_search.h b/editor/editor_help_search.h index 58061dae4c..b8b3c26b41 100644 --- a/editor/editor_help_search.h +++ b/editor/editor_help_search.h @@ -63,6 +63,7 @@ class EditorHelpSearch : public ConfirmationDialog { Tree *results_tree = nullptr; bool old_search = false; String old_term; + int old_search_flags = 0; class Runner; Ref<Runner> search; @@ -119,26 +120,30 @@ class EditorHelpSearch::Runner : public RefCounted { template <typename T> struct MemberMatch { - T *doc = nullptr; + const T *doc = nullptr; bool name = false; String keyword; + + MemberMatch() {} + MemberMatch(const T *p_doc) : + doc(p_doc) {} }; struct ClassMatch { - DocData::ClassDoc *doc = nullptr; + const DocData::ClassDoc *doc = nullptr; bool name = false; String keyword; - Vector<MemberMatch<DocData::MethodDoc>> constructors; - Vector<MemberMatch<DocData::MethodDoc>> methods; - Vector<MemberMatch<DocData::MethodDoc>> operators; - Vector<MemberMatch<DocData::MethodDoc>> signals; - Vector<MemberMatch<DocData::ConstantDoc>> constants; - Vector<MemberMatch<DocData::PropertyDoc>> properties; - Vector<MemberMatch<DocData::ThemeItemDoc>> theme_properties; - Vector<MemberMatch<DocData::MethodDoc>> annotations; + LocalVector<MemberMatch<DocData::MethodDoc>> constructors; + LocalVector<MemberMatch<DocData::MethodDoc>> methods; + LocalVector<MemberMatch<DocData::MethodDoc>> operators; + LocalVector<MemberMatch<DocData::MethodDoc>> signals; + LocalVector<MemberMatch<DocData::ConstantDoc>> constants; + LocalVector<MemberMatch<DocData::PropertyDoc>> properties; + LocalVector<MemberMatch<DocData::ThemeItemDoc>> theme_properties; + LocalVector<MemberMatch<DocData::MethodDoc>> annotations; bool required() { - return name || !keyword.is_empty() || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size() || annotations.size(); + return name || !keyword.is_empty() || !constructors.is_empty() || !methods.is_empty() || !operators.is_empty() || !signals.is_empty() || !constants.is_empty() || !properties.is_empty() || !theme_properties.is_empty() || !annotations.is_empty(); } }; @@ -155,6 +160,7 @@ class EditorHelpSearch::Runner : public RefCounted { LocalVector<RBSet<String, NaturalNoCaseComparator>::Element *> iterator_stack; HashMap<String, ClassMatch> matches; HashMap<String, ClassMatch>::Iterator iterator_match; + LocalVector<Pair<DocData::ClassDoc *, String>> matched_classes; TreeItem *root_item = nullptr; HashMap<String, TreeItem *> class_items; TreeItem *matched_item = nullptr; @@ -165,6 +171,12 @@ class EditorHelpSearch::Runner : public RefCounted { void _populate_cache(); bool _find_or_create_item(TreeItem *p_parent, const String &p_item_meta, TreeItem *&r_item); + bool _fill(); + bool _phase_fill_classes_init(); + bool _phase_fill_classes(); + bool _phase_fill_member_items_init(); + bool _phase_fill_member_items(); + bool _slice(); bool _phase_match_classes_init(); bool _phase_match_classes(); @@ -177,21 +189,25 @@ class EditorHelpSearch::Runner : public RefCounted { String _build_method_tooltip(const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) const; String _build_keywords_tooltip(const String &p_keywords) const; - void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, Vector<MemberMatch<DocData::MethodDoc>> *r_match_methods); + void _match_method_name_and_push_back(Vector<DocData::MethodDoc> &p_methods, LocalVector<MemberMatch<DocData::MethodDoc>> *r_match_methods); bool _all_terms_in_name(const String &p_name) const; String _match_keywords_in_all_terms(const String &p_keywords) const; bool _match_string(const String &p_term, const String &p_string) const; String _match_keywords(const String &p_term, const String &p_keywords) const; void _match_item(TreeItem *p_item, const String &p_text, bool p_is_keywords = false); TreeItem *_create_class_hierarchy(const ClassMatch &p_match); + TreeItem *_create_class_hierarchy(const DocData::ClassDoc *p_class_doc, const String &p_matching_keyword, bool p_gray); TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray, const String &p_matching_keyword); - TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const MemberMatch<DocData::MethodDoc> &p_match); + TreeItem *_create_category_item(TreeItem *p_parent, const String &p_class, const StringName &p_icon, const String &p_metatype, const String &p_type); + TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match); + TreeItem *_create_constructor_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match); + TreeItem *_create_operator_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match); TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match); TreeItem *_create_annotation_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::MethodDoc> &p_match); TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ConstantDoc> &p_match); TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::PropertyDoc> &p_match); TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const MemberMatch<DocData::ThemeItemDoc> &p_match); - TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &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); + TreeItem *_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); public: bool work(uint64_t slot = 100000); diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp index 46113ab2cb..e699b486ee 100644 --- a/editor/editor_interface.cpp +++ b/editor/editor_interface.cpp @@ -86,6 +86,10 @@ Ref<EditorSettings> EditorInterface::get_editor_settings() const { return EditorSettings::get_singleton(); } +EditorUndoRedoManager *EditorInterface::get_editor_undo_redo() const { + return EditorUndoRedoManager::get_singleton(); +} + TypedArray<Texture2D> EditorInterface::_make_mesh_previews(const TypedArray<Mesh> &p_meshes, int p_preview_size) { Vector<Ref<Mesh>> meshes; @@ -525,6 +529,7 @@ void EditorInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_resource_previewer"), &EditorInterface::get_resource_previewer); ClassDB::bind_method(D_METHOD("get_selection"), &EditorInterface::get_selection); ClassDB::bind_method(D_METHOD("get_editor_settings"), &EditorInterface::get_editor_settings); + ClassDB::bind_method(D_METHOD("get_editor_undo_redo"), &EditorInterface::get_editor_undo_redo); ClassDB::bind_method(D_METHOD("make_mesh_previews", "meshes", "preview_size"), &EditorInterface::_make_mesh_previews); diff --git a/editor/editor_interface.h b/editor/editor_interface.h index 3ef4325780..2538f97c77 100644 --- a/editor/editor_interface.h +++ b/editor/editor_interface.h @@ -45,6 +45,7 @@ class EditorPlugin; class EditorResourcePreview; class EditorSelection; class EditorSettings; +class EditorUndoRedoManager; class FileSystemDock; class Mesh; class Node; @@ -93,6 +94,7 @@ public: EditorResourcePreview *get_resource_previewer() const; EditorSelection *get_selection() const; Ref<EditorSettings> get_editor_settings() const; + EditorUndoRedoManager *get_editor_undo_redo() const; Vector<Ref<Texture2D>> make_mesh_previews(const Vector<Ref<Mesh>> &p_meshes, Vector<Transform3D> *p_transforms, int p_preview_size); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index c6144a34cb..d3fa2ed716 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -481,6 +481,9 @@ void EditorNode::_gdextensions_reloaded() { // In case the developer is inspecting an object that will be changed by the reload. InspectorDock::get_inspector_singleton()->update_tree(); + // Reload script editor to revalidate GDScript if classes are added or removed. + ScriptEditor::get_singleton()->reload_scripts(true); + // Regenerate documentation. EditorHelp::generate_doc(); } @@ -2961,7 +2964,7 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { ERR_PRINT("Failed to load scene"); } editor_data.move_edited_scene_to_index(cur_idx); - EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id()); + EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false); scene_tabs->set_current_tab(cur_idx); } break; @@ -3963,7 +3966,7 @@ void EditorNode::_set_current_scene_nocheck(int p_idx) { editor_folding.load_scene_folding(editor_data.get_edited_scene_root(p_idx), editor_data.get_scene_path(p_idx)); } - EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(p_idx)); + EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(p_idx), false); } Dictionary state = editor_data.restore_edited_scene_state(editor_selection, &editor_history); @@ -4073,7 +4076,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b _set_current_scene(idx); } } else { - EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_current_edited_scene_history_id()); + EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_current_edited_scene_history_id(), false); } dependency_errors.clear(); @@ -4949,7 +4952,9 @@ bool EditorNode::is_object_of_custom_type(const Object *p_object, const StringNa } void EditorNode::progress_add_task(const String &p_task, const String &p_label, int p_steps, bool p_can_cancel) { - if (singleton->cmdline_export_mode) { + if (!singleton) { + return; + } else if (singleton->cmdline_export_mode) { print_line(p_task + ": begin: " + p_label + " steps: " + itos(p_steps)); } else if (singleton->progress_dialog) { singleton->progress_dialog->add_task(p_task, p_label, p_steps, p_can_cancel); @@ -4957,7 +4962,9 @@ void EditorNode::progress_add_task(const String &p_task, const String &p_label, } bool EditorNode::progress_task_step(const String &p_task, const String &p_state, int p_step, bool p_force_refresh) { - if (singleton->cmdline_export_mode) { + if (!singleton) { + return false; + } else if (singleton->cmdline_export_mode) { print_line("\t" + p_task + ": step " + itos(p_step) + ": " + p_state); return false; } else if (singleton->progress_dialog) { @@ -4968,7 +4975,9 @@ bool EditorNode::progress_task_step(const String &p_task, const String &p_state, } void EditorNode::progress_end_task(const String &p_task) { - if (singleton->cmdline_export_mode) { + if (!singleton) { + return; + } else if (singleton->cmdline_export_mode) { print_line(p_task + ": end"); } else if (singleton->progress_dialog) { singleton->progress_dialog->end_task(p_task); @@ -5928,7 +5937,7 @@ void EditorNode::reload_scene(const String &p_path) { bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id); // Scene is not open, so at it might be instantiated. We'll refresh the whole scene later. - EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id); + EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false); if (is_unsaved) { EditorUndoRedoManager::get_singleton()->set_history_as_unsaved(current_history_id); } @@ -5947,7 +5956,7 @@ void EditorNode::reload_scene(const String &p_path) { // Adjust index so tab is back a the previous position. editor_data.move_edited_scene_to_index(scene_idx); - EditorUndoRedoManager::get_singleton()->clear_history(false, editor_data.get_scene_history_id(scene_idx)); + EditorUndoRedoManager::get_singleton()->clear_history(editor_data.get_scene_history_id(scene_idx), false); // Recover the tab. scene_tabs->set_current_tab(current_tab); @@ -6092,7 +6101,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes() { bool is_unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(current_history_id); // Clear the history for this affected tab. - EditorUndoRedoManager::get_singleton()->clear_history(false, current_history_id); + EditorUndoRedoManager::get_singleton()->clear_history(current_history_id, false); // Update the version editor_data.is_scene_changed(current_scene_idx); @@ -7358,11 +7367,9 @@ EditorNode::EditorNode() { settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the user data folder (\"user://\").")); -#ifndef ANDROID_ENABLED ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11); ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::META | KeyModifierMask::CTRL | Key::F); settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN); -#endif settings_menu->add_separator(); #ifndef ANDROID_ENABLED diff --git a/editor/editor_properties_vector.cpp b/editor/editor_properties_vector.cpp index a40055cf85..9ff8bf674d 100644 --- a/editor/editor_properties_vector.cpp +++ b/editor/editor_properties_vector.cpp @@ -130,9 +130,11 @@ void EditorPropertyVectorN::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { if (linked->is_visible()) { - const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property()); - linked->set_pressed_no_signal(EditorSettings::get_singleton()->get_project_metadata("linked_properties", key, true)); - _update_ratio(); + if (get_edited_object()) { + const String key = vformat("%s:%s", get_edited_object()->get_class(), get_edited_property()); + linked->set_pressed_no_signal(EditorSettings::get_singleton()->get_project_metadata("linked_properties", key, true)); + _update_ratio(); + } } } break; 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_run_native.cpp b/editor/editor_run_native.cpp index 5d378820ae..e0e1ef6d19 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -141,7 +141,7 @@ Error EditorRunNative::start_run_native(int p_id) { emit_signal(SNAME("native_run"), preset); - int flags = 0; + BitField<EditorExportPlatform::DebugFlags> flags = 0; bool deploy_debug_remote = is_deploy_debug_remote_enabled(); bool deploy_dumb = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_file_server", false); @@ -149,16 +149,16 @@ Error EditorRunNative::start_run_native(int p_id) { bool debug_navigation = EditorSettings::get_singleton()->get_project_metadata("debug_options", "run_debug_navigation", false); if (deploy_debug_remote) { - flags |= EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG; + flags.set_flag(EditorExportPlatform::DEBUG_FLAG_REMOTE_DEBUG); } if (deploy_dumb) { - flags |= EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT; + flags.set_flag(EditorExportPlatform::DEBUG_FLAG_DUMB_CLIENT); } if (debug_collisions) { - flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS; + flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_COLLISIONS); } if (debug_navigation) { - flags |= EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION; + flags.set_flag(EditorExportPlatform::DEBUG_FLAG_VIEW_NAVIGATION); } eep->clear_messages(); diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp index 55bc198dfb..c0bf216634 100644 --- a/editor/editor_undo_redo_manager.cpp +++ b/editor/editor_undo_redo_manager.cpp @@ -390,7 +390,7 @@ bool EditorUndoRedoManager::has_history(int p_idx) const { return history_map.has(p_idx); } -void EditorUndoRedoManager::clear_history(bool p_increase_version, int p_idx) { +void EditorUndoRedoManager::clear_history(int p_idx, bool p_increase_version) { if (p_idx != INVALID_HISTORY) { History &history = get_or_create_history(p_idx); history.undo_redo->clear_history(p_increase_version); @@ -507,6 +507,7 @@ void EditorUndoRedoManager::_bind_methods() { ClassDB::bind_method(D_METHOD("get_object_history_id", "object"), &EditorUndoRedoManager::get_history_id_for_object); ClassDB::bind_method(D_METHOD("get_history_undo_redo", "id"), &EditorUndoRedoManager::get_history_undo_redo); + ClassDB::bind_method(D_METHOD("clear_history", "id", "increase_version"), &EditorUndoRedoManager::clear_history, DEFVAL(INVALID_HISTORY), DEFVAL(true)); ADD_SIGNAL(MethodInfo("history_changed")); ADD_SIGNAL(MethodInfo("version_changed")); diff --git a/editor/editor_undo_redo_manager.h b/editor/editor_undo_redo_manager.h index 219d5e0702..54475c3c6c 100644 --- a/editor/editor_undo_redo_manager.h +++ b/editor/editor_undo_redo_manager.h @@ -125,7 +125,7 @@ public: bool undo_history(int p_id); bool redo(); bool redo_history(int p_id); - void clear_history(bool p_increase_version = true, int p_idx = INVALID_HISTORY); + void clear_history(int p_idx = INVALID_HISTORY, bool p_increase_version = true); void set_history_as_saved(int p_idx); void set_history_as_unsaved(int p_idx); diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index 72ab186036..975a601ae1 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -124,7 +124,17 @@ void EditorExport::_bind_methods() { void EditorExport::add_export_platform(const Ref<EditorExportPlatform> &p_platform) { export_platforms.push_back(p_platform); + should_update_presets = true; + should_reload_presets = true; +} + +void EditorExport::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) { + export_platforms.erase(p_platform); + p_platform->cleanup(); + + should_update_presets = true; + should_reload_presets = true; } int EditorExport::get_export_platform_count() { @@ -244,7 +254,7 @@ void EditorExport::load_config() { if (!preset.is_valid()) { index++; - ERR_CONTINUE(!preset.is_valid()); + continue; // Unknown platform, skip without error (platform might be loaded later). } preset->set_name(config->get_value(section, "name")); @@ -343,6 +353,12 @@ void EditorExport::load_config() { void EditorExport::update_export_presets() { HashMap<StringName, List<EditorExportPlatform::ExportOption>> platform_options; + if (should_reload_presets) { + should_reload_presets = false; + export_presets.clear(); + load_config(); + } + for (int i = 0; i < export_platforms.size(); i++) { Ref<EditorExportPlatform> platform = export_platforms[i]; diff --git a/editor/export/editor_export.h b/editor/export/editor_export.h index f8cb90dc39..ebb2038f53 100644 --- a/editor/export/editor_export.h +++ b/editor/export/editor_export.h @@ -47,6 +47,7 @@ class EditorExport : public Node { Timer *save_timer = nullptr; bool block_save = false; bool should_update_presets = false; + bool should_reload_presets = false; static EditorExport *singleton; @@ -66,6 +67,7 @@ public: void add_export_platform(const Ref<EditorExportPlatform> &p_platform); int get_export_platform_count(); Ref<EditorExportPlatform> get_export_platform(int p_idx); + void remove_export_platform(const Ref<EditorExportPlatform> &p_platform); void add_export_preset(const Ref<EditorExportPreset> &p_preset, int p_at_pos = -1); int get_export_preset_count() const; diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index 8b31eda3bc..7ad589a58d 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -71,7 +71,7 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err) p_log->add_text(" "); p_log->add_text(get_name()); p_log->add_text(" - "); - if (p_err == OK) { + if (p_err == OK && get_worst_message_type() < EditorExportPlatform::EXPORT_MESSAGE_ERROR) { if (get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) { p_log->add_image(p_log->get_editor_theme_icon(SNAME("StatusWarning")), 16 * EDSCALE, 16 * EDSCALE, Color(1.0, 1.0, 1.0), INLINE_ALIGNMENT_CENTER); p_log->add_text(" "); @@ -167,58 +167,6 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err) return has_messages; } -void EditorExportPlatform::gen_debug_flags(Vector<String> &r_flags, int p_flags) { - String host = EDITOR_GET("network/debug/remote_host"); - int remote_port = (int)EDITOR_GET("network/debug/remote_port"); - - if (EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) { - host = EDITOR_GET("export/android/wifi_remote_debug_host"); - } else if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) { - host = "localhost"; - } - - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { - int port = EDITOR_GET("filesystem/file_server/port"); - String passwd = EDITOR_GET("filesystem/file_server/password"); - r_flags.push_back("--remote-fs"); - r_flags.push_back(host + ":" + itos(port)); - if (!passwd.is_empty()) { - r_flags.push_back("--remote-fs-password"); - r_flags.push_back(passwd); - } - } - - if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) { - r_flags.push_back("--remote-debug"); - - r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); - - List<String> breakpoints; - ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); - - if (breakpoints.size()) { - r_flags.push_back("--breakpoints"); - String bpoints; - for (const List<String>::Element *E = breakpoints.front(); E; E = E->next()) { - bpoints += E->get().replace(" ", "%20"); - if (E->next()) { - bpoints += ","; - } - } - - r_flags.push_back(bpoints); - } - } - - if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) { - r_flags.push_back("--debug-collisions"); - } - - if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) { - r_flags.push_back("--debug-navigation"); - } -} - Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export."); @@ -530,7 +478,7 @@ HashSet<String> EditorExportPlatform::get_features(const Ref<EditorExportPreset> return result; } -EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +EditorExportPlatform::ExportNotifier::ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { HashSet<String> features = p_platform.get_features(p_preset, p_debug); Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); //initial export plugin callback @@ -919,6 +867,55 @@ Vector<String> EditorExportPlatform::get_forced_export_files() { return files; } +Error EditorExportPlatform::_script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) { + Callable cb = ((ScriptCallbackData *)p_userdata)->file_cb; + ERR_FAIL_COND_V(!cb.is_valid(), FAILED); + + Variant path = p_path; + Variant data = p_data; + Variant file = p_file; + Variant total = p_total; + Variant enc_in = p_enc_in_filters; + Variant enc_ex = p_enc_ex_filters; + Variant enc_key = p_key; + + Variant ret; + Callable::CallError ce; + const Variant *args[7] = { &path, &data, &file, &total, &enc_in, &enc_ex, &enc_key }; + + cb.callp(args, 7, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute file save callback: %s.", Variant::get_callable_error_text(cb, args, 7, ce))); + + return (Error)ret.operator int(); +} + +Error EditorExportPlatform::_script_add_shared_object(void *p_userdata, const SharedObject &p_so) { + Callable cb = ((ScriptCallbackData *)p_userdata)->so_cb; + if (!cb.is_valid()) { + return OK; // Optional. + } + + Variant path = p_so.path; + Variant tags = p_so.tags; + Variant target = p_so.target; + + Variant ret; + Callable::CallError ce; + const Variant *args[3] = { &path, &tags, &target }; + + cb.callp(args, 3, ret, ce); + ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, vformat("Failed to execute shared object save callback: %s.", Variant::get_callable_error_text(cb, args, 3, ce))); + + return (Error)ret.operator int(); +} + +Error EditorExportPlatform::_export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func) { + ScriptCallbackData data; + data.file_cb = p_save_func; + data.so_cb = p_so_func; + return export_project_files(p_preset, p_debug, _script_save_file, &data, _script_add_shared_object); +} + Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) { //figure out paths of files that will be exported HashSet<String> paths; @@ -1425,7 +1422,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & return p_func(p_udata, "res://" + config_file, data, idx, total, enc_in_filters, enc_ex_filters, key); } -Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObject &p_so) { +Error EditorExportPlatform::_pack_add_shared_object(void *p_userdata, const SharedObject &p_so) { PackData *pack_data = (PackData *)p_userdata; if (pack_data->so_files) { pack_data->so_files->push_back(p_so); @@ -1434,6 +1431,15 @@ Error EditorExportPlatform::_add_shared_object(void *p_userdata, const SharedObj return OK; } +Error EditorExportPlatform::_zip_add_shared_object(void *p_userdata, const SharedObject &p_so) { + ZipData *zip_data = (ZipData *)p_userdata; + if (zip_data->so_files) { + zip_data->so_files->push_back(p_so); + } + + return OK; +} + void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name) { String dir = p_folder.is_empty() ? p_root_path : p_root_path.path_join(p_folder); @@ -1551,6 +1557,54 @@ void EditorExportPlatform::zip_folder_recursive(zipFile &p_zip, const String &p_ da->list_dir_end(); } +Dictionary EditorExportPlatform::_save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed) { + Vector<SharedObject> so_files; + int64_t embedded_start = 0; + int64_t embedded_size = 0; + Error err_code = save_pack(p_preset, p_debug, p_path, &so_files, p_embed, &embedded_start, &embedded_size); + + Dictionary ret; + ret["result"] = err_code; + if (err_code == OK) { + Array arr; + for (const SharedObject &E : so_files) { + Dictionary so; + so["path"] = E.path; + so["tags"] = E.tags; + so["target_folder"] = E.target; + arr.push_back(so); + } + ret["so_files"] = arr; + if (p_embed) { + ret["embedded_start"] = embedded_start; + ret["embedded_size"] = embedded_size; + } + } + + return ret; +} + +Dictionary EditorExportPlatform::_save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { + Vector<SharedObject> so_files; + Error err_code = save_zip(p_preset, p_debug, p_path, &so_files); + + Dictionary ret; + ret["result"] = err_code; + if (err_code == OK) { + Array arr; + for (const SharedObject &E : so_files) { + Dictionary so; + so["path"] = E.path; + so["tags"] = E.tags; + so["target_folder"] = E.target; + arr.push_back(so); + } + ret["so_files"] = arr; + } + + return ret; +} + Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) { EditorProgress ep("savepack", TTR("Packing"), 102, true); @@ -1570,7 +1624,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b pd.f = ftmp; pd.so_files = p_so_files; - Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _add_shared_object); + Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _pack_add_shared_object); // Close temp file. pd.f.unref(); @@ -1777,7 +1831,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b return OK; } -Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) { +Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files) { EditorProgress ep("savezip", TTR("Packing"), 102, true); Ref<FileAccess> io_fa; @@ -1787,8 +1841,9 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo ZipData zd; zd.ep = &ep; zd.zip = zip; + zd.so_files = p_so_files; - Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd); + Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd, _zip_add_shared_object); if (err != OK && err != ERR_SKIP) { add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files.")); } @@ -1798,45 +1853,48 @@ Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bo return OK; } -Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); return save_pack(p_preset, p_debug, p_path); } -Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); return save_zip(p_preset, p_debug, p_path); } -void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags) { +Vector<String> EditorExportPlatform::gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags) { + Vector<String> ret; String host = EDITOR_GET("network/debug/remote_host"); int remote_port = (int)EDITOR_GET("network/debug/remote_port"); - if (p_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) { + if (get_name() == "Android" && EditorSettings::get_singleton()->has_setting("export/android/use_wifi_for_remote_debug") && EDITOR_GET("export/android/use_wifi_for_remote_debug")) { + host = EDITOR_GET("export/android/wifi_remote_debug_host"); + } else if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) { host = "localhost"; } - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { int port = EDITOR_GET("filesystem/file_server/port"); String passwd = EDITOR_GET("filesystem/file_server/password"); - r_flags.push_back("--remote-fs"); - r_flags.push_back(host + ":" + itos(port)); + ret.push_back("--remote-fs"); + ret.push_back(host + ":" + itos(port)); if (!passwd.is_empty()) { - r_flags.push_back("--remote-fs-password"); - r_flags.push_back(passwd); + ret.push_back("--remote-fs-password"); + ret.push_back(passwd); } } - if (p_flags & DEBUG_FLAG_REMOTE_DEBUG) { - r_flags.push_back("--remote-debug"); + if (p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { + ret.push_back("--remote-debug"); - r_flags.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); + ret.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); List<String> breakpoints; ScriptEditor::get_singleton()->get_breakpoints(&breakpoints); if (breakpoints.size()) { - r_flags.push_back("--breakpoints"); + ret.push_back("--breakpoints"); String bpoints; for (List<String>::Element *E = breakpoints.front(); E; E = E->next()) { bpoints += E->get().replace(" ", "%20"); @@ -1845,17 +1903,18 @@ void EditorExportPlatform::gen_export_flags(Vector<String> &r_flags, int p_flags } } - r_flags.push_back(bpoints); + ret.push_back(bpoints); } } - if (p_flags & DEBUG_FLAG_VIEW_COLLISIONS) { - r_flags.push_back("--debug-collisions"); + if (p_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) { + ret.push_back("--debug-collisions"); } - if (p_flags & DEBUG_FLAG_VIEW_NAVIGATION) { - r_flags.push_back("--debug-navigation"); + if (p_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) { + ret.push_back("--debug-navigation"); } + return ret; } bool EditorExportPlatform::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { @@ -2035,8 +2094,61 @@ Error EditorExportPlatform::ssh_push_to_remote(const String &p_host, const Strin return OK; } +Array EditorExportPlatform::get_current_presets() const { + Array ret; + for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { + Ref<EditorExportPreset> ep = EditorExport::get_singleton()->get_export_preset(i); + if (ep->get_platform() == this) { + ret.push_back(ep); + } + } + return ret; +} + void EditorExportPlatform::_bind_methods() { ClassDB::bind_method(D_METHOD("get_os_name"), &EditorExportPlatform::get_os_name); + + ClassDB::bind_method(D_METHOD("create_preset"), &EditorExportPlatform::create_preset); + + ClassDB::bind_method(D_METHOD("find_export_template", "template_file_name"), &EditorExportPlatform::_find_export_template); + ClassDB::bind_method(D_METHOD("get_current_presets"), &EditorExportPlatform::get_current_presets); + + ClassDB::bind_method(D_METHOD("save_pack", "preset", "debug", "path", "embed"), &EditorExportPlatform::_save_pack, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("save_zip", "preset", "debug", "path"), &EditorExportPlatform::_save_zip); + + ClassDB::bind_method(D_METHOD("gen_export_flags", "flags"), &EditorExportPlatform::gen_export_flags); + + ClassDB::bind_method(D_METHOD("export_project_files", "preset", "debug", "save_cb", "shared_cb"), &EditorExportPlatform::_export_project_files, DEFVAL(Callable())); + + ClassDB::bind_method(D_METHOD("export_project", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_project, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("export_pack", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_pack, DEFVAL(0)); + ClassDB::bind_method(D_METHOD("export_zip", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_zip, DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("clear_messages"), &EditorExportPlatform::clear_messages); + ClassDB::bind_method(D_METHOD("add_message", "type", "category", "message"), &EditorExportPlatform::add_message); + ClassDB::bind_method(D_METHOD("get_message_count"), &EditorExportPlatform::get_message_count); + + ClassDB::bind_method(D_METHOD("get_message_type", "index"), &EditorExportPlatform::_get_message_type); + ClassDB::bind_method(D_METHOD("get_message_category", "index"), &EditorExportPlatform::_get_message_category); + ClassDB::bind_method(D_METHOD("get_message_text", "index"), &EditorExportPlatform::_get_message_text); + ClassDB::bind_method(D_METHOD("get_worst_message_type"), &EditorExportPlatform::get_worst_message_type); + + ClassDB::bind_method(D_METHOD("ssh_run_on_remote", "host", "port", "ssh_arg", "cmd_args", "output", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote, DEFVAL(Array()), DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("ssh_run_on_remote_no_wait", "host", "port", "ssh_args", "cmd_args", "port_fwd"), &EditorExportPlatform::_ssh_run_on_remote_no_wait, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("ssh_push_to_remote", "host", "port", "scp_args", "src_file", "dst_file"), &EditorExportPlatform::ssh_push_to_remote); + + ClassDB::bind_static_method("EditorExportPlatform", D_METHOD("get_forced_export_files"), &EditorExportPlatform::get_forced_export_files); + + BIND_ENUM_CONSTANT(EXPORT_MESSAGE_NONE); + BIND_ENUM_CONSTANT(EXPORT_MESSAGE_INFO); + BIND_ENUM_CONSTANT(EXPORT_MESSAGE_WARNING); + BIND_ENUM_CONSTANT(EXPORT_MESSAGE_ERROR); + + BIND_BITFIELD_FLAG(DEBUG_FLAG_DUMB_CLIENT); + BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG); + BIND_BITFIELD_FLAG(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST); + BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_COLLISIONS); + BIND_BITFIELD_FLAG(DEBUG_FLAG_VIEW_NAVIGATION); } EditorExportPlatform::EditorExportPlatform() { diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index 3fd75ff67f..a800bb95e6 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -56,6 +56,14 @@ public: typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so); + enum DebugFlags { + DEBUG_FLAG_DUMB_CLIENT = 1, + DEBUG_FLAG_REMOTE_DEBUG = 2, + DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4, + DEBUG_FLAG_VIEW_COLLISIONS = 8, + DEBUG_FLAG_VIEW_NAVIGATION = 16, + }; + enum ExportMessageType { EXPORT_MESSAGE_NONE, EXPORT_MESSAGE_INFO, @@ -92,6 +100,7 @@ private: struct ZipData { void *zip = nullptr; EditorProgress *ep = nullptr; + Vector<SharedObject> *so_files = nullptr; }; Vector<ExportMessage> messages; @@ -101,13 +110,22 @@ private: void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths); static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error _pack_add_shared_object(void *p_userdata, const SharedObject &p_so); + static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error _zip_add_shared_object(void *p_userdata, const SharedObject &p_so); + + struct ScriptCallbackData { + Callable file_cb; + Callable so_cb; + }; + + static Error _script_save_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key); + static Error _script_add_shared_object(void *p_userdata, const SharedObject &p_so); void _edit_files_with_filter(Ref<DirAccess> &da, const Vector<String> &p_filters, HashSet<String> &r_list, bool exclude); void _edit_filter_list(HashSet<String> &r_list, const String &p_filter, bool exclude); - static Error _add_shared_object(void *p_userdata, const SharedObject &p_so); - struct FileExportCache { uint64_t source_modified_time = 0; String source_md5; @@ -126,19 +144,46 @@ private: protected: struct ExportNotifier { - ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); + ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags); ~ExportNotifier(); }; HashSet<String> get_features(const Ref<EditorExportPreset> &p_preset, bool p_debug) const; - bool exists_export_template(const String &template_file_name, String *err) const; - String find_export_template(const String &template_file_name, String *err = nullptr) const; - void gen_export_flags(Vector<String> &r_flags, int p_flags); - void gen_debug_flags(Vector<String> &r_flags, int p_flags); + Dictionary _find_export_template(const String &p_template_file_name) const { + Dictionary ret; + String err; + + String path = find_export_template(p_template_file_name, &err); + ret["result"] = (err.is_empty() && !path.is_empty()) ? OK : FAILED; + ret["path"] = path; + ret["error_string"] = err; + + return ret; + } + + bool exists_export_template(const String &p_template_file_name, String *r_err) const; + String find_export_template(const String &p_template_file_name, String *r_err = nullptr) const; + Vector<String> gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags); virtual void zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder, const String &p_pkg_name); + Error _ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, Array r_output = Array(), int p_port_fwd = -1) const { + String pipe; + Error err = ssh_run_on_remote(p_host, p_port, p_ssh_args, p_cmd_args, &pipe, p_port_fwd); + r_output.push_back(pipe); + return err; + } + OS::ProcessID _ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, int p_port_fwd = -1) const { + OS::ProcessID pid = 0; + Error err = ssh_run_on_remote_no_wait(p_host, p_port, p_ssh_args, p_cmd_args, &pid, p_port_fwd); + if (err != OK) { + return -1; + } else { + return pid; + } + } + Error ssh_run_on_remote(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, String *r_out = nullptr, int p_port_fwd = -1) const; Error ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid = nullptr, int p_port_fwd = -1) const; Error ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const; @@ -195,6 +240,21 @@ public: return messages[p_index]; } + virtual ExportMessageType _get_message_type(int p_index) const { + ERR_FAIL_INDEX_V(p_index, messages.size(), EXPORT_MESSAGE_NONE); + return messages[p_index].msg_type; + } + + virtual String _get_message_category(int p_index) const { + ERR_FAIL_INDEX_V(p_index, messages.size(), String()); + return messages[p_index].category; + } + + virtual String _get_message_text(int p_index) const { + ERR_FAIL_INDEX_V(p_index, messages.size(), String()); + return messages[p_index].text; + } + virtual ExportMessageType get_worst_message_type() const { ExportMessageType worst_type = EXPORT_MESSAGE_NONE; for (int i = 0; i < messages.size(); i++) { @@ -216,10 +276,16 @@ public: virtual String get_name() const = 0; virtual Ref<Texture2D> get_logo() const = 0; + Array get_current_presets() const; + + Error _export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, const Callable &p_save_func, const Callable &p_so_func); Error export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func = nullptr); + Dictionary _save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed = false); + Dictionary _save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); + Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr); - Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); + Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr); virtual bool poll_export() { return false; } virtual int get_options_count() const { return 0; } @@ -229,31 +295,26 @@ public: virtual String get_option_tooltip(int p_device) const { return ""; } virtual String get_device_architecture(int p_device) const { return ""; } - enum DebugFlags { - DEBUG_FLAG_DUMB_CLIENT = 1, - DEBUG_FLAG_REMOTE_DEBUG = 2, - DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST = 4, - DEBUG_FLAG_VIEW_COLLISIONS = 8, - DEBUG_FLAG_VIEW_NAVIGATION = 16, - }; - virtual void cleanup() {} - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { return OK; } + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { return OK; } virtual Ref<Texture2D> get_run_icon() const { return get_logo(); } - bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const; + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const = 0; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const = 0; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const = 0; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) = 0; - virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); - virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0); + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) = 0; + virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0); + virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0); virtual void get_platform_features(List<String> *r_features) const = 0; - virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) = 0; + virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features){}; virtual String get_debug_protocol() const { return "tcp://"; } EditorExportPlatform(); }; +VARIANT_ENUM_CAST(EditorExportPlatform::ExportMessageType) +VARIANT_BITFIELD_CAST(EditorExportPlatform::DebugFlags); + #endif // EDITOR_EXPORT_PLATFORM_H diff --git a/editor/export/editor_export_platform_extension.cpp b/editor/export/editor_export_platform_extension.cpp new file mode 100644 index 0000000000..808a2076e2 --- /dev/null +++ b/editor/export/editor_export_platform_extension.cpp @@ -0,0 +1,317 @@ +/**************************************************************************/ +/* editor_export_platform_extension.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_export_platform_extension.h" + +void EditorExportPlatformExtension::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_config_error", "error_text"), &EditorExportPlatformExtension::set_config_error); + ClassDB::bind_method(D_METHOD("get_config_error"), &EditorExportPlatformExtension::get_config_error); + + ClassDB::bind_method(D_METHOD("set_config_missing_templates", "missing_templates"), &EditorExportPlatformExtension::set_config_missing_templates); + ClassDB::bind_method(D_METHOD("get_config_missing_templates"), &EditorExportPlatformExtension::get_config_missing_templates); + + GDVIRTUAL_BIND(_get_preset_features, "preset"); + GDVIRTUAL_BIND(_is_executable, "path"); + GDVIRTUAL_BIND(_get_export_options); + GDVIRTUAL_BIND(_should_update_export_options); + GDVIRTUAL_BIND(_get_export_option_visibility, "preset", "option"); + GDVIRTUAL_BIND(_get_export_option_warning, "preset", "option"); + + GDVIRTUAL_BIND(_get_os_name); + GDVIRTUAL_BIND(_get_name); + GDVIRTUAL_BIND(_get_logo); + + GDVIRTUAL_BIND(_poll_export); + GDVIRTUAL_BIND(_get_options_count); + GDVIRTUAL_BIND(_get_options_tooltip); + + GDVIRTUAL_BIND(_get_option_icon, "device"); + GDVIRTUAL_BIND(_get_option_label, "device"); + GDVIRTUAL_BIND(_get_option_tooltip, "device"); + GDVIRTUAL_BIND(_get_device_architecture, "device"); + + GDVIRTUAL_BIND(_cleanup); + + GDVIRTUAL_BIND(_run, "preset", "device", "debug_flags"); + GDVIRTUAL_BIND(_get_run_icon); + + GDVIRTUAL_BIND(_can_export, "preset", "debug"); + GDVIRTUAL_BIND(_has_valid_export_configuration, "preset", "debug"); + GDVIRTUAL_BIND(_has_valid_project_configuration, "preset"); + + GDVIRTUAL_BIND(_get_binary_extensions, "preset"); + + GDVIRTUAL_BIND(_export_project, "preset", "debug", "path", "flags"); + GDVIRTUAL_BIND(_export_pack, "preset", "debug", "path", "flags"); + GDVIRTUAL_BIND(_export_zip, "preset", "debug", "path", "flags"); + + GDVIRTUAL_BIND(_get_platform_features); + + GDVIRTUAL_BIND(_get_debug_protocol); +} + +void EditorExportPlatformExtension::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const { + Vector<String> ret; + if (GDVIRTUAL_REQUIRED_CALL(_get_preset_features, p_preset, ret) && r_features) { + for (const String &E : ret) { + r_features->push_back(E); + } + } +} + +bool EditorExportPlatformExtension::is_executable(const String &p_path) const { + bool ret = false; + GDVIRTUAL_CALL(_is_executable, p_path, ret); + return ret; +} + +void EditorExportPlatformExtension::get_export_options(List<ExportOption> *r_options) const { + TypedArray<Dictionary> ret; + if (GDVIRTUAL_CALL(_get_export_options, ret) && r_options) { + for (const Variant &var : ret) { + const Dictionary &d = var; + ERR_CONTINUE(!d.has("name")); + ERR_CONTINUE(!d.has("type")); + + PropertyInfo pinfo = PropertyInfo::from_dict(d); + ERR_CONTINUE(pinfo.name.is_empty() && (pinfo.usage & PROPERTY_USAGE_STORAGE)); + ERR_CONTINUE(pinfo.type < 0 || pinfo.type >= Variant::VARIANT_MAX); + + Variant default_value; + if (d.has("default_value")) { + default_value = d["default_value"]; + } + bool update_visibility = false; + if (d.has("update_visibility")) { + update_visibility = d["update_visibility"]; + } + bool required = false; + if (d.has("required")) { + required = d["required"]; + } + + r_options->push_back(ExportOption(pinfo, default_value, update_visibility, required)); + } + } +} + +bool EditorExportPlatformExtension::should_update_export_options() { + bool ret = false; + GDVIRTUAL_CALL(_should_update_export_options, ret); + return ret; +} + +bool EditorExportPlatformExtension::get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const { + bool ret = true; + GDVIRTUAL_CALL(_get_export_option_visibility, Ref<EditorExportPreset>(p_preset), p_option, ret); + return ret; +} + +String EditorExportPlatformExtension::get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const { + String ret; + GDVIRTUAL_CALL(_get_export_option_warning, Ref<EditorExportPreset>(p_preset), p_name, ret); + return ret; +} + +String EditorExportPlatformExtension::get_os_name() const { + String ret; + GDVIRTUAL_REQUIRED_CALL(_get_os_name, ret); + return ret; +} + +String EditorExportPlatformExtension::get_name() const { + String ret; + GDVIRTUAL_REQUIRED_CALL(_get_name, ret); + return ret; +} + +Ref<Texture2D> EditorExportPlatformExtension::get_logo() const { + Ref<Texture2D> ret; + GDVIRTUAL_REQUIRED_CALL(_get_logo, ret); + return ret; +} + +bool EditorExportPlatformExtension::poll_export() { + bool ret = false; + GDVIRTUAL_CALL(_poll_export, ret); + return ret; +} + +int EditorExportPlatformExtension::get_options_count() const { + int ret = 0; + GDVIRTUAL_CALL(_get_options_count, ret); + return ret; +} + +String EditorExportPlatformExtension::get_options_tooltip() const { + String ret; + GDVIRTUAL_CALL(_get_options_tooltip, ret); + return ret; +} + +Ref<ImageTexture> EditorExportPlatformExtension::get_option_icon(int p_index) const { + Ref<ImageTexture> ret; + if (GDVIRTUAL_CALL(_get_option_icon, p_index, ret)) { + return ret; + } + return EditorExportPlatform::get_option_icon(p_index); +} + +String EditorExportPlatformExtension::get_option_label(int p_device) const { + String ret; + GDVIRTUAL_CALL(_get_option_label, p_device, ret); + return ret; +} + +String EditorExportPlatformExtension::get_option_tooltip(int p_device) const { + String ret; + GDVIRTUAL_CALL(_get_option_tooltip, p_device, ret); + return ret; +} + +String EditorExportPlatformExtension::get_device_architecture(int p_device) const { + String ret; + GDVIRTUAL_CALL(_get_device_architecture, p_device, ret); + return ret; +} + +void EditorExportPlatformExtension::cleanup() { + GDVIRTUAL_CALL(_cleanup); +} + +Error EditorExportPlatformExtension::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { + Error ret = OK; + GDVIRTUAL_CALL(_run, p_preset, p_device, p_debug_flags, ret); + return ret; +} + +Ref<Texture2D> EditorExportPlatformExtension::get_run_icon() const { + Ref<Texture2D> ret; + if (GDVIRTUAL_CALL(_get_run_icon, ret)) { + return ret; + } + return EditorExportPlatform::get_run_icon(); +} + +bool EditorExportPlatformExtension::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { + bool ret = false; + config_error = r_error; + config_missing_templates = r_missing_templates; + if (GDVIRTUAL_CALL(_can_export, p_preset, p_debug, ret)) { + r_error = config_error; + r_missing_templates = config_missing_templates; + return ret; + } + return EditorExportPlatform::can_export(p_preset, r_error, r_missing_templates, p_debug); +} + +bool EditorExportPlatformExtension::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { + bool ret = false; + config_error = r_error; + config_missing_templates = r_missing_templates; + if (GDVIRTUAL_REQUIRED_CALL(_has_valid_export_configuration, p_preset, p_debug, ret)) { + r_error = config_error; + r_missing_templates = config_missing_templates; + } + return ret; +} + +bool EditorExportPlatformExtension::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const { + bool ret = false; + config_error = r_error; + if (GDVIRTUAL_REQUIRED_CALL(_has_valid_project_configuration, p_preset, ret)) { + r_error = config_error; + } + return ret; +} + +List<String> EditorExportPlatformExtension::get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const { + List<String> ret_list; + Vector<String> ret; + if (GDVIRTUAL_REQUIRED_CALL(_get_binary_extensions, p_preset, ret)) { + for (const String &E : ret) { + ret_list.push_back(E); + } + } + return ret_list; +} + +Error EditorExportPlatformExtension::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + Error ret = FAILED; + GDVIRTUAL_REQUIRED_CALL(_export_project, p_preset, p_debug, p_path, p_flags, ret); + return ret; +} + +Error EditorExportPlatformExtension::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + Error ret = FAILED; + if (GDVIRTUAL_CALL(_export_pack, p_preset, p_debug, p_path, p_flags, ret)) { + return ret; + } + return save_pack(p_preset, p_debug, p_path); +} + +Error EditorExportPlatformExtension::export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); + + Error ret = FAILED; + if (GDVIRTUAL_CALL(_export_zip, p_preset, p_debug, p_path, p_flags, ret)) { + return ret; + } + return save_zip(p_preset, p_debug, p_path); +} + +void EditorExportPlatformExtension::get_platform_features(List<String> *r_features) const { + Vector<String> ret; + if (GDVIRTUAL_REQUIRED_CALL(_get_platform_features, ret) && r_features) { + for (const String &E : ret) { + r_features->push_back(E); + } + } +} + +String EditorExportPlatformExtension::get_debug_protocol() const { + String ret; + if (GDVIRTUAL_CALL(_get_debug_protocol, ret)) { + return ret; + } + return EditorExportPlatform::get_debug_protocol(); +} + +EditorExportPlatformExtension::EditorExportPlatformExtension() { + //NOP +} + +EditorExportPlatformExtension::~EditorExportPlatformExtension() { + //NOP +} diff --git a/editor/export/editor_export_platform_extension.h b/editor/export/editor_export_platform_extension.h new file mode 100644 index 0000000000..6391e65ac1 --- /dev/null +++ b/editor/export/editor_export_platform_extension.h @@ -0,0 +1,149 @@ +/**************************************************************************/ +/* editor_export_platform_extension.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_EXPORT_PLATFORM_EXTENSION_H +#define EDITOR_EXPORT_PLATFORM_EXTENSION_H + +#include "editor_export_platform.h" +#include "editor_export_preset.h" + +class EditorExportPlatformExtension : public EditorExportPlatform { + GDCLASS(EditorExportPlatformExtension, EditorExportPlatform); + + mutable String config_error; + mutable bool config_missing_templates = false; + +protected: + static void _bind_methods(); + +public: + virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override; + GDVIRTUAL1RC(Vector<String>, _get_preset_features, Ref<EditorExportPreset>); + + virtual bool is_executable(const String &p_path) const override; + GDVIRTUAL1RC(bool, _is_executable, const String &); + + virtual void get_export_options(List<ExportOption> *r_options) const override; + GDVIRTUAL0RC(TypedArray<Dictionary>, _get_export_options); + + virtual bool should_update_export_options() override; + GDVIRTUAL0R(bool, _should_update_export_options); + + virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; + GDVIRTUAL2RC(bool, _get_export_option_visibility, Ref<EditorExportPreset>, const String &); + + virtual String get_export_option_warning(const EditorExportPreset *p_preset, const StringName &p_name) const override; + GDVIRTUAL2RC(String, _get_export_option_warning, Ref<EditorExportPreset>, const StringName &); + + virtual String get_os_name() const override; + GDVIRTUAL0RC(String, _get_os_name); + + virtual String get_name() const override; + GDVIRTUAL0RC(String, _get_name); + + virtual Ref<Texture2D> get_logo() const override; + GDVIRTUAL0RC(Ref<Texture2D>, _get_logo); + + virtual bool poll_export() override; + GDVIRTUAL0R(bool, _poll_export); + + virtual int get_options_count() const override; + GDVIRTUAL0RC(int, _get_options_count); + + virtual String get_options_tooltip() const override; + GDVIRTUAL0RC(String, _get_options_tooltip); + + virtual Ref<ImageTexture> get_option_icon(int p_index) const override; + GDVIRTUAL1RC(Ref<ImageTexture>, _get_option_icon, int); + + virtual String get_option_label(int p_device) const override; + GDVIRTUAL1RC(String, _get_option_label, int); + + virtual String get_option_tooltip(int p_device) const override; + GDVIRTUAL1RC(String, _get_option_tooltip, int); + + virtual String get_device_architecture(int p_device) const override; + GDVIRTUAL1RC(String, _get_device_architecture, int); + + virtual void cleanup() override; + GDVIRTUAL0(_cleanup); + + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; + GDVIRTUAL3R(Error, _run, Ref<EditorExportPreset>, int, BitField<EditorExportPlatform::DebugFlags>); + + virtual Ref<Texture2D> get_run_icon() const override; + GDVIRTUAL0RC(Ref<Texture2D>, _get_run_icon); + + void set_config_error(const String &p_error) const { + config_error = p_error; + } + String get_config_error() const { + return config_error; + } + + void set_config_missing_templates(bool p_missing_templates) const { + config_missing_templates = p_missing_templates; + } + bool get_config_missing_templates() const { + return config_missing_templates; + } + + virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; + GDVIRTUAL2RC(bool, _can_export, Ref<EditorExportPreset>, bool); + + virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; + GDVIRTUAL2RC(bool, _has_valid_export_configuration, Ref<EditorExportPreset>, bool); + + virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; + GDVIRTUAL1RC(bool, _has_valid_project_configuration, Ref<EditorExportPreset>); + + virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; + GDVIRTUAL1RC(Vector<String>, _get_binary_extensions, Ref<EditorExportPreset>); + + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; + GDVIRTUAL4R(Error, _export_project, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>); + + virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; + GDVIRTUAL4R(Error, _export_pack, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>); + + virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; + GDVIRTUAL4R(Error, _export_zip, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>); + + virtual void get_platform_features(List<String> *r_features) const override; + GDVIRTUAL0RC(Vector<String>, _get_platform_features); + + virtual String get_debug_protocol() const override; + GDVIRTUAL0RC(String, _get_debug_protocol); + + EditorExportPlatformExtension(); + ~EditorExportPlatformExtension(); +}; + +#endif // EDITOR_EXPORT_PLATFORM_EXTENSION_H diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp index cdaf18b346..24d89b7f34 100644 --- a/editor/export/editor_export_platform_pc.cpp +++ b/editor/export/editor_export_platform_pc.cpp @@ -115,7 +115,7 @@ bool EditorExportPlatformPC::has_valid_project_configuration(const Ref<EditorExp return true; } -Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); Error err = prepare_template(p_preset, p_debug, p_path, p_flags); @@ -129,7 +129,7 @@ Error EditorExportPlatformPC::export_project(const Ref<EditorExportPreset> &p_pr return err; } -Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { if (!DirAccess::exists(p_path.get_base_dir())) { add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), TTR("The given export path doesn't exist.")); return ERR_FILE_BAD_PATH; @@ -182,7 +182,7 @@ Error EditorExportPlatformPC::prepare_template(const Ref<EditorExportPreset> &p_ return err; } -Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { String pck_path; if (p_preset->get("binary_format/embed_pck")) { pck_path = p_path; diff --git a/editor/export/editor_export_platform_pc.h b/editor/export/editor_export_platform_pc.h index 53574c2333..668ddaf47e 100644 --- a/editor/export/editor_export_platform_pc.h +++ b/editor/export/editor_export_platform_pc.h @@ -54,13 +54,13 @@ public: virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); virtual String get_template_file_name(const String &p_target, const String &p_arch) const = 0; - virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); - virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { return OK; }; - virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); + virtual Error prepare_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags); + virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { return OK; } + virtual Error export_project_data(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags); void set_name(const String &p_name); void set_os_name(const String &p_name); diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index 28d0750d5a..f56dd61e3b 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -48,6 +48,14 @@ Ref<EditorExportPreset> EditorExportPlugin::get_export_preset() const { return export_preset; } +Ref<EditorExportPlatform> EditorExportPlugin::get_export_platform() const { + if (export_preset.is_valid()) { + return export_preset->get_platform(); + } else { + return Ref<EditorExportPlatform>(); + } +} + void EditorExportPlugin::add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap) { ExtraFile ef; ef.data = p_file; @@ -321,6 +329,9 @@ void EditorExportPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("skip"), &EditorExportPlugin::skip); ClassDB::bind_method(D_METHOD("get_option", "name"), &EditorExportPlugin::get_option); + ClassDB::bind_method(D_METHOD("get_export_preset"), &EditorExportPlugin::get_export_preset); + ClassDB::bind_method(D_METHOD("get_export_platform"), &EditorExportPlugin::get_export_platform); + GDVIRTUAL_BIND(_export_file, "path", "type", "features"); GDVIRTUAL_BIND(_export_begin, "features", "is_debug", "path", "flags"); GDVIRTUAL_BIND(_export_end); diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index 56eea85010..7a355614c7 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -91,6 +91,7 @@ class EditorExportPlugin : public RefCounted { protected: void set_export_preset(const Ref<EditorExportPreset> &p_preset); Ref<EditorExportPreset> get_export_preset() const; + Ref<EditorExportPlatform> get_export_platform() const; void add_file(const String &p_path, const Vector<uint8_t> &p_file, bool p_remap); void add_shared_object(const String &p_path, const Vector<String> &tags, const String &p_target = String()); diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp index e2e3e9d154..9f805666d0 100644 --- a/editor/export/editor_export_preset.cpp +++ b/editor/export/editor_export_preset.cpp @@ -62,6 +62,48 @@ bool EditorExportPreset::_get(const StringName &p_name, Variant &r_ret) const { void EditorExportPreset::_bind_methods() { ClassDB::bind_method(D_METHOD("_get_property_warning", "name"), &EditorExportPreset::_get_property_warning); + + ClassDB::bind_method(D_METHOD("has", "property"), &EditorExportPreset::has); + + ClassDB::bind_method(D_METHOD("get_files_to_export"), &EditorExportPreset::get_files_to_export); + ClassDB::bind_method(D_METHOD("get_customized_files"), &EditorExportPreset::get_customized_files); + ClassDB::bind_method(D_METHOD("get_customized_files_count"), &EditorExportPreset::get_customized_files_count); + ClassDB::bind_method(D_METHOD("has_export_file", "path"), &EditorExportPreset::has_export_file); + ClassDB::bind_method(D_METHOD("get_file_export_mode", "path", "default"), &EditorExportPreset::get_file_export_mode, DEFVAL(MODE_FILE_NOT_CUSTOMIZED)); + + ClassDB::bind_method(D_METHOD("get_preset_name"), &EditorExportPreset::get_name); + ClassDB::bind_method(D_METHOD("is_runnable"), &EditorExportPreset::is_runnable); + ClassDB::bind_method(D_METHOD("are_advanced_options_enabled"), &EditorExportPreset::are_advanced_options_enabled); + ClassDB::bind_method(D_METHOD("is_dedicated_server"), &EditorExportPreset::is_dedicated_server); + ClassDB::bind_method(D_METHOD("get_export_filter"), &EditorExportPreset::get_export_filter); + ClassDB::bind_method(D_METHOD("get_include_filter"), &EditorExportPreset::get_include_filter); + ClassDB::bind_method(D_METHOD("get_exclude_filter"), &EditorExportPreset::get_exclude_filter); + ClassDB::bind_method(D_METHOD("get_custom_features"), &EditorExportPreset::get_custom_features); + ClassDB::bind_method(D_METHOD("get_export_path"), &EditorExportPreset::get_export_path); + ClassDB::bind_method(D_METHOD("get_encryption_in_filter"), &EditorExportPreset::get_enc_in_filter); + ClassDB::bind_method(D_METHOD("get_encryption_ex_filter"), &EditorExportPreset::get_enc_ex_filter); + ClassDB::bind_method(D_METHOD("get_encrypt_pck"), &EditorExportPreset::get_enc_pck); + ClassDB::bind_method(D_METHOD("get_encrypt_directory"), &EditorExportPreset::get_enc_directory); + ClassDB::bind_method(D_METHOD("get_encryption_key"), &EditorExportPreset::get_script_encryption_key); + ClassDB::bind_method(D_METHOD("get_script_export_mode"), &EditorExportPreset::get_script_export_mode); + + ClassDB::bind_method(D_METHOD("get_or_env", "name", "env_var"), &EditorExportPreset::_get_or_env); + ClassDB::bind_method(D_METHOD("get_version", "name", "windows_version"), &EditorExportPreset::get_version); + + BIND_ENUM_CONSTANT(EXPORT_ALL_RESOURCES); + BIND_ENUM_CONSTANT(EXPORT_SELECTED_SCENES); + BIND_ENUM_CONSTANT(EXPORT_SELECTED_RESOURCES); + BIND_ENUM_CONSTANT(EXCLUDE_SELECTED_RESOURCES); + BIND_ENUM_CONSTANT(EXPORT_CUSTOMIZED); + + BIND_ENUM_CONSTANT(MODE_FILE_NOT_CUSTOMIZED); + BIND_ENUM_CONSTANT(MODE_FILE_STRIP); + BIND_ENUM_CONSTANT(MODE_FILE_KEEP); + BIND_ENUM_CONSTANT(MODE_FILE_REMOVE); + + BIND_ENUM_CONSTANT(MODE_SCRIPT_TEXT); + BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS); + BIND_ENUM_CONSTANT(MODE_SCRIPT_BINARY_TOKENS_COMPRESSED); } String EditorExportPreset::_get_property_warning(const StringName &p_name) const { diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h index c6a8808af1..f220477461 100644 --- a/editor/export/editor_export_preset.h +++ b/editor/export/editor_export_preset.h @@ -168,6 +168,9 @@ public: void set_script_export_mode(int p_mode); int get_script_export_mode() const; + Variant _get_or_env(const StringName &p_name, const String &p_env_var) const { + return get_or_env(p_name, p_env_var); + } Variant get_or_env(const StringName &p_name, const String &p_env_var, bool *r_valid = nullptr) const; // Return the preset's version number, or fall back to the @@ -183,4 +186,8 @@ public: EditorExportPreset(); }; +VARIANT_ENUM_CAST(EditorExportPreset::ExportFilter); +VARIANT_ENUM_CAST(EditorExportPreset::FileExportMode); +VARIANT_ENUM_CAST(EditorExportPreset::ScriptExportMode); + #endif // EDITOR_EXPORT_PRESET_H diff --git a/editor/export/export_template_manager.cpp b/editor/export/export_template_manager.cpp index 9dfbe515d2..2e4f2da9a8 100644 --- a/editor/export/export_template_manager.cpp +++ b/editor/export/export_template_manager.cpp @@ -47,6 +47,32 @@ #include "scene/gui/tree.h" #include "scene/main/http_request.h" +enum DownloadsAvailability { + DOWNLOADS_AVAILABLE, + DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE, + DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS, +}; + +static DownloadsAvailability _get_downloads_availability() { + const int network_mode = EDITOR_GET("network/connection/network_mode"); + if (network_mode == EditorSettings::NETWORK_OFFLINE) { + return DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE; + } + + // Downloadable export templates are only available for stable and official alpha/beta/RC builds + // (which always have a number following their status, e.g. "alpha1"). + // Therefore, don't display download-related features when using a development version + // (whose builds aren't numbered). + if (String(VERSION_STATUS) == String("dev") || + String(VERSION_STATUS) == String("alpha") || + String(VERSION_STATUS) == String("beta") || + String(VERSION_STATUS) == String("rc")) { + return DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS; + } + + return DOWNLOADS_AVAILABLE; +} + void ExportTemplateManager::_update_template_status() { // Fetch installed templates from the file system. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); @@ -641,9 +667,55 @@ void ExportTemplateManager::_open_template_folder(const String &p_version) { void ExportTemplateManager::popup_manager() { _update_template_status(); - if (downloads_available && !is_downloading_templates) { - _refresh_mirrors(); + + switch (_get_downloads_availability()) { + case DOWNLOADS_AVAILABLE: { + current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file.")); + + mirrors_list->clear(); + mirrors_list->add_item(TTR("Best available mirror"), 0); + mirrors_list->set_disabled(false); + mirrors_list->set_tooltip_text(""); + + mirror_options_button->set_disabled(false); + + download_current_button->set_disabled(false); + download_current_button->set_tooltip_text(""); + + if (!is_downloading_templates) { + _refresh_mirrors(); + } + } break; + + case DOWNLOADS_NOT_AVAILABLE_IN_OFFLINE_MODE: { + current_missing_label->set_text(TTR("Export templates are missing. Install them from a file.")); + + mirrors_list->clear(); + mirrors_list->add_item(TTR("Not available in offline mode"), 0); + mirrors_list->set_disabled(true); + mirrors_list->set_tooltip_text(TTR("Template downloading is disabled in offline mode.")); + + mirror_options_button->set_disabled(true); + + download_current_button->set_disabled(true); + download_current_button->set_tooltip_text(TTR("Template downloading is disabled in offline mode.")); + } break; + + case DOWNLOADS_NOT_AVAILABLE_FOR_DEV_BUILDS: { + current_missing_label->set_text(TTR("Export templates are missing. Install them from a file.")); + + mirrors_list->clear(); + mirrors_list->add_item(TTR("No templates for development builds"), 0); + mirrors_list->set_disabled(true); + mirrors_list->set_tooltip_text(TTR("Official export templates aren't available for development builds.")); + + mirror_options_button->set_disabled(true); + + download_current_button->set_disabled(true); + download_current_button->set_tooltip_text(TTR("Official export templates aren't available for development builds.")); + } break; } + popup_centered(Size2(720, 280) * EDSCALE); } @@ -866,16 +938,6 @@ ExportTemplateManager::ExportTemplateManager() { set_hide_on_ok(false); set_ok_button_text(TTR("Close")); - // Downloadable export templates are only available for stable and official alpha/beta/RC builds - // (which always have a number following their status, e.g. "alpha1"). - // Therefore, don't display download-related features when using a development version - // (whose builds aren't numbered). - downloads_available = - String(VERSION_STATUS) != String("dev") && - String(VERSION_STATUS) != String("alpha") && - String(VERSION_STATUS) != String("beta") && - String(VERSION_STATUS) != String("rc"); - VBoxContainer *main_vb = memnew(VBoxContainer); add_child(main_vb); @@ -898,11 +960,6 @@ ExportTemplateManager::ExportTemplateManager() { current_missing_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); current_missing_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); - if (downloads_available) { - current_missing_label->set_text(TTR("Export templates are missing. Download them or install from a file.")); - } else { - current_missing_label->set_text(TTR("Export templates are missing. Install them from a file.")); - } current_hb->add_child(current_missing_label); // Status: Current version is installed. @@ -957,12 +1014,6 @@ ExportTemplateManager::ExportTemplateManager() { mirrors_list = memnew(OptionButton); mirrors_list->set_custom_minimum_size(Size2(280, 0) * EDSCALE); - if (downloads_available) { - mirrors_list->add_item(TTR("Best available mirror"), 0); - } else { - mirrors_list->add_item(TTR("(no templates for development builds)"), 0); - mirrors_list->set_disabled(true); - } download_install_hb->add_child(mirrors_list); request_mirrors = memnew(HTTPRequest); @@ -972,24 +1023,17 @@ ExportTemplateManager::ExportTemplateManager() { mirror_options_button = memnew(MenuButton); mirror_options_button->get_popup()->add_item(TTR("Open in Web Browser"), VISIT_WEB_MIRROR); mirror_options_button->get_popup()->add_item(TTR("Copy Mirror URL"), COPY_MIRROR_URL); - mirror_options_button->set_disabled(!downloads_available); download_install_hb->add_child(mirror_options_button); mirror_options_button->get_popup()->connect(SceneStringName(id_pressed), callable_mp(this, &ExportTemplateManager::_mirror_options_button_cbk)); download_install_hb->add_spacer(); - Button *download_current_button = memnew(Button); + download_current_button = memnew(Button); download_current_button->set_text(TTR("Download and Install")); download_current_button->set_tooltip_text(TTR("Download and install templates for the current version from the best possible mirror.")); download_install_hb->add_child(download_current_button); download_current_button->connect(SceneStringName(pressed), callable_mp(this, &ExportTemplateManager::_download_current)); - // Update downloads buttons to prevent unsupported downloads. - if (!downloads_available) { - download_current_button->set_disabled(true); - download_current_button->set_tooltip_text(TTR("Official export templates aren't available for development builds.")); - } - HBoxContainer *install_file_hb = memnew(HBoxContainer); install_file_hb->set_alignment(BoxContainer::ALIGNMENT_END); install_options_vb->add_child(install_file_hb); diff --git a/editor/export/export_template_manager.h b/editor/export/export_template_manager.h index 5227e43a6e..ccfe568d32 100644 --- a/editor/export/export_template_manager.h +++ b/editor/export/export_template_manager.h @@ -46,7 +46,6 @@ class ExportTemplateManager : public AcceptDialog { GDCLASS(ExportTemplateManager, AcceptDialog); bool current_version_exists = false; - bool downloads_available = true; bool mirrors_available = false; bool is_refreshing_mirrors = false; bool is_downloading_templates = false; @@ -74,6 +73,7 @@ class ExportTemplateManager : public AcceptDialog { Label *download_progress_label = nullptr; HTTPRequest *download_templates = nullptr; Button *install_file_button = nullptr; + Button *download_current_button = nullptr; HTTPRequest *request_mirrors = nullptr; enum TemplatesAction { diff --git a/editor/icons/MemberConstructor.svg b/editor/icons/MemberConstructor.svg new file mode 100644 index 0000000000..0e61768739 --- /dev/null +++ b/editor/icons/MemberConstructor.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M9 6c-1.33 2.67-1.33 4.33 0 7h2c-1.33-2.67-1.33-4.33 0-7zm4 0c1.33 2.67 1.33 4.33 0 7h2c1.33-2.67 1.33-4.33 0-7zM1 11a1 1 0 0 0 0 2 1 1 0 0 0 0-2zm6 2v-2H6a1 1 0 0 1 0-2h1V7H6a3 3 0 0 0 0 6z"/></svg>
\ No newline at end of file diff --git a/editor/icons/MemberOperator.svg b/editor/icons/MemberOperator.svg new file mode 100644 index 0000000000..a00d990e26 --- /dev/null +++ b/editor/icons/MemberOperator.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M9 6c-1.33 2.67-1.33 4.33 0 7h2c-1.33-2.67-1.33-4.33 0-7zm4 0c1.33 2.67 1.33 4.33 0 7h2c1.33-2.67 1.33-4.33 0-7zM1 11a1 1 0 0 0 0 2 1 1 0 0 0 0-2zm4-4a3 3 0 0 0 0 6 3 3 0 0 0 0-6zm0 2a1 1 0 0 1 0 2 1 1 0 0 1 0-2z"/></svg>
\ No newline at end of file diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 853e3e813b..fa07511dd0 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -649,6 +649,9 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R String name = p_node->get_name(); NodePath original_path = p_root->get_path_to(p_node); // Used to detect renames due to import hints. + Ref<Resource> original_meta = memnew(Resource); // Create temp resource to hold original meta + original_meta->merge_meta_from(p_node); + bool isroot = p_node == p_root; if (!isroot && _teststr(name, "noimp")) { @@ -1022,6 +1025,8 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R print_verbose(vformat("Fix: Renamed %s to %s", original_path, new_path)); r_node_renames.push_back({ original_path, p_node }); } + // If we created new node instead, merge meta values from the original node. + p_node->merge_meta_from(*original_meta); } return p_node; @@ -2452,6 +2457,8 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_ mesh_node->set_transform(src_mesh_node->get_transform()); mesh_node->set_skin(src_mesh_node->get_skin()); mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path()); + mesh_node->merge_meta_from(src_mesh_node); + if (src_mesh_node->get_mesh().is_valid()) { Ref<ArrayMesh> mesh; if (!src_mesh_node->get_mesh()->has_mesh()) { @@ -2599,6 +2606,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_ for (int i = 0; i < mesh->get_surface_count(); i++) { mesh_node->set_surface_override_material(i, src_mesh_node->get_surface_material(i)); } + mesh->merge_meta_from(*src_mesh_node->get_mesh()); } } @@ -3096,6 +3104,19 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p } } + // Apply RESET animation before serializing. + if (_scene_import_type == "PackedScene") { + int scene_child_count = scene->get_child_count(); + for (int i = 0; i < scene_child_count; i++) { + AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(scene->get_child(i)); + if (ap) { + if (ap->can_apply_reset()) { + ap->apply_reset(); + } + } + } + } + if (post_import_script.is_valid()) { post_import_script->init(p_source_file); scene = post_import_script->post_import(scene); @@ -3114,7 +3135,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p progress.step(TTR("Saving..."), 104); int flags = 0; - if (EDITOR_GET("filesystem/on_save/compress_binary_resources")) { + if (EditorSettings::get_singleton() && EDITOR_GET("filesystem/on_save/compress_binary_resources")) { flags |= ResourceSaver::FLAG_COMPRESS; } diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp index ce3411bf41..ce403a66de 100644 --- a/editor/import/resource_importer_wav.cpp +++ b/editor/import/resource_importer_wav.cpp @@ -90,7 +90,7 @@ 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, "Disabled,RAM (Ima-ADPCM),QOA (Quite OK Audio)"), 0)); + r_options->push_back(ImportOption(PropertyInfo(Variant::INT, "compress/mode", PROPERTY_HINT_ENUM, "PCM (Uncompressed),IMA ADPCM,Quite OK Audio"), 0)); } 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/inspector_dock.cpp b/editor/inspector_dock.cpp index aa075c80c3..dc07403213 100644 --- a/editor/inspector_dock.cpp +++ b/editor/inspector_dock.cpp @@ -190,7 +190,7 @@ void InspectorDock::_menu_option_confirm(int p_option, bool p_confirmed) { } int history_id = EditorUndoRedoManager::get_singleton()->get_history_id_for_object(current); - EditorUndoRedoManager::get_singleton()->clear_history(true, history_id); + EditorUndoRedoManager::get_singleton()->clear_history(history_id); EditorNode::get_singleton()->edit_item(current, inspector); } diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 660e4647a1..73c17aa760 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(); @@ -1413,6 +1476,12 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_timel _seek_value_changed(p_pos, p_timeline_only); } +void AnimationPlayerEditor::_animation_update_key_frame() { + if (player) { + player->advance(0); + } +} + void AnimationPlayerEditor::_animation_tool_menu(int p_option) { String current = _get_current(); @@ -1505,30 +1574,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(); } } } @@ -1874,6 +1941,7 @@ bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> p_anim) { void AnimationPlayerEditor::_bind_methods() { // Needed for UndoRedo. ClassDB::bind_method(D_METHOD("_animation_player_changed"), &AnimationPlayerEditor::_animation_player_changed); + ClassDB::bind_method(D_METHOD("_animation_update_key_frame"), &AnimationPlayerEditor::_animation_update_key_frame); ClassDB::bind_method(D_METHOD("_start_onion_skinning"), &AnimationPlayerEditor::_start_onion_skinning); ClassDB::bind_method(D_METHOD("_stop_onion_skinning"), &AnimationPlayerEditor::_stop_onion_skinning); @@ -1902,27 +1970,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); @@ -2138,6 +2206,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() { @@ -2165,7 +2241,7 @@ void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const V return; } te->_clear_selection(); - te->insert_value_key(p_keyed, p_value, p_advance); + te->insert_value_key(p_keyed, p_advance); } void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) { diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 4a3b1f37ab..860d421b91 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -178,6 +178,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(); @@ -216,6 +217,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _animation_key_editor_seek(float p_pos, bool p_timeline_only = false, bool p_update_position_only = false); void _animation_key_editor_anim_len_changed(float p_len); + void _animation_update_key_frame(); virtual void shortcut_input(const Ref<InputEvent> &p_ev) override; void _animation_tool_menu(int p_option); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index ec42cb31be..6e41e98360 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4331,13 +4331,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Node2D *n2d = Object::cast_to<Node2D>(ci); if (key_pos && p_location) { - te->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); + te->insert_node_value_key(n2d, "position", p_on_existing); } if (key_rot && p_rotation) { - te->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); + te->insert_node_value_key(n2d, "rotation", p_on_existing); } if (key_scale && p_scale) { - te->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); + te->insert_node_value_key(n2d, "scale", p_on_existing); } if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { @@ -4363,13 +4363,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, if (has_chain && ik_chain.size()) { for (Node2D *&F : ik_chain) { if (key_pos) { - te->insert_node_value_key(F, "position", F->get_position(), p_on_existing); + te->insert_node_value_key(F, "position", p_on_existing); } if (key_rot) { - te->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); + te->insert_node_value_key(F, "rotation", p_on_existing); } if (key_scale) { - te->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); + te->insert_node_value_key(F, "scale", p_on_existing); } } } @@ -4379,13 +4379,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Control *ctrl = Object::cast_to<Control>(ci); if (key_pos) { - te->insert_node_value_key(ctrl, "position", ctrl->get_position(), p_on_existing); + te->insert_node_value_key(ctrl, "position", p_on_existing); } if (key_rot) { - te->insert_node_value_key(ctrl, "rotation", ctrl->get_rotation(), p_on_existing); + te->insert_node_value_key(ctrl, "rotation", p_on_existing); } if (key_scale) { - te->insert_node_value_key(ctrl, "size", ctrl->get_size(), p_on_existing); + te->insert_node_value_key(ctrl, "size", p_on_existing); } } } diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp index d9f60e155d..8ce667568f 100644 --- a/editor/plugins/editor_plugin.cpp +++ b/editor/plugins/editor_plugin.cpp @@ -40,6 +40,7 @@ #include "editor/editor_translation_parser.h" #include "editor/editor_undo_redo_manager.h" #include "editor/export/editor_export.h" +#include "editor/export/editor_export_platform.h" #include "editor/gui/editor_bottom_panel.h" #include "editor/gui/editor_title_bar.h" #include "editor/import/3d/resource_importer_scene.h" @@ -441,6 +442,16 @@ void EditorPlugin::remove_export_plugin(const Ref<EditorExportPlugin> &p_exporte EditorExport::get_singleton()->remove_export_plugin(p_exporter); } +void EditorPlugin::add_export_platform(const Ref<EditorExportPlatform> &p_platform) { + ERR_FAIL_COND(p_platform.is_null()); + EditorExport::get_singleton()->add_export_platform(p_platform); +} + +void EditorPlugin::remove_export_platform(const Ref<EditorExportPlatform> &p_platform) { + ERR_FAIL_COND(p_platform.is_null()); + EditorExport::get_singleton()->remove_export_platform(p_platform); +} + void EditorPlugin::add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin) { ERR_FAIL_COND(!p_gizmo_plugin.is_valid()); Node3DEditor::get_singleton()->add_gizmo_plugin(p_gizmo_plugin); @@ -608,6 +619,8 @@ void EditorPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("remove_scene_post_import_plugin", "scene_import_plugin"), &EditorPlugin::remove_scene_post_import_plugin); ClassDB::bind_method(D_METHOD("add_export_plugin", "plugin"), &EditorPlugin::add_export_plugin); ClassDB::bind_method(D_METHOD("remove_export_plugin", "plugin"), &EditorPlugin::remove_export_plugin); + ClassDB::bind_method(D_METHOD("add_export_platform", "platform"), &EditorPlugin::add_export_platform); + ClassDB::bind_method(D_METHOD("remove_export_platform", "platform"), &EditorPlugin::remove_export_platform); ClassDB::bind_method(D_METHOD("add_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::add_node_3d_gizmo_plugin); ClassDB::bind_method(D_METHOD("remove_node_3d_gizmo_plugin", "plugin"), &EditorPlugin::remove_node_3d_gizmo_plugin); ClassDB::bind_method(D_METHOD("add_inspector_plugin", "plugin"), &EditorPlugin::add_inspector_plugin); diff --git a/editor/plugins/editor_plugin.h b/editor/plugins/editor_plugin.h index f6c4b35407..2e0771f906 100644 --- a/editor/plugins/editor_plugin.h +++ b/editor/plugins/editor_plugin.h @@ -41,6 +41,7 @@ class PopupMenu; class EditorDebuggerPlugin; class EditorExport; class EditorExportPlugin; +class EditorExportPlatform; class EditorImportPlugin; class EditorInspectorPlugin; class EditorInterface; @@ -224,6 +225,9 @@ public: void add_export_plugin(const Ref<EditorExportPlugin> &p_exporter); void remove_export_plugin(const Ref<EditorExportPlugin> &p_exporter); + void add_export_platform(const Ref<EditorExportPlatform> &p_platform); + void remove_export_platform(const Ref<EditorExportPlatform> &p_platform); + void add_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin); void remove_node_3d_gizmo_plugin(const Ref<EditorNode3DGizmoPlugin> &p_gizmo_plugin); 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..1b2cd441ad 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -1060,7 +1060,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/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index a33fc47955..d7de5a7223 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -578,8 +578,8 @@ void ScriptEditor::_clear_breakpoints() { script_editor_cache->get_sections(&cached_editors); for (const String &E : cached_editors) { Array breakpoints = _get_cached_breakpoints_for_script(E); - for (int i = 0; i < breakpoints.size(); i++) { - EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, false); + for (int breakpoint : breakpoints) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, false); } if (breakpoints.size() > 0) { @@ -1820,6 +1820,48 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { emit_signal(SNAME("editor_script_changed"), p_script); } +Vector<String> ScriptEditor::_get_breakpoints() { + Vector<String> ret; + HashSet<String> loaded_scripts; + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (!se) { + continue; + } + + Ref<Script> scr = se->get_edited_resource(); + if (scr.is_null()) { + continue; + } + + String base = scr->get_path(); + loaded_scripts.insert(base); + if (base.is_empty() || base.begins_with("local://")) { + continue; + } + + PackedInt32Array bpoints = se->get_breakpoints(); + for (int32_t bpoint : bpoints) { + ret.push_back(base + ":" + itos((int)bpoint + 1)); + } + } + + // Load breakpoints that are in closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int breakpoint : breakpoints) { + ret.push_back(E + ":" + itos((int)breakpoint + 1)); + } + } + return ret; +} + void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { HashSet<String> loaded_scripts; for (int i = 0; i < tab_container->get_tab_count(); i++) { @@ -1829,19 +1871,19 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { } Ref<Script> scr = se->get_edited_resource(); - if (scr == nullptr) { + if (scr.is_null()) { continue; } String base = scr->get_path(); loaded_scripts.insert(base); - if (base.begins_with("local://") || base.is_empty()) { + if (base.is_empty() || base.begins_with("local://")) { continue; } PackedInt32Array bpoints = se->get_breakpoints(); - for (int j = 0; j < bpoints.size(); j++) { - p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1)); + for (int32_t bpoint : bpoints) { + p_breakpoints->push_back(base + ":" + itos((int)bpoint + 1)); } } @@ -1854,8 +1896,8 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { } Array breakpoints = _get_cached_breakpoints_for_script(E); - for (int i = 0; i < breakpoints.size(); i++) { - p_breakpoints->push_back(E + ":" + itos((int)breakpoints[i] + 1)); + for (int breakpoint : breakpoints) { + p_breakpoints->push_back(E + ":" + itos((int)breakpoint + 1)); } } } @@ -2944,8 +2986,8 @@ void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_fi // If Script, update breakpoints with debugger. Array breakpoints = _get_cached_breakpoints_for_script(p_new_file); - for (int i = 0; i < breakpoints.size(); i++) { - int line = (int)breakpoints[i] + 1; + for (int breakpoint : breakpoints) { + int line = (int)breakpoint + 1; EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false); if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) { EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true); @@ -2969,8 +3011,8 @@ void ScriptEditor::_file_removed(const String &p_removed_file) { // Check closed. if (script_editor_cache->has_section(p_removed_file)) { Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file); - for (int i = 0; i < breakpoints.size(); i++) { - EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoints[i] + 1, false); + for (int breakpoint : breakpoints) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoint + 1, false); } script_editor_cache->erase_section(p_removed_file); } @@ -3425,8 +3467,8 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { } Array breakpoints = _get_cached_breakpoints_for_script(E); - for (int i = 0; i < breakpoints.size(); i++) { - EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, true); + for (int breakpoint : breakpoints) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoint + 1, true); } } @@ -3972,6 +4014,7 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto); ClassDB::bind_method("get_current_editor", &ScriptEditor::_get_current_editor); ClassDB::bind_method("get_open_script_editors", &ScriptEditor::_get_open_script_editors); + ClassDB::bind_method("get_breakpoints", &ScriptEditor::_get_breakpoints); ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter); ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 66885790a7..e0fac5d0c6 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -539,6 +539,7 @@ public: _FORCE_INLINE_ bool edit(const Ref<Resource> &p_resource, bool p_grab_focus = true) { return edit(p_resource, -1, 0, p_grab_focus); } bool edit(const Ref<Resource> &p_resource, int p_line, int p_col, bool p_grab_focus = true); + Vector<String> _get_breakpoints(); void get_breakpoints(List<String> *p_breakpoints); PackedStringArray get_unsaved_scripts() const; diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 230d5809aa..dc4d4db3f8 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -47,6 +47,7 @@ #include "scene/3d/physics/physics_body_3d.h" #include "scene/gui/separator.h" #include "scene/gui/texture_rect.h" +#include "scene/property_utils.h" #include "scene/resources/3d/capsule_shape_3d.h" #include "scene/resources/skeleton_profile.h" #include "scene/resources/surface_tool.h" @@ -65,7 +66,7 @@ void BoneTransformEditor::create_editors() { // Position property. position_property = memnew(EditorPropertyVector3()); - position_property->setup(-10000, 10000, 0.001f, true); + position_property->setup(-10000, 10000, 0.001, true); position_property->set_label("Position"); position_property->set_selectable(false); position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); @@ -74,7 +75,7 @@ void BoneTransformEditor::create_editors() { // Rotation property. rotation_property = memnew(EditorPropertyQuaternion()); - rotation_property->setup(-10000, 10000, 0.001f, true); + rotation_property->setup(-10000, 10000, 0.001, true); rotation_property->set_label("Rotation"); rotation_property->set_selectable(false); rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); @@ -83,7 +84,7 @@ void BoneTransformEditor::create_editors() { // Scale property. scale_property = memnew(EditorPropertyVector3()); - scale_property->setup(-10000, 10000, 0.001f, true); + scale_property->setup(-10000, 10000, 0.001, true, true); scale_property->set_label("Scale"); scale_property->set_selectable(false); scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); @@ -97,7 +98,7 @@ void BoneTransformEditor::create_editors() { // Transform/Matrix property. rest_matrix = memnew(EditorPropertyTransform3D()); - rest_matrix->setup(-10000, 10000, 0.001f, true); + rest_matrix->setup(-10000, 10000, 0.001, true); rest_matrix->set_label("Transform"); rest_matrix->set_selectable(false); rest_section->get_vbox()->add_child(rest_matrix); @@ -122,6 +123,13 @@ void BoneTransformEditor::_value_changed(const String &p_property, const Variant undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property)); undo_redo->add_do_property(skeleton, p_property, p_value); + + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + if (se) { + undo_redo->add_do_method(se, "update_joint_tree"); + undo_redo->add_undo_method(se, "update_joint_tree"); + } + undo_redo->commit_action(); } } @@ -189,26 +197,31 @@ void BoneTransformEditor::_update_properties() { if (split[2] == "enabled") { enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); enabled_checkbox->update_property(); + enabled_checkbox->update_editor_property_status(); enabled_checkbox->queue_redraw(); } if (split[2] == "position") { position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); position_property->update_property(); + position_property->update_editor_property_status(); position_property->queue_redraw(); } if (split[2] == "rotation") { rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); rotation_property->update_property(); + rotation_property->update_editor_property_status(); rotation_property->queue_redraw(); } if (split[2] == "scale") { scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); scale_property->update_property(); + scale_property->update_editor_property_status(); scale_property->queue_redraw(); } if (split[2] == "rest") { rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); rest_matrix->update_property(); + rest_matrix->update_editor_property_status(); rest_matrix->queue_redraw(); } } @@ -232,6 +245,11 @@ void Skeleton3DEditor::set_bone_options_enabled(const bool p_bone_options_enable skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled); }; +void Skeleton3DEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("update_all"), &Skeleton3DEditor::update_all); + ClassDB::bind_method(D_METHOD("update_joint_tree"), &Skeleton3DEditor::update_joint_tree); +} + void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { if (!skeleton) { return; @@ -294,6 +312,10 @@ void Skeleton3DEditor::reset_pose(const bool p_all_bones) { ur->add_undo_method(skeleton, "set_bone_pose_scale", selected_bone, skeleton->get_bone_pose_scale(selected_bone)); ur->add_do_method(skeleton, "reset_bone_pose", selected_bone); } + + ur->add_undo_method(this, "update_joint_tree"); + ur->add_do_method(this, "update_joint_tree"); + ur->commit_action(); } @@ -357,6 +379,10 @@ void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) { ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone)); ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone)); } + + ur->add_undo_method(this, "update_joint_tree"); + ur->add_do_method(this, "update_joint_tree"); + ur->commit_action(); } @@ -620,9 +646,12 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se } ur->add_undo_method(skeleton_node, "set_bone_parent", p_selected_boneidx, skeleton_node->get_bone_parent(p_selected_boneidx)); ur->add_do_method(skeleton_node, "set_bone_parent", p_selected_boneidx, p_target_boneidx); + + ur->add_undo_method(this, "update_joint_tree"); + ur->add_do_method(this, "update_joint_tree"); + skeleton_node->set_bone_parent(p_selected_boneidx, p_target_boneidx); - update_joint_tree(); ur->commit_action(); } @@ -655,6 +684,107 @@ void Skeleton3DEditor::_joint_tree_selection_changed() { void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) { } +void Skeleton3DEditor::_joint_tree_button_clicked(Object *p_item, int p_column, int p_id, MouseButton p_button) { + if (!skeleton) { + return; + } + + TreeItem *tree_item = Object::cast_to<TreeItem>(p_item); + if (tree_item) { + String tree_item_metadata = tree_item->get_metadata(0); + + String bone_enabled_property = tree_item_metadata + "/enabled"; + String bone_parent_property = tree_item_metadata + "/parent"; + String bone_name_property = tree_item_metadata + "/name"; + String bone_position_property = tree_item_metadata + "/position"; + String bone_rotation_property = tree_item_metadata + "/rotation"; + String bone_scale_property = tree_item_metadata + "/scale"; + String bone_rest_property = tree_item_metadata + "/rest"; + + Variant current_enabled = skeleton->get(bone_enabled_property); + Variant current_parent = skeleton->get(bone_parent_property); + Variant current_name = skeleton->get(bone_name_property); + Variant current_position = skeleton->get(bone_position_property); + Variant current_rotation = skeleton->get(bone_rotation_property); + Variant current_scale = skeleton->get(bone_scale_property); + Variant current_rest = skeleton->get(bone_rest_property); + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(TTR("Revert Bone")); + + bool can_revert_enabled = EditorPropertyRevert::can_property_revert(skeleton, bone_enabled_property, ¤t_enabled); + if (can_revert_enabled) { + bool is_valid = false; + Variant new_enabled = EditorPropertyRevert::get_property_revert_value(skeleton, bone_enabled_property, &is_valid); + if (is_valid) { + ur->add_undo_method(skeleton, "set", bone_enabled_property, current_enabled); + ur->add_do_method(skeleton, "set", bone_enabled_property, new_enabled); + } + } + + bool can_revert_parent = EditorPropertyRevert::can_property_revert(skeleton, bone_parent_property, ¤t_parent); + if (can_revert_parent) { + bool is_valid = false; + Variant new_parent = EditorPropertyRevert::get_property_revert_value(skeleton, bone_parent_property, &is_valid); + if (is_valid) { + ur->add_undo_method(skeleton, "set", bone_parent_property, current_parent); + ur->add_do_method(skeleton, "set", bone_parent_property, new_parent); + } + } + bool can_revert_name = EditorPropertyRevert::can_property_revert(skeleton, bone_name_property, ¤t_name); + if (can_revert_name) { + bool is_valid = false; + Variant new_name = EditorPropertyRevert::get_property_revert_value(skeleton, bone_name_property, &is_valid); + if (is_valid) { + ur->add_undo_method(skeleton, "set", bone_name_property, current_name); + ur->add_do_method(skeleton, "set", bone_name_property, new_name); + } + } + bool can_revert_position = EditorPropertyRevert::can_property_revert(skeleton, bone_position_property, ¤t_position); + if (can_revert_position) { + bool is_valid = false; + Variant new_position = EditorPropertyRevert::get_property_revert_value(skeleton, bone_position_property, &is_valid); + if (is_valid) { + ur->add_undo_method(skeleton, "set", bone_position_property, current_position); + ur->add_do_method(skeleton, "set", bone_position_property, new_position); + } + } + bool can_revert_rotation = EditorPropertyRevert::can_property_revert(skeleton, bone_rotation_property, ¤t_rotation); + if (can_revert_rotation) { + bool is_valid = false; + Variant new_rotation = EditorPropertyRevert::get_property_revert_value(skeleton, bone_rotation_property, &is_valid); + if (is_valid) { + ur->add_undo_method(skeleton, "set", bone_rotation_property, current_rotation); + ur->add_do_method(skeleton, "set", bone_rotation_property, new_rotation); + } + } + bool can_revert_scale = EditorPropertyRevert::can_property_revert(skeleton, bone_scale_property, ¤t_scale); + if (can_revert_scale) { + bool is_valid = false; + Variant new_scale = EditorPropertyRevert::get_property_revert_value(skeleton, bone_scale_property, &is_valid); + if (is_valid) { + ur->add_undo_method(skeleton, "set", bone_scale_property, current_scale); + ur->add_do_method(skeleton, "set", bone_scale_property, new_scale); + } + } + bool can_revert_rest = EditorPropertyRevert::can_property_revert(skeleton, bone_rest_property, ¤t_rest); + if (can_revert_rest) { + bool is_valid = false; + Variant new_rest = EditorPropertyRevert::get_property_revert_value(skeleton, bone_rest_property, &is_valid); + if (is_valid) { + ur->add_undo_method(skeleton, "set", bone_rest_property, current_rest); + ur->add_do_method(skeleton, "set", bone_rest_property, new_rest); + } + } + + ur->add_undo_method(this, "update_all"); + ur->add_do_method(this, "update_all"); + + ur->commit_action(); + } + return; +} + void Skeleton3DEditor::_update_properties() { if (pose_editor) { pose_editor->_update_properties(); @@ -693,15 +823,52 @@ void Skeleton3DEditor::update_joint_tree() { joint_item->set_selectable(0, true); joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); + String bone_enabled_property = "bones/" + itos(current_bone_idx) + "/enabled"; + String bone_parent_property = "bones/" + itos(current_bone_idx) + "/parent"; + String bone_name_property = "bones/" + itos(current_bone_idx) + "/name"; + String bone_position_property = "bones/" + itos(current_bone_idx) + "/position"; + String bone_rotation_property = "bones/" + itos(current_bone_idx) + "/rotation"; + String bone_scale_property = "bones/" + itos(current_bone_idx) + "/scale"; + String bone_rest_property = "bones/" + itos(current_bone_idx) + "/rest"; + + Variant current_enabled = skeleton->get(bone_enabled_property); + Variant current_parent = skeleton->get(bone_parent_property); + Variant current_name = skeleton->get(bone_name_property); + Variant current_position = skeleton->get(bone_position_property); + Variant current_rotation = skeleton->get(bone_rotation_property); + Variant current_scale = skeleton->get(bone_scale_property); + Variant current_rest = skeleton->get(bone_rest_property); + + bool can_revert_enabled = EditorPropertyRevert::can_property_revert(skeleton, bone_enabled_property, ¤t_enabled); + bool can_revert_parent = EditorPropertyRevert::can_property_revert(skeleton, bone_parent_property, ¤t_parent); + bool can_revert_name = EditorPropertyRevert::can_property_revert(skeleton, bone_name_property, ¤t_name); + bool can_revert_position = EditorPropertyRevert::can_property_revert(skeleton, bone_position_property, ¤t_position); + bool can_revert_rotation = EditorPropertyRevert::can_property_revert(skeleton, bone_rotation_property, ¤t_rotation); + bool can_revert_scale = EditorPropertyRevert::can_property_revert(skeleton, bone_scale_property, ¤t_scale); + bool can_revert_rest = EditorPropertyRevert::can_property_revert(skeleton, bone_rest_property, ¤t_rest); + + if (can_revert_enabled || can_revert_parent || can_revert_name || can_revert_position || can_revert_rotation || can_revert_scale || can_revert_rest) { + joint_item->add_button(0, get_editor_theme_icon(SNAME("ReloadSmall")), JOINT_BUTTON_REVERT, false, TTR("Revert")); + } + // Add the bone's children to the list of bones to be processed. Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx); int child_bone_size = current_bone_child_bones.size(); for (int i = 0; i < child_bone_size; i++) { bones_to_process.push_back(current_bone_child_bones[i]); } + + if (current_bone_idx == selected_bone) { + joint_item->select(0); + } } } +void Skeleton3DEditor::update_all() { + _update_properties(); + update_joint_tree(); +} + void Skeleton3DEditor::create_editors() { set_h_size_flags(SIZE_EXPAND_FILL); set_focus_mode(FOCUS_ALL); @@ -840,6 +1007,7 @@ void Skeleton3DEditor::_notification(int p_what) { joint_tree->connect(SceneStringName(item_selected), callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); joint_tree->connect("item_mouse_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); + joint_tree->connect("button_clicked", callable_mp(this, &Skeleton3DEditor::_joint_tree_button_clicked)); #ifdef TOOLS_ENABLED skeleton->connect(SceneStringName(pose_updated), callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); skeleton->connect(SceneStringName(pose_updated), callable_mp(this, &Skeleton3DEditor::_update_properties)); @@ -1337,6 +1505,10 @@ void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, c ur->add_undo_method(skeleton, "set_bone_pose_scale", p_ids[i], se->get_bone_original_scale()); } } + + ur->add_do_method(se, "update_joint_tree"); + ur->add_undo_method(se, "update_joint_tree"); + ur->commit_action(); } diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 79dc16ae2f..0bb58aac23 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -96,6 +96,8 @@ public: class Skeleton3DEditor : public VBoxContainer { GDCLASS(Skeleton3DEditor, VBoxContainer); + static void _bind_methods(); + friend class Skeleton3DEditorPlugin; enum SkeletonOption { @@ -116,6 +118,10 @@ class Skeleton3DEditor : public VBoxContainer { Skeleton3D *skeleton = nullptr; + enum { + JOINT_BUTTON_REVERT = 0, + }; + Tree *joint_tree = nullptr; BoneTransformEditor *rest_editor = nullptr; BoneTransformEditor *pose_editor = nullptr; @@ -149,6 +155,7 @@ class Skeleton3DEditor : public VBoxContainer { EditorFileDialog *file_export_lib = nullptr; void update_joint_tree(); + void update_all(); void create_editors(); @@ -189,6 +196,7 @@ class Skeleton3DEditor : public VBoxContainer { void _joint_tree_selection_changed(); void _joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button); + void _joint_tree_button_clicked(Object *p_item, int p_column, int p_id, MouseButton p_button); void _update_properties(); void _subgizmo_selection_change(); diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 99635a2531..ea1756b65a 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -2174,8 +2174,26 @@ void ThemeTypeDialog::_add_type_filter_cbk(const String &p_value) { _update_add_type_options(p_value); } +void ThemeTypeDialog::_type_filter_input(const Ref<InputEvent> &p_ie) { + Ref<InputEventKey> k = p_ie; + if (k.is_valid() && k->is_pressed()) { + switch (k->get_keycode()) { + case Key::UP: + case Key::DOWN: + case Key::PAGEUP: + case Key::PAGEDOWN: { + add_type_options->gui_input(k); + add_type_filter->accept_event(); + } break; + default: + break; + } + } +} + void ThemeTypeDialog::_add_type_options_cbk(int p_index) { add_type_filter->set_text(add_type_options->get_item_text(p_index)); + add_type_filter->set_caret_column(add_type_filter->get_text().length()); } void ThemeTypeDialog::_add_type_dialog_entered(const String &p_value) { @@ -2245,6 +2263,7 @@ ThemeTypeDialog::ThemeTypeDialog() { add_type_vb->add_child(add_type_filter); add_type_filter->connect(SceneStringName(text_changed), callable_mp(this, &ThemeTypeDialog::_add_type_filter_cbk)); add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_entered)); + add_type_filter->connect(SceneStringName(gui_input), callable_mp(this, &ThemeTypeDialog::_type_filter_input)); Label *add_type_options_label = memnew(Label); add_type_options_label->set_text(TTR("Available Node-based types:")); diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index ba8e3a30b7..ae92365c32 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -303,6 +303,7 @@ class ThemeTypeDialog : public ConfirmationDialog { void _update_add_type_options(const String &p_filter = ""); void _add_type_filter_cbk(const String &p_value); + void _type_filter_input(const Ref<InputEvent> &p_ie); void _add_type_options_cbk(int p_index); void _add_type_dialog_entered(const String &p_value); void _add_type_dialog_activated(int p_index); diff --git a/editor/plugins/tiles/tile_map_layer_editor.cpp b/editor/plugins/tiles/tile_map_layer_editor.cpp index 7e7d7e3291..7e6746dd3c 100644 --- a/editor/plugins/tiles/tile_map_layer_editor.cpp +++ b/editor/plugins/tiles/tile_map_layer_editor.cpp @@ -86,8 +86,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() { transform_toolbar->show(); tools_settings_vsep_2->show(); random_tile_toggle->show(); - scatter_label->show(); - scatter_spinbox->show(); + scatter_controls_container->set_visible(random_tile_toggle->is_pressed()); } else { tools_settings_vsep->show(); picker_button->show(); @@ -96,8 +95,7 @@ void TileMapLayerEditorTilesPlugin::_update_toolbar() { tools_settings_vsep_2->show(); bucket_contiguous_checkbox->show(); random_tile_toggle->show(); - scatter_label->show(); - scatter_spinbox->show(); + scatter_controls_container->set_visible(random_tile_toggle->is_pressed()); } } diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 610ad3efdf..8e135e7eae 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -46,6 +46,7 @@ #include "editor/editor_translation_parser.h" #include "editor/editor_undo_redo_manager.h" #include "editor/export/editor_export_platform.h" +#include "editor/export/editor_export_platform_extension.h" #include "editor/export/editor_export_platform_pc.h" #include "editor/export/editor_export_plugin.h" #include "editor/filesystem_dock.h" @@ -161,6 +162,8 @@ void register_editor_types() { GDREGISTER_CLASS(EditorExportPlugin); GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform); GDREGISTER_ABSTRACT_CLASS(EditorExportPlatformPC); + GDREGISTER_CLASS(EditorExportPlatformExtension); + GDREGISTER_ABSTRACT_CLASS(EditorExportPreset); register_exporter_types(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 02c8a03ac6..3110ecb926 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -2650,6 +2650,13 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { } } + if (!entire_scene) { + for (const Node *E : remove_list) { + // `move_child` + `get_index` doesn't really work for internal nodes. + ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported."); + } + } + EditorNode::get_singleton()->hide_unused_editors(this); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); @@ -2662,10 +2669,6 @@ void SceneTreeDock::_delete_confirm(bool p_cut) { undo_redo->add_undo_method(scene_tree, "update_tree"); undo_redo->add_undo_reference(edited_scene); } else { - for (const Node *E : remove_list) { - // `move_child` + `get_index` doesn't really work for internal nodes. - ERR_FAIL_COND_MSG(E->get_internal_mode() != INTERNAL_MODE_DISABLED, "Trying to remove internal node, this is not supported."); - } if (delete_tracks_checkbox->is_pressed() || p_cut) { remove_list.sort_custom<Node::Comparator>(); // Sort nodes to keep positions. HashMap<Node *, NodePath> path_renames; diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index f654707630..f5a790353a 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1804,7 +1804,9 @@ void EditorThemeManager::_populate_editor_styles(const Ref<EditorTheme> &p_theme p_theme->set_color("background", EditorStringName(Editor), background_color_opaque); p_theme->set_stylebox("Background", EditorStringName(EditorStyles), make_flat_stylebox(background_color_opaque, p_config.base_margin, p_config.base_margin, p_config.base_margin, p_config.base_margin)); - p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), p_config.base_style); + Ref<StyleBoxFlat> editor_panel_foreground = p_config.base_style->duplicate(); + editor_panel_foreground->set_corner_radius_all(0); + p_theme->set_stylebox("PanelForeground", EditorStringName(EditorStyles), editor_panel_foreground); // Editor focus. p_theme->set_stylebox("Focus", EditorStringName(EditorStyles), p_config.button_style_focus); diff --git a/main/main.cpp b/main/main.cpp index 791562a9bb..101fc642b6 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -3394,7 +3394,8 @@ void Main::setup_boot_logo() { boot_logo.instantiate(); Error load_err = ImageLoader::load_image(boot_logo_path, boot_logo); if (load_err) { - ERR_PRINT("Non-existing or invalid boot splash at '" + boot_logo_path + "'. Loading default splash."); + String msg = (boot_logo_path.ends_with(".png") ? "" : "The only supported format is PNG."); + ERR_PRINT("Non-existing or invalid boot splash at '" + boot_logo_path + +"'. " + msg + " Loading default splash."); } } } else { diff --git a/methods.py b/methods.py index 6d81b35aff..bfd08cfc7b 100644 --- a/methods.py +++ b/methods.py @@ -163,7 +163,7 @@ def add_source_files(self, sources, files, allow_gen=False): def disable_warnings(self): # 'self' is the environment - if self.msvc: + if self.msvc and not using_clang(self): # We have to remove existing warning level defines before appending /w, # otherwise we get: "warning D9025 : overriding '/W3' with '/w'" self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not (x.startswith("/W") or x.startswith("/w"))] @@ -817,21 +817,20 @@ def get_compiler_version(env): "apple_patch3": -1, } - if not env.msvc: - # Not using -dumpversion as some GCC distros only return major, and - # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803 - try: - version = ( - subprocess.check_output([env.subst(env["CXX"]), "--version"], shell=(os.name == "nt")) - .strip() - .decode("utf-8") - ) - except (subprocess.CalledProcessError, OSError): - print_warning("Couldn't parse CXX environment variable to infer compiler version.") - return ret - else: + if env.msvc and not using_clang(env): # TODO: Implement for MSVC return ret + + # Not using -dumpversion as some GCC distros only return major, and + # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803 + try: + version = subprocess.check_output( + [env.subst(env["CXX"]), "--version"], shell=(os.name == "nt"), encoding="utf-8" + ).strip() + except (subprocess.CalledProcessError, OSError): + print_warning("Couldn't parse CXX environment variable to infer compiler version.") + return ret + match = re.search( r"(?:(?<=version )|(?<=\) )|(?<=^))" r"(?P<major>\d+)" diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected index 39dd064012..851ef69261 100644 --- a/misc/extension_api_validation/4.3-stable.expected +++ b/misc/extension_api_validation/4.3-stable.expected @@ -56,3 +56,12 @@ Validate extension JSON: Error: Field 'classes/RegEx/methods/compile/arguments': Validate extension JSON: Error: Field 'classes/RegEx/methods/create_from_string/arguments': size changed value in new API, from 1 to 2. Add optional argument to control error printing on compilation fail. Compatibility methods registered. + + +GH-95375 +-------- +Validate extension JSON: Error: Field 'classes/AudioStreamPlayer/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing". +Validate extension JSON: Error: Field 'classes/AudioStreamPlayer2D/properties/playing': setter changed value in new API, from "_set_playing" to &"set_playing". +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. 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/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index c72755642b..e3f2a61090 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -57,6 +57,7 @@ #include "scene/scene_string_names.h" #ifdef TOOLS_ENABLED +#include "core/extension/gdextension_manager.h" #include "editor/editor_paths.h" #endif @@ -953,7 +954,8 @@ bool GDScript::_get(const StringName &p_name, Variant &r_ret) const { if (E) { if (likely(top->valid) && E->value.getter) { Callable::CallError ce; - r_ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce); + const Variant ret = const_cast<GDScript *>(this)->callp(E->value.getter, nullptr, 0, ce); + r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant(); return true; } r_ret = top->static_variables[E->value.index]; @@ -1726,10 +1728,9 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { if (E) { if (likely(script->valid) && E->value.getter) { Callable::CallError err; - r_ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err); - if (err.error == Callable::CallError::CALL_OK) { - return true; - } + const Variant ret = const_cast<GDScriptInstance *>(this)->callp(E->value.getter, nullptr, 0, err); + r_ret = (err.error == Callable::CallError::CALL_OK) ? ret : Variant(); + return true; } r_ret = members[E->value.index]; return true; @@ -1751,7 +1752,8 @@ bool GDScriptInstance::get(const StringName &p_name, Variant &r_ret) const { if (E) { if (likely(sptr->valid) && E->value.getter) { Callable::CallError ce; - r_ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce); + const Variant ret = const_cast<GDScript *>(sptr)->callp(E->value.getter, nullptr, 0, ce); + r_ret = (ce.error == Callable::CallError::CALL_OK) ? ret : Variant(); return true; } r_ret = sptr->static_variables[E->value.index]; @@ -2177,9 +2179,26 @@ void GDScriptLanguage::_add_global(const StringName &p_name, const Variant &p_va global_array.write[globals[p_name]] = p_value; return; } - globals[p_name] = global_array.size(); - global_array.push_back(p_value); - _global_array = global_array.ptrw(); + + if (global_array_empty_indexes.size()) { + int index = global_array_empty_indexes[global_array_empty_indexes.size() - 1]; + globals[p_name] = index; + global_array.write[index] = p_value; + global_array_empty_indexes.resize(global_array_empty_indexes.size() - 1); + } else { + globals[p_name] = global_array.size(); + global_array.push_back(p_value); + _global_array = global_array.ptrw(); + } +} + +void GDScriptLanguage::_remove_global(const StringName &p_name) { + if (!globals.has(p_name)) { + return; + } + global_array_empty_indexes.push_back(globals[p_name]); + global_array.write[globals[p_name]] = Variant::NIL; + globals.erase(p_name); } void GDScriptLanguage::add_global_constant(const StringName &p_variable, const Variant &p_value) { @@ -2237,11 +2256,40 @@ void GDScriptLanguage::init() { _add_global(E.name, E.ptr); } +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + GDExtensionManager::get_singleton()->connect("extension_loaded", callable_mp(this, &GDScriptLanguage::_extension_loaded)); + GDExtensionManager::get_singleton()->connect("extension_unloading", callable_mp(this, &GDScriptLanguage::_extension_unloading)); + } +#endif + #ifdef TESTS_ENABLED GDScriptTests::GDScriptTestRunner::handle_cmdline(); #endif } +#ifdef TOOLS_ENABLED +void GDScriptLanguage::_extension_loaded(const Ref<GDExtension> &p_extension) { + List<StringName> class_list; + ClassDB::get_extension_class_list(p_extension, &class_list); + for (const StringName &n : class_list) { + if (globals.has(n)) { + continue; + } + Ref<GDScriptNativeClass> nc = memnew(GDScriptNativeClass(n)); + _add_global(n, nc); + } +} + +void GDScriptLanguage::_extension_unloading(const Ref<GDExtension> &p_extension) { + List<StringName> class_list; + ClassDB::get_extension_class_list(p_extension, &class_list); + for (const StringName &n : class_list) { + _remove_global(n); + } +} +#endif + String GDScriptLanguage::get_type() const { return "GDScript"; } diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 6527a0ea4d..4d21651365 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -417,6 +417,7 @@ class GDScriptLanguage : public ScriptLanguage { Vector<Variant> global_array; HashMap<StringName, int> globals; HashMap<StringName, Variant> named_globals; + Vector<int> global_array_empty_indexes; struct CallLevel { Variant *stack = nullptr; @@ -448,6 +449,7 @@ class GDScriptLanguage : public ScriptLanguage { int _debug_max_call_stack = 0; void _add_global(const StringName &p_name, const Variant &p_value); + void _remove_global(const StringName &p_name); friend class GDScriptInstance; @@ -467,6 +469,11 @@ class GDScriptLanguage : public ScriptLanguage { HashMap<String, ObjectID> orphan_subclasses; +#ifdef TOOLS_ENABLED + void _extension_loaded(const Ref<GDExtension> &p_extension); + void _extension_unloading(const Ref<GDExtension> &p_extension); +#endif + public: int calls; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index c4cef0287f..582305d900 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -260,6 +260,7 @@ void GDScriptParser::override_completion_context(const Node *p_for_node, Complet context.current_line = tokenizer->get_cursor_line(); context.current_argument = p_argument; context.node = p_node; + context.parser = this; completion_context = context; } diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd index efd8ad6edb..60bcde4b8c 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd @@ -3,14 +3,13 @@ const const_color: Color = 'red' func func_color(arg_color: Color = 'blue') -> bool: return arg_color == Color.BLUE -@warning_ignore("assert_always_true") func test(): - assert(const_color == Color.RED) + Utils.check(const_color == Color.RED) - assert(func_color() == true) - assert(func_color('blue') == true) + Utils.check(func_color() == true) + Utils.check(func_color('blue') == true) var var_color: Color = 'green' - assert(var_color == Color.GREEN) + Utils.check(var_color == Color.GREEN) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd index bed9dd0e96..5318d11f33 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd @@ -5,20 +5,19 @@ const const_float_cast: float = 76 as float const const_packed_empty: PackedFloat64Array = [] const const_packed_ints: PackedFloat64Array = [52] -@warning_ignore("assert_always_true") func test(): - assert(typeof(const_float_int) == TYPE_FLOAT) - assert(str(const_float_int) == '19') - assert(typeof(const_float_plus) == TYPE_FLOAT) - assert(str(const_float_plus) == '34') - assert(typeof(const_float_cast) == TYPE_FLOAT) - assert(str(const_float_cast) == '76') + Utils.check(typeof(const_float_int) == TYPE_FLOAT) + Utils.check(str(const_float_int) == '19') + Utils.check(typeof(const_float_plus) == TYPE_FLOAT) + Utils.check(str(const_float_plus) == '34') + Utils.check(typeof(const_float_cast) == TYPE_FLOAT) + Utils.check(str(const_float_cast) == '76') - assert(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY) - assert(str(const_packed_empty) == '[]') - assert(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY) - assert(str(const_packed_ints) == '[52]') - assert(typeof(const_packed_ints[0]) == TYPE_FLOAT) - assert(str(const_packed_ints[0]) == '52') + Utils.check(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY) + Utils.check(str(const_packed_empty) == '[]') + Utils.check(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY) + Utils.check(str(const_packed_ints) == '[52]') + Utils.check(typeof(const_packed_ints[0]) == TYPE_FLOAT) + Utils.check(str(const_packed_ints[0]) == '52') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd index d2d9d04508..a569488d3c 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/enums_in_range_call.gd @@ -5,5 +5,5 @@ func test(): for value in range(E.E0, E.E3): var inferable := value total += inferable - assert(total == 0 + 1 + 2) + Utils.check(total == 0 + 1 + 2) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd index 39f490c4b3..ec89226328 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/export_enum_as_dictionary.gd @@ -2,8 +2,6 @@ class_name TestExportEnumAsDictionary enum MyEnum {A, B, C} -const Utils = preload("../../utils.notest.gd") - @export var test_1 = MyEnum @export var test_2 = MyEnum.A @export var test_3 := MyEnum diff --git a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd index 4a7f10f1ee..9ce0782d5c 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/for_range_usage.gd @@ -3,5 +3,5 @@ func test(): var result := '' for i in range(array.size(), 0, -1): result += str(array[i - 1]) - assert(result == '963') + Utils.check(result == '963') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd index d678f3acfc..e0cbdacb38 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/function_match_parent_signature_with_extra_parameters.gd @@ -2,11 +2,11 @@ func test(): var instance := Parent.new() var result := instance.my_function(1) print(result) - assert(result == 1) + Utils.check(result == 1) instance = Child.new() result = instance.my_function(2) print(result) - assert(result == 0) + Utils.check(result == 0) class Parent: func my_function(par1: int) -> int: diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd index 0b1576e66e..cbe8e9da34 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd @@ -8,27 +8,27 @@ func convert_var_array_to_packed() -> PackedStringArray: var array := ['79']; re func test(): var converted_literal_int := convert_literal_int_to_float() - assert(typeof(converted_literal_int) == TYPE_FLOAT) - assert(converted_literal_int == 76.0) + Utils.check(typeof(converted_literal_int) == TYPE_FLOAT) + Utils.check(converted_literal_int == 76.0) var converted_arg_int := convert_arg_int_to_float(36) - assert(typeof(converted_arg_int) == TYPE_FLOAT) - assert(converted_arg_int == 36.0) + Utils.check(typeof(converted_arg_int) == TYPE_FLOAT) + Utils.check(converted_arg_int == 36.0) var converted_var_int := convert_var_int_to_float() - assert(typeof(converted_var_int) == TYPE_FLOAT) - assert(converted_var_int == 59.0) + Utils.check(typeof(converted_var_int) == TYPE_FLOAT) + Utils.check(converted_var_int == 59.0) var converted_literal_array := convert_literal_array_to_packed() - assert(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY) - assert(str(converted_literal_array) == '["46"]') + Utils.check(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY) + Utils.check(str(converted_literal_array) == '["46"]') var converted_arg_array := convert_arg_array_to_packed(['91']) - assert(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY) - assert(str(converted_arg_array) == '["91"]') + Utils.check(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY) + Utils.check(str(converted_arg_array) == '["91"]') var converted_var_array := convert_var_array_to_packed() - assert(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY) - assert(str(converted_var_array) == '["79"]') + Utils.check(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY) + Utils.check(str(converted_var_array) == '["79"]') print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd index 44ca5f4dd0..d49acaacd3 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd @@ -2,7 +2,7 @@ func test(): var left_hard_int := 1 var right_hard_int := 2 var result_hard_int := left_hard_int if true else right_hard_int - assert(result_hard_int == 1) + Utils.check(result_hard_int == 1) @warning_ignore("inference_on_variant") var left_hard_variant := 1 as Variant @@ -10,6 +10,6 @@ func test(): var right_hard_variant := 2.0 as Variant @warning_ignore("inference_on_variant") var result_hard_variant := left_hard_variant if true else right_hard_variant - assert(result_hard_variant == 1) + Utils.check(result_hard_variant == 1) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd index 12dc0b93df..ee30f01dfb 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/type_test_usage.gd @@ -4,124 +4,123 @@ class A extends RefCounted: class B extends A: pass -@warning_ignore("assert_always_true") func test(): var builtin: Variant = 3 - assert((builtin is Variant) == true) - assert((builtin is int) == true) - assert(is_instance_of(builtin, TYPE_INT) == true) - assert((builtin is float) == false) - assert(is_instance_of(builtin, TYPE_FLOAT) == false) + Utils.check((builtin is Variant) == true) + Utils.check((builtin is int) == true) + Utils.check(is_instance_of(builtin, TYPE_INT) == true) + Utils.check((builtin is float) == false) + Utils.check(is_instance_of(builtin, TYPE_FLOAT) == false) const const_builtin: Variant = 3 - assert((const_builtin is Variant) == true) - assert((const_builtin is int) == true) - assert(is_instance_of(const_builtin, TYPE_INT) == true) - assert((const_builtin is float) == false) - assert(is_instance_of(const_builtin, TYPE_FLOAT) == false) + Utils.check((const_builtin is Variant) == true) + Utils.check((const_builtin is int) == true) + Utils.check(is_instance_of(const_builtin, TYPE_INT) == true) + Utils.check((const_builtin is float) == false) + Utils.check(is_instance_of(const_builtin, TYPE_FLOAT) == false) var int_array: Variant = [] as Array[int] - assert((int_array is Variant) == true) - assert((int_array is Array) == true) - assert(is_instance_of(int_array, TYPE_ARRAY) == true) - assert((int_array is Array[int]) == true) - assert((int_array is Array[float]) == false) - assert((int_array is int) == false) - assert(is_instance_of(int_array, TYPE_INT) == false) + Utils.check((int_array is Variant) == true) + Utils.check((int_array is Array) == true) + Utils.check(is_instance_of(int_array, TYPE_ARRAY) == true) + Utils.check((int_array is Array[int]) == true) + Utils.check((int_array is Array[float]) == false) + Utils.check((int_array is int) == false) + Utils.check(is_instance_of(int_array, TYPE_INT) == false) var const_int_array: Variant = [] as Array[int] - assert((const_int_array is Variant) == true) - assert((const_int_array is Array) == true) - assert(is_instance_of(const_int_array, TYPE_ARRAY) == true) - assert((const_int_array is Array[int]) == true) - assert((const_int_array is Array[float]) == false) - assert((const_int_array is int) == false) - assert(is_instance_of(const_int_array, TYPE_INT) == false) + Utils.check((const_int_array is Variant) == true) + Utils.check((const_int_array is Array) == true) + Utils.check(is_instance_of(const_int_array, TYPE_ARRAY) == true) + Utils.check((const_int_array is Array[int]) == true) + Utils.check((const_int_array is Array[float]) == false) + Utils.check((const_int_array is int) == false) + Utils.check(is_instance_of(const_int_array, TYPE_INT) == false) var b_array: Variant = [] as Array[B] - assert((b_array is Variant) == true) - assert((b_array is Array) == true) - assert(is_instance_of(b_array, TYPE_ARRAY) == true) - assert((b_array is Array[B]) == true) - assert((b_array is Array[A]) == false) - assert((b_array is Array[int]) == false) - assert((b_array is int) == false) - assert(is_instance_of(b_array, TYPE_INT) == false) + Utils.check((b_array is Variant) == true) + Utils.check((b_array is Array) == true) + Utils.check(is_instance_of(b_array, TYPE_ARRAY) == true) + Utils.check((b_array is Array[B]) == true) + Utils.check((b_array is Array[A]) == false) + Utils.check((b_array is Array[int]) == false) + Utils.check((b_array is int) == false) + Utils.check(is_instance_of(b_array, TYPE_INT) == false) var const_b_array: Variant = [] as Array[B] - assert((const_b_array is Variant) == true) - assert((const_b_array is Array) == true) - assert(is_instance_of(const_b_array, TYPE_ARRAY) == true) - assert((const_b_array is Array[B]) == true) - assert((const_b_array is Array[A]) == false) - assert((const_b_array is Array[int]) == false) - assert((const_b_array is int) == false) - assert(is_instance_of(const_b_array, TYPE_INT) == false) + Utils.check((const_b_array is Variant) == true) + Utils.check((const_b_array is Array) == true) + Utils.check(is_instance_of(const_b_array, TYPE_ARRAY) == true) + Utils.check((const_b_array is Array[B]) == true) + Utils.check((const_b_array is Array[A]) == false) + Utils.check((const_b_array is Array[int]) == false) + Utils.check((const_b_array is int) == false) + Utils.check(is_instance_of(const_b_array, TYPE_INT) == false) var native: Variant = RefCounted.new() - assert((native is Variant) == true) - assert((native is Object) == true) - assert(is_instance_of(native, TYPE_OBJECT) == true) - assert(is_instance_of(native, Object) == true) - assert((native is RefCounted) == true) - assert(is_instance_of(native, RefCounted) == true) - assert((native is Node) == false) - assert(is_instance_of(native, Node) == false) - assert((native is int) == false) - assert(is_instance_of(native, TYPE_INT) == false) + Utils.check((native is Variant) == true) + Utils.check((native is Object) == true) + Utils.check(is_instance_of(native, TYPE_OBJECT) == true) + Utils.check(is_instance_of(native, Object) == true) + Utils.check((native is RefCounted) == true) + Utils.check(is_instance_of(native, RefCounted) == true) + Utils.check((native is Node) == false) + Utils.check(is_instance_of(native, Node) == false) + Utils.check((native is int) == false) + Utils.check(is_instance_of(native, TYPE_INT) == false) var a_script: Variant = A.new() - assert((a_script is Variant) == true) - assert((a_script is Object) == true) - assert(is_instance_of(a_script, TYPE_OBJECT) == true) - assert(is_instance_of(a_script, Object) == true) - assert((a_script is RefCounted) == true) - assert(is_instance_of(a_script, RefCounted) == true) - assert((a_script is A) == true) - assert(is_instance_of(a_script, A) == true) - assert((a_script is B) == false) - assert(is_instance_of(a_script, B) == false) - assert((a_script is Node) == false) - assert(is_instance_of(a_script, Node) == false) - assert((a_script is int) == false) - assert(is_instance_of(a_script, TYPE_INT) == false) + Utils.check((a_script is Variant) == true) + Utils.check((a_script is Object) == true) + Utils.check(is_instance_of(a_script, TYPE_OBJECT) == true) + Utils.check(is_instance_of(a_script, Object) == true) + Utils.check((a_script is RefCounted) == true) + Utils.check(is_instance_of(a_script, RefCounted) == true) + Utils.check((a_script is A) == true) + Utils.check(is_instance_of(a_script, A) == true) + Utils.check((a_script is B) == false) + Utils.check(is_instance_of(a_script, B) == false) + Utils.check((a_script is Node) == false) + Utils.check(is_instance_of(a_script, Node) == false) + Utils.check((a_script is int) == false) + Utils.check(is_instance_of(a_script, TYPE_INT) == false) var b_script: Variant = B.new() - assert((b_script is Variant) == true) - assert((b_script is Object) == true) - assert(is_instance_of(b_script, TYPE_OBJECT) == true) - assert(is_instance_of(b_script, Object) == true) - assert((b_script is RefCounted) == true) - assert(is_instance_of(b_script, RefCounted) == true) - assert((b_script is A) == true) - assert(is_instance_of(b_script, A) == true) - assert((b_script is B) == true) - assert(is_instance_of(b_script, B) == true) - assert((b_script is Node) == false) - assert(is_instance_of(b_script, Node) == false) - assert((b_script is int) == false) - assert(is_instance_of(b_script, TYPE_INT) == false) + Utils.check((b_script is Variant) == true) + Utils.check((b_script is Object) == true) + Utils.check(is_instance_of(b_script, TYPE_OBJECT) == true) + Utils.check(is_instance_of(b_script, Object) == true) + Utils.check((b_script is RefCounted) == true) + Utils.check(is_instance_of(b_script, RefCounted) == true) + Utils.check((b_script is A) == true) + Utils.check(is_instance_of(b_script, A) == true) + Utils.check((b_script is B) == true) + Utils.check(is_instance_of(b_script, B) == true) + Utils.check((b_script is Node) == false) + Utils.check(is_instance_of(b_script, Node) == false) + Utils.check((b_script is int) == false) + Utils.check(is_instance_of(b_script, TYPE_INT) == false) var var_null: Variant = null - assert((var_null is Variant) == true) - assert((var_null is int) == false) - assert(is_instance_of(var_null, TYPE_INT) == false) - assert((var_null is Object) == false) - assert(is_instance_of(var_null, TYPE_OBJECT) == false) - assert((var_null is RefCounted) == false) - assert(is_instance_of(var_null, RefCounted) == false) - assert((var_null is A) == false) - assert(is_instance_of(var_null, A) == false) + Utils.check((var_null is Variant) == true) + Utils.check((var_null is int) == false) + Utils.check(is_instance_of(var_null, TYPE_INT) == false) + Utils.check((var_null is Object) == false) + Utils.check(is_instance_of(var_null, TYPE_OBJECT) == false) + Utils.check((var_null is RefCounted) == false) + Utils.check(is_instance_of(var_null, RefCounted) == false) + Utils.check((var_null is A) == false) + Utils.check(is_instance_of(var_null, A) == false) const const_null: Variant = null - assert((const_null is Variant) == true) - assert((const_null is int) == false) - assert(is_instance_of(const_null, TYPE_INT) == false) - assert((const_null is Object) == false) - assert(is_instance_of(const_null, TYPE_OBJECT) == false) - assert((const_null is RefCounted) == false) - assert(is_instance_of(const_null, RefCounted) == false) - assert((const_null is A) == false) - assert(is_instance_of(const_null, A) == false) + Utils.check((const_null is Variant) == true) + Utils.check((const_null is int) == false) + Utils.check(is_instance_of(const_null, TYPE_INT) == false) + Utils.check((const_null is Object) == false) + Utils.check(is_instance_of(const_null, TYPE_OBJECT) == false) + Utils.check((const_null is RefCounted) == false) + Utils.check(is_instance_of(const_null, RefCounted) == false) + Utils.check((const_null is A) == false) + Utils.check(is_instance_of(const_null, A) == false) print('ok') diff --git a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd index b000c82717..fe0274c27b 100644 --- a/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd +++ b/modules/gdscript/tests/scripts/analyzer/features/typed_array_usage.gd @@ -10,206 +10,205 @@ class Members: var two: Array[int] = one func check_passing() -> bool: - assert(str(one) == '[104]') - assert(str(two) == '[104]') + Utils.check(str(one) == '[104]') + Utils.check(str(two) == '[104]') two.push_back(582) - assert(str(one) == '[104, 582]') - assert(str(two) == '[104, 582]') + Utils.check(str(one) == '[104, 582]') + Utils.check(str(two) == '[104, 582]') two = [486] - assert(str(one) == '[104, 582]') - assert(str(two) == '[486]') + Utils.check(str(one) == '[104, 582]') + Utils.check(str(two) == '[486]') return true @warning_ignore("unsafe_method_access") -@warning_ignore("assert_always_true") @warning_ignore("return_value_discarded") func test(): var untyped_basic = [459] - assert(str(untyped_basic) == '[459]') - assert(untyped_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(untyped_basic) == '[459]') + Utils.check(untyped_basic.get_typed_builtin() == TYPE_NIL) var inferred_basic := [366] - assert(str(inferred_basic) == '[366]') - assert(inferred_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(inferred_basic) == '[366]') + Utils.check(inferred_basic.get_typed_builtin() == TYPE_NIL) var typed_basic: Array = [521] - assert(str(typed_basic) == '[521]') - assert(typed_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(typed_basic) == '[521]') + Utils.check(typed_basic.get_typed_builtin() == TYPE_NIL) var empty_floats: Array[float] = [] - assert(str(empty_floats) == '[]') - assert(empty_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(empty_floats) == '[]') + Utils.check(empty_floats.get_typed_builtin() == TYPE_FLOAT) untyped_basic = empty_floats - assert(untyped_basic.get_typed_builtin() == TYPE_FLOAT) + Utils.check(untyped_basic.get_typed_builtin() == TYPE_FLOAT) inferred_basic = empty_floats - assert(inferred_basic.get_typed_builtin() == TYPE_FLOAT) + Utils.check(inferred_basic.get_typed_builtin() == TYPE_FLOAT) typed_basic = empty_floats - assert(typed_basic.get_typed_builtin() == TYPE_FLOAT) + Utils.check(typed_basic.get_typed_builtin() == TYPE_FLOAT) empty_floats.push_back(705.0) untyped_basic.push_back(430.0) inferred_basic.push_back(263.0) typed_basic.push_back(518.0) - assert(str(empty_floats) == '[705, 430, 263, 518]') - assert(str(untyped_basic) == '[705, 430, 263, 518]') - assert(str(inferred_basic) == '[705, 430, 263, 518]') - assert(str(typed_basic) == '[705, 430, 263, 518]') + Utils.check(str(empty_floats) == '[705, 430, 263, 518]') + Utils.check(str(untyped_basic) == '[705, 430, 263, 518]') + Utils.check(str(inferred_basic) == '[705, 430, 263, 518]') + Utils.check(str(typed_basic) == '[705, 430, 263, 518]') const constant_float := 950.0 const constant_int := 170 var typed_float := 954.0 var filled_floats: Array[float] = [constant_float, constant_int, typed_float, empty_floats[1] + empty_floats[2]] - assert(str(filled_floats) == '[950, 170, 954, 693]') - assert(filled_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(filled_floats) == '[950, 170, 954, 693]') + Utils.check(filled_floats.get_typed_builtin() == TYPE_FLOAT) var casted_floats := [empty_floats[2] * 2] as Array[float] - assert(str(casted_floats) == '[526]') - assert(casted_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(casted_floats) == '[526]') + Utils.check(casted_floats.get_typed_builtin() == TYPE_FLOAT) var returned_floats = (func () -> Array[float]: return [554]).call() - assert(str(returned_floats) == '[554]') - assert(returned_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(returned_floats) == '[554]') + Utils.check(returned_floats.get_typed_builtin() == TYPE_FLOAT) var passed_floats = floats_identity([663.0 if randf() > 0.5 else 663.0]) - assert(str(passed_floats) == '[663]') - assert(passed_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(passed_floats) == '[663]') + Utils.check(passed_floats.get_typed_builtin() == TYPE_FLOAT) var default_floats = (func (floats: Array[float] = [364.0]): return floats).call() - assert(str(default_floats) == '[364]') - assert(default_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(default_floats) == '[364]') + Utils.check(default_floats.get_typed_builtin() == TYPE_FLOAT) var typed_int := 556 var converted_floats: Array[float] = [typed_int] converted_floats.push_back(498) - assert(str(converted_floats) == '[556, 498]') - assert(converted_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(converted_floats) == '[556, 498]') + Utils.check(converted_floats.get_typed_builtin() == TYPE_FLOAT) const constant_basic = [228] - assert(str(constant_basic) == '[228]') - assert(constant_basic.get_typed_builtin() == TYPE_NIL) + Utils.check(str(constant_basic) == '[228]') + Utils.check(constant_basic.get_typed_builtin() == TYPE_NIL) const constant_floats: Array[float] = [constant_float - constant_basic[0] - constant_int] - assert(str(constant_floats) == '[552]') - assert(constant_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(constant_floats) == '[552]') + Utils.check(constant_floats.get_typed_builtin() == TYPE_FLOAT) var source_floats: Array[float] = [999.74] untyped_basic = source_floats var destination_floats: Array[float] = untyped_basic destination_floats[0] -= 0.74 - assert(str(source_floats) == '[999]') - assert(str(untyped_basic) == '[999]') - assert(str(destination_floats) == '[999]') - assert(destination_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(source_floats) == '[999]') + Utils.check(str(untyped_basic) == '[999]') + Utils.check(str(destination_floats) == '[999]') + Utils.check(destination_floats.get_typed_builtin() == TYPE_FLOAT) var duplicated_floats := empty_floats.duplicate().slice(2, 3) duplicated_floats[0] *= 3 - assert(str(duplicated_floats) == '[789]') - assert(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) + Utils.check(str(duplicated_floats) == '[789]') + Utils.check(duplicated_floats.get_typed_builtin() == TYPE_FLOAT) var b_objects: Array[B] = [B.new(), B.new() as A, null] - assert(b_objects.size() == 3) - assert(b_objects.get_typed_builtin() == TYPE_OBJECT) - assert(b_objects.get_typed_script() == B) + Utils.check(b_objects.size() == 3) + Utils.check(b_objects.get_typed_builtin() == TYPE_OBJECT) + Utils.check(b_objects.get_typed_script() == B) var a_objects: Array[A] = [A.new(), B.new(), null, b_objects[0]] - assert(a_objects.size() == 4) - assert(a_objects.get_typed_builtin() == TYPE_OBJECT) - assert(a_objects.get_typed_script() == A) + Utils.check(a_objects.size() == 4) + Utils.check(a_objects.get_typed_builtin() == TYPE_OBJECT) + Utils.check(a_objects.get_typed_script() == A) var a_passed = (func check_a_passing(p_objects: Array[A]): return p_objects.size()).call(a_objects) - assert(a_passed == 4) + Utils.check(a_passed == 4) var b_passed = (func check_b_passing(basic: Array): return basic[0] != null).call(b_objects) - assert(b_passed == true) + Utils.check(b_passed == true) var empty_strings: Array[String] = [] var empty_bools: Array[bool] = [] var empty_basic_one := [] var empty_basic_two := [] - assert(empty_strings == empty_bools) - assert(empty_basic_one == empty_basic_two) - assert(empty_strings.hash() == empty_bools.hash()) - assert(empty_basic_one.hash() == empty_basic_two.hash()) + 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: Array[int] = [527] var assign_target: Array[int] = [] assign_target.assign(assign_source) - assert(str(assign_source) == '[527]') - assert(str(assign_target) == '[527]') + Utils.check(str(assign_source) == '[527]') + Utils.check(str(assign_target) == '[527]') assign_source.push_back(657) - assert(str(assign_source) == '[527, 657]') - assert(str(assign_target) == '[527]') + Utils.check(str(assign_source) == '[527, 657]') + Utils.check(str(assign_target) == '[527]') var defaults_passed = (func check_defaults_passing(one: Array[int] = [], two := one): one.push_back(887) two.push_back(198) - assert(str(one) == '[887, 198]') - assert(str(two) == '[887, 198]') + Utils.check(str(one) == '[887, 198]') + Utils.check(str(two) == '[887, 198]') two = [130] - assert(str(one) == '[887, 198]') - assert(str(two) == '[130]') + Utils.check(str(one) == '[887, 198]') + Utils.check(str(two) == '[130]') return true ).call() - assert(defaults_passed == true) + Utils.check(defaults_passed == true) var members := Members.new() var members_passed := members.check_passing() - assert(members_passed == true) + Utils.check(members_passed == true) var resized_basic: Array = [] resized_basic.resize(1) - assert(typeof(resized_basic[0]) == TYPE_NIL) - assert(resized_basic[0] == null) + Utils.check(typeof(resized_basic[0]) == TYPE_NIL) + Utils.check(resized_basic[0] == null) var resized_ints: Array[int] = [] resized_ints.resize(1) - assert(typeof(resized_ints[0]) == TYPE_INT) - assert(resized_ints[0] == 0) + Utils.check(typeof(resized_ints[0]) == TYPE_INT) + Utils.check(resized_ints[0] == 0) var resized_arrays: Array[Array] = [] resized_arrays.resize(1) - assert(typeof(resized_arrays[0]) == TYPE_ARRAY) + Utils.check(typeof(resized_arrays[0]) == TYPE_ARRAY) resized_arrays[0].resize(1) resized_arrays[0][0] = 523 - assert(str(resized_arrays) == '[[523]]') + Utils.check(str(resized_arrays) == '[[523]]') var resized_objects: Array[Object] = [] resized_objects.resize(1) - assert(typeof(resized_objects[0]) == TYPE_NIL) - assert(resized_objects[0] == null) + Utils.check(typeof(resized_objects[0]) == TYPE_NIL) + Utils.check(resized_objects[0] == null) var typed_enums: Array[E] = [] typed_enums.resize(1) - assert(str(typed_enums) == '[0]') + Utils.check(str(typed_enums) == '[0]') typed_enums[0] = E.E0 - assert(str(typed_enums) == '[391]') - assert(typed_enums.get_typed_builtin() == TYPE_INT) + Utils.check(str(typed_enums) == '[391]') + Utils.check(typed_enums.get_typed_builtin() == TYPE_INT) const const_enums: Array[E] = [] - assert(const_enums.get_typed_builtin() == TYPE_INT) - assert(const_enums.get_typed_class_name() == &'') + Utils.check(const_enums.get_typed_builtin() == TYPE_INT) + Utils.check(const_enums.get_typed_class_name() == &'') var a := A.new() var typed_natives: Array[RefCounted] = [a] var typed_scripts = Array(typed_natives, TYPE_OBJECT, "RefCounted", A) - assert(typed_scripts[0] == a) + Utils.check(typed_scripts[0] == a) print('ok') diff --git a/modules/gdscript/tests/scripts/parser/features/annotations.gd b/modules/gdscript/tests/scripts/parser/features/annotations.gd index 7a7d6d953e..1e5d3fdcad 100644 --- a/modules/gdscript/tests/scripts/parser/features/annotations.gd +++ b/modules/gdscript/tests/scripts/parser/features/annotations.gd @@ -1,7 +1,5 @@ extends Node -const Utils = preload("../../utils.notest.gd") - @export_enum("A", "B", "C") var test_1 @export_enum("A", "B", "C",) var test_2 diff --git a/modules/gdscript/tests/scripts/parser/features/class.gd b/modules/gdscript/tests/scripts/parser/features/class.gd index af24b32322..482d04a63b 100644 --- a/modules/gdscript/tests/scripts/parser/features/class.gd +++ b/modules/gdscript/tests/scripts/parser/features/class.gd @@ -18,8 +18,8 @@ func test(): test_instance.number = 42 var test_sub = TestSub.new() - assert(test_sub.number == 25) # From Test. - assert(test_sub.other_string == "bye") # From TestSub. + Utils.check(test_sub.number == 25) # From Test. + Utils.check(test_sub.other_string == "bye") # From TestSub. var _test_constructor = TestConstructor.new() _test_constructor = TestConstructor.new(500) diff --git a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd index 0d97135a7b..cfda255905 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_arrays.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_arrays.gd @@ -1,5 +1,3 @@ -const Utils = preload("../../utils.notest.gd") - @export_dir var test_dir: Array[String] @export_dir var test_dir_packed: PackedStringArray @export_file var test_file: Array[String] diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd index 7f0737f4db..d50f0b2528 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_enum.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd @@ -1,5 +1,3 @@ -const Utils = preload("../../utils.notest.gd") - @export_enum("Red", "Green", "Blue") var test_untyped @export_enum("Red:10", "Green:20", "Blue:30") var test_with_values diff --git a/modules/gdscript/tests/scripts/parser/features/export_variable.gd b/modules/gdscript/tests/scripts/parser/features/export_variable.gd index 8bcb2bcb9a..1e134d0e0e 100644 --- a/modules/gdscript/tests/scripts/parser/features/export_variable.gd +++ b/modules/gdscript/tests/scripts/parser/features/export_variable.gd @@ -1,7 +1,6 @@ class_name ExportVariableTest extends Node -const Utils = preload("../../utils.notest.gd") const PreloadedGlobalClass = preload("./export_variable_global.notest.gd") const PreloadedUnnamedClass = preload("./export_variable_unnamed.notest.gd") diff --git a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd index 2fa45c1d7d..0ec118b6b7 100644 --- a/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd +++ b/modules/gdscript/tests/scripts/parser/features/good_continue_in_lambda.gd @@ -9,5 +9,5 @@ func test(): j_string += str(j) return j_string i_string += lambda.call() - assert(i_string == '0202') + Utils.check(i_string == '0202') print('ok') diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.gd b/modules/gdscript/tests/scripts/parser/features/truthiness.gd index 9c67a152f5..736cda7f74 100644 --- a/modules/gdscript/tests/scripts/parser/features/truthiness.gd +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.gd @@ -1,30 +1,32 @@ func test(): - # The assertions below should all evaluate to `true` for this test to pass. - assert(true) - assert(not false) - assert(500) - assert(not 0) - assert(500.5) - assert(not 0.0) - assert("non-empty string") - assert(["non-empty array"]) - assert({"non-empty": "dictionary"}) - assert(Vector2(1, 0)) - assert(Vector2i(-1, -1)) - assert(Vector3(0, 0, 0.0001)) - assert(Vector3i(0, 0, 10000)) + # The checks below should all evaluate to `true` for this test to pass. + Utils.check(true) + Utils.check(not false) + Utils.check(500) + Utils.check(not 0) + Utils.check(500.5) + Utils.check(not 0.0) + Utils.check("non-empty string") + Utils.check(["non-empty array"]) + Utils.check({"non-empty": "dictionary"}) + Utils.check(Vector2(1, 0)) + Utils.check(Vector2i(-1, -1)) + Utils.check(Vector3(0, 0, 0.0001)) + Utils.check(Vector3i(0, 0, 10000)) # Zero position is `true` only if the Rect2's size is non-zero. - assert(Rect2(0, 0, 0, 1)) + Utils.check(Rect2(0, 0, 0, 1)) # Zero size is `true` only if the position is non-zero. - assert(Rect2(1, 1, 0, 0)) + Utils.check(Rect2(1, 1, 0, 0)) # Zero position is `true` only if the Rect2's size is non-zero. - assert(Rect2i(0, 0, 0, 1)) + Utils.check(Rect2i(0, 0, 0, 1)) # Zero size is `true` only if the position is non-zero. - assert(Rect2i(1, 1, 0, 0)) + Utils.check(Rect2i(1, 1, 0, 0)) # A fully black color is only truthy if its alpha component is not equal to `1`. - assert(Color(0, 0, 0, 0.5)) + Utils.check(Color(0, 0, 0, 0.5)) + + print("ok") diff --git a/modules/gdscript/tests/scripts/parser/features/truthiness.out b/modules/gdscript/tests/scripts/parser/features/truthiness.out index 705524857b..1b47ed10dc 100644 --- a/modules/gdscript/tests/scripts/parser/features/truthiness.out +++ b/modules/gdscript/tests/scripts/parser/features/truthiness.out @@ -1,65 +1,2 @@ GDTEST_OK ->> WARNING ->> Line: 3 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 4 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 5 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 6 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 7 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 8 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 9 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 12 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 13 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 14 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 15 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 18 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 21 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 24 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 27 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. ->> WARNING ->> Line: 30 ->> ASSERT_ALWAYS_TRUE ->> Assert statement is redundant because the expression is always true. +ok diff --git a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd index bd38259cec..6eec37d64d 100644 --- a/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd +++ b/modules/gdscript/tests/scripts/runtime/features/array_string_stringname_equivalent.gd @@ -25,7 +25,7 @@ func test(): print("String in Array[StringName]: ", "abc" in stringname_array) var packed_string_array: PackedStringArray = [] - assert(!packed_string_array.push_back("abc")) + Utils.check(!packed_string_array.push_back("abc")) print("StringName in PackedStringArray: ", &"abc" in packed_string_array) string_array.push_back("abc") diff --git a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd index d1746979be..6aa863c05f 100644 --- a/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd +++ b/modules/gdscript/tests/scripts/runtime/features/constants_are_read_only.gd @@ -1,10 +1,9 @@ const array: Array = [0] const dictionary := {1: 2} -@warning_ignore("assert_always_true") func test(): - assert(array.is_read_only() == true) - assert(str(array) == '[0]') - assert(dictionary.is_read_only() == true) - assert(str(dictionary) == '{ 1: 2 }') + Utils.check(array.is_read_only() == true) + Utils.check(str(array) == '[0]') + Utils.check(dictionary.is_read_only() == true) + Utils.check(str(dictionary) == '{ 1: 2 }') print('ok') diff --git a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd index a778fb1a94..0f2526667d 100644 --- a/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd +++ b/modules/gdscript/tests/scripts/runtime/features/conversions_from_native_members.gd @@ -2,8 +2,8 @@ class Foo extends Node: func _init(): name = 'f' var string: String = name - assert(typeof(string) == TYPE_STRING) - assert(string == 'f') + Utils.check(typeof(string) == TYPE_STRING) + Utils.check(string == 'f') print('ok') func test(): diff --git a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd index 0851d939dc..9e67e75140 100644 --- a/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd +++ b/modules/gdscript/tests/scripts/runtime/features/default_set_beforehand.gd @@ -6,15 +6,15 @@ extends Node @onready var later_untyped = [1] func test(): - assert(typeof(later_inferred) == TYPE_ARRAY) - assert(later_inferred.size() == 0) + Utils.check(typeof(later_inferred) == TYPE_ARRAY) + Utils.check(later_inferred.size() == 0) - assert(typeof(later_static) == TYPE_ARRAY) - assert(later_static.size() == 0) + Utils.check(typeof(later_static) == TYPE_ARRAY) + Utils.check(later_static.size() == 0) - assert(typeof(later_static_with_init) == TYPE_ARRAY) - assert(later_static_with_init.size() == 0) + Utils.check(typeof(later_static_with_init) == TYPE_ARRAY) + Utils.check(later_static_with_init.size() == 0) - assert(typeof(later_untyped) == TYPE_NIL) + Utils.check(typeof(later_untyped) == TYPE_NIL) print("ok") diff --git a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd index 0133d7fcfc..90df98e05b 100644 --- a/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd +++ b/modules/gdscript/tests/scripts/runtime/features/export_group_no_name_conflict_with_properties.gd @@ -1,5 +1,3 @@ -const Utils = preload("../../utils.notest.gd") - # GH-73843 @export_group("Resource") diff --git a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd index 4cb51f8512..48a9349bf8 100644 --- a/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd +++ b/modules/gdscript/tests/scripts/runtime/features/match_with_pattern_guards.gd @@ -62,7 +62,7 @@ func test(): 0 when side_effect(): print("will run the side effect call, but not this") _: - assert(global == 1) + Utils.check(global == 1) print("side effect only ran once") func side_effect(): diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info.gd b/modules/gdscript/tests/scripts/runtime/features/member_info.gd index 42b29eee43..91d5a501c8 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info.gd @@ -5,8 +5,6 @@ class MyClass: enum MyEnum {} -const Utils = preload("../../utils.notest.gd") - static var test_static_var_untyped static var test_static_var_weak_null = null static var test_static_var_weak_int = 1 diff --git a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd index ee5c1e1267..4ddbeaec0b 100644 --- a/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd +++ b/modules/gdscript/tests/scripts/runtime/features/member_info_inheritance.gd @@ -1,7 +1,5 @@ # GH-82169 -const Utils = preload("../../utils.notest.gd") - class A: static var test_static_var_a1 static var test_static_var_a2 diff --git a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd index fd23ea0db5..d6847768e6 100644 --- a/modules/gdscript/tests/scripts/runtime/features/metatypes.gd +++ b/modules/gdscript/tests/scripts/runtime/features/metatypes.gd @@ -3,7 +3,6 @@ class MyClass: enum MyEnum {A, B, C} -const Utils = preload("../../utils.notest.gd") const Other = preload("./metatypes.notest.gd") var test_native := JSON diff --git a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd index e1aba83507..dee36d3ae0 100644 --- a/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd +++ b/modules/gdscript/tests/scripts/runtime/features/set_does_not_leak.gd @@ -6,6 +6,6 @@ class MyObj: func test(): var obj_1 = MyObj.new() var obj_2 = MyObj.new() - assert(obj_2.get_reference_count() == 1) + Utils.check(obj_2.get_reference_count() == 1) obj_1.set(&"obj", obj_2) - assert(obj_2.get_reference_count() == 1) + Utils.check(obj_2.get_reference_count() == 1) diff --git a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd index b07c40b6da..11a670a7fb 100644 --- a/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd +++ b/modules/gdscript/tests/scripts/runtime/features/single_underscore_node_name.gd @@ -12,4 +12,4 @@ func test() -> void: node1.add_child(node2) add_child(node3) - assert(get_node("_/Child") == $_/Child) + Utils.check(get_node("_/Child") == $_/Child) diff --git a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd index 691b611574..d72662736e 100644 --- a/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd +++ b/modules/gdscript/tests/scripts/runtime/features/standalone_calls_do_not_write_to_nil.gd @@ -14,33 +14,33 @@ func test(): func test_construct(v, f): @warning_ignore("unsafe_call_argument") Vector2(v, v) # Built-in type construct. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_utility(v, f): abs(v) # Utility function. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_builtin_call(v, f): @warning_ignore("unsafe_method_access") v.angle() # Built-in method call. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_builtin_call_validated(v: Vector2, f): @warning_ignore("return_value_discarded") v.abs() # Built-in method call validated. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_object_call(v, f): @warning_ignore("unsafe_method_access") v.get_reference_count() # Native type method call. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_object_call_method_bind(v: Resource, f): @warning_ignore("return_value_discarded") v.duplicate() # Native type method call with MethodBind. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. func test_object_call_method_bind_validated(v: RefCounted, f): @warning_ignore("return_value_discarded") v.get_reference_count() # Native type method call with validated MethodBind. - assert(not f) # Test unary operator reading from `nil`. + Utils.check(not f) # Test unary operator reading from `nil`. diff --git a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd index ec444b4ffa..859bfd7987 100644 --- a/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd +++ b/modules/gdscript/tests/scripts/runtime/features/typed_array_init_with_untyped_in_literal.gd @@ -1,6 +1,6 @@ func test(): var untyped: Variant = 32 var typed: Array[int] = [untyped] - assert(typed.get_typed_builtin() == TYPE_INT) - assert(str(typed) == '[32]') + Utils.check(typed.get_typed_builtin() == TYPE_INT) + Utils.check(str(typed) == '[32]') print('ok') diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 7fdd6556ec..5d615d8557 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -1,3 +1,13 @@ +class_name Utils + + +# `assert()` is not evaluated in non-debug builds. Do not use `assert()` +# for anything other than testing the `assert()` itself. +static func check(condition: Variant) -> void: + if not condition: + printerr("Check failed.") + + static func get_type(property: Dictionary, is_return: bool = false) -> String: match property.type: TYPE_NIL: @@ -46,7 +56,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String: while true: if not hint_string.contains(":"): - push_error("Invalid PROPERTY_HINT_TYPE_STRING format.") + printerr("Invalid PROPERTY_HINT_TYPE_STRING format.") var elem_type_hint: String = hint_string.get_slice(":", 0) hint_string = hint_string.substr(elem_type_hint.length() + 1) @@ -58,7 +68,7 @@ static func get_human_readable_hint_string(property: Dictionary) -> String: type_hint_prefixes += "<%s>:" % type_string(elem_type) else: if elem_type_hint.count("/") != 1: - push_error("Invalid PROPERTY_HINT_TYPE_STRING format.") + printerr("Invalid PROPERTY_HINT_TYPE_STRING format.") elem_type = elem_type_hint.get_slice("/", 0).to_int() elem_hint = elem_type_hint.get_slice("/", 1).to_int() type_hint_prefixes += "<%s>/<%s>:" % [ @@ -188,7 +198,7 @@ static func get_property_hint_name(hint: PropertyHint) -> String: return "PROPERTY_HINT_HIDE_QUATERNION_EDIT" PROPERTY_HINT_PASSWORD: return "PROPERTY_HINT_PASSWORD" - push_error("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") + printerr("Argument `hint` is invalid. Use `PROPERTY_HINT_*` constants.") return "<invalid hint>" @@ -240,7 +250,7 @@ static func get_property_usage_string(usage: int) -> String: usage &= ~flag[0] if usage != PROPERTY_USAGE_NONE: - push_error("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") + printerr("Argument `usage` is invalid. Use `PROPERTY_USAGE_*` constants.") return "<invalid usage flags>" return result.left(-1) diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml index ebeed015e9..10534594d3 100644 --- a/modules/gltf/doc_classes/GLTFDocument.xml +++ b/modules/gltf/doc_classes/GLTFDocument.xml @@ -63,6 +63,13 @@ The [param bake_fps] parameter overrides the bake_fps in [param state]. </description> </method> + <method name="get_supported_gltf_extensions" qualifiers="static"> + <return type="PackedStringArray" /> + <description> + Returns a list of all support glTF extensions, including extensions supported directly by the engine, and extensions supported by user plugins registering [GLTFDocumentExtension] classes. + [b]Note:[/b] If this method is run before a GLTFDocumentExtension is registered, its extensions won't be included in the list. Be sure to only run this method after all extensions are registered. If you run this when the engine starts, consider waiting a frame before calling this method to ensure all extensions are registered. + </description> + </method> <method name="register_gltf_document_extension" qualifiers="static"> <return type="void" /> <param index="0" name="extension" type="GLTFDocumentExtension" /> diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index cd25b93e6c..69973a34dd 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -69,6 +69,24 @@ #include <stdlib.h> #include <cstdint> +static void _attach_extras_to_meta(const Dictionary &p_extras, Ref<Resource> p_node) { + if (!p_extras.is_empty()) { + p_node->set_meta("extras", p_extras); + } +} + +static void _attach_meta_to_extras(Ref<Resource> p_node, Dictionary &p_json) { + if (p_node->has_meta("extras")) { + Dictionary node_extras = p_node->get_meta("extras"); + if (p_json.has("extras")) { + Dictionary extras = p_json["extras"]; + extras.merge(node_extras); + } else { + p_json["extras"] = node_extras; + } + } +} + static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) { Ref<ImporterMesh> importer_mesh; importer_mesh.instantiate(); @@ -101,6 +119,7 @@ static Ref<ImporterMesh> _mesh_to_importer_mesh(Ref<Mesh> p_mesh) { array, p_mesh->surface_get_blend_shape_arrays(surface_i), p_mesh->surface_get_lods(surface_i), mat, mat_name, p_mesh->surface_get_format(surface_i)); } + importer_mesh->merge_meta_from(*p_mesh); return importer_mesh; } @@ -458,7 +477,7 @@ Error GLTFDocument::_serialize_nodes(Ref<GLTFState> p_state) { if (extensions.is_empty()) { node.erase("extensions"); } - + _attach_meta_to_extras(gltf_node, node); nodes.push_back(node); } if (!nodes.is_empty()) { @@ -624,6 +643,10 @@ Error GLTFDocument::_parse_nodes(Ref<GLTFState> p_state) { } } + if (n.has("extras")) { + _attach_extras_to_meta(n["extras"], node); + } + if (n.has("children")) { const Array &children = n["children"]; for (int j = 0; j < children.size(); j++) { @@ -2727,6 +2750,8 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) { Dictionary e; e["targetNames"] = target_names; + gltf_mesh["extras"] = e; + _attach_meta_to_extras(import_mesh, gltf_mesh); weights.resize(target_names.size()); for (int name_i = 0; name_i < target_names.size(); name_i++) { @@ -2742,8 +2767,6 @@ Error GLTFDocument::_serialize_meshes(Ref<GLTFState> p_state) { ERR_FAIL_COND_V(target_names.size() != weights.size(), FAILED); - gltf_mesh["extras"] = e; - gltf_mesh["primitives"] = primitives; meshes.push_back(gltf_mesh); @@ -2776,6 +2799,7 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> p_state) { Array primitives = d["primitives"]; const Dictionary &extras = d.has("extras") ? (Dictionary)d["extras"] : Dictionary(); + _attach_extras_to_meta(extras, mesh); Ref<ImporterMesh> import_mesh; import_mesh.instantiate(); String mesh_name = "mesh"; @@ -4170,6 +4194,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) { } d["extensions"] = extensions; + _attach_meta_to_extras(material, d); materials.push_back(d); } if (!materials.size()) { @@ -4372,6 +4397,10 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) { } } } + + if (material_dict.has("extras")) { + _attach_extras_to_meta(material_dict["extras"], material); + } p_state->materials.push_back(material); } @@ -5161,6 +5190,7 @@ ImporterMeshInstance3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFState> p_s return mi; } mi->set_mesh(import_mesh); + import_mesh->merge_meta_from(*mesh); return mi; } @@ -5285,6 +5315,7 @@ void GLTFDocument::_convert_scene_node(Ref<GLTFState> p_state, Node *p_current, gltf_root = current_node_i; p_state->root_nodes.push_back(gltf_root); } + gltf_node->merge_meta_from(p_current); _create_gltf_node(p_state, p_current, current_node_i, p_gltf_parent, gltf_root, gltf_node); for (int node_i = 0; node_i < p_current->get_child_count(); node_i++) { _convert_scene_node(p_state, p_current->get_child(node_i), current_node_i, gltf_root); @@ -5676,6 +5707,8 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> p_state, const GLTFNodeIn current_node->set_transform(gltf_node->transform); } + current_node->merge_meta_from(*gltf_node); + p_state->scene_nodes.insert(p_node_index, current_node); for (int i = 0; i < gltf_node->children.size(); ++i) { _generate_scene_node(p_state, gltf_node->children[i], current_node, p_scene_root); @@ -7060,6 +7093,8 @@ void GLTFDocument::_bind_methods() { &GLTFDocument::register_gltf_document_extension, DEFVAL(false)); ClassDB::bind_static_method("GLTFDocument", D_METHOD("unregister_gltf_document_extension", "extension"), &GLTFDocument::unregister_gltf_document_extension); + ClassDB::bind_static_method("GLTFDocument", D_METHOD("get_supported_gltf_extensions"), + &GLTFDocument::get_supported_gltf_extensions); } void GLTFDocument::_build_parent_hierachy(Ref<GLTFState> p_state) { @@ -7100,6 +7135,36 @@ Vector<Ref<GLTFDocumentExtension>> GLTFDocument::get_all_gltf_document_extension return all_document_extensions; } +Vector<String> GLTFDocument::get_supported_gltf_extensions() { + HashSet<String> set = get_supported_gltf_extensions_hashset(); + Vector<String> vec; + for (const String &s : set) { + vec.append(s); + } + vec.sort(); + return vec; +} + +HashSet<String> GLTFDocument::get_supported_gltf_extensions_hashset() { + HashSet<String> supported_extensions; + // If the extension is supported directly in GLTFDocument, list it here. + // Other built-in extensions are supported by GLTFDocumentExtension classes. + supported_extensions.insert("GODOT_single_root"); + supported_extensions.insert("KHR_lights_punctual"); + supported_extensions.insert("KHR_materials_emissive_strength"); + supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); + supported_extensions.insert("KHR_materials_unlit"); + supported_extensions.insert("KHR_texture_transform"); + for (Ref<GLTFDocumentExtension> ext : all_document_extensions) { + ERR_CONTINUE(ext.is_null()); + Vector<String> ext_supported_extensions = ext->get_supported_extensions(); + for (int i = 0; i < ext_supported_extensions.size(); ++i) { + supported_extensions.insert(ext_supported_extensions[i]); + } + } + return supported_extensions; +} + PackedByteArray GLTFDocument::_serialize_glb_buffer(Ref<GLTFState> p_state, Error *r_err) { Error err = _encode_buffer_glb(p_state, ""); if (r_err) { @@ -7452,19 +7517,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) { Vector<String> ext_array = p_state->json["extensionsRequired"]; p_state->extensions_required = ext_array; } - HashSet<String> supported_extensions; - supported_extensions.insert("KHR_lights_punctual"); - supported_extensions.insert("KHR_materials_pbrSpecularGlossiness"); - supported_extensions.insert("KHR_texture_transform"); - supported_extensions.insert("KHR_materials_unlit"); - supported_extensions.insert("KHR_materials_emissive_strength"); - for (Ref<GLTFDocumentExtension> ext : document_extensions) { - ERR_CONTINUE(ext.is_null()); - Vector<String> ext_supported_extensions = ext->get_supported_extensions(); - for (int i = 0; i < ext_supported_extensions.size(); ++i) { - supported_extensions.insert(ext_supported_extensions[i]); - } - } + HashSet<String> supported_extensions = get_supported_gltf_extensions_hashset(); Error ret = OK; for (int i = 0; i < p_state->extensions_required.size(); i++) { if (!supported_extensions.has(p_state->extensions_required[i])) { diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h index d37544750d..b3e6dcf54a 100644 --- a/modules/gltf/gltf_document.h +++ b/modules/gltf/gltf_document.h @@ -92,6 +92,8 @@ public: static void unregister_gltf_document_extension(Ref<GLTFDocumentExtension> p_extension); static void unregister_all_gltf_document_extensions(); static Vector<Ref<GLTFDocumentExtension>> get_all_gltf_document_extensions(); + static Vector<String> get_supported_gltf_extensions(); + static HashSet<String> get_supported_gltf_extensions_hashset(); void set_naming_version(int p_version); int get_naming_version() const; diff --git a/modules/gltf/tests/test_gltf_extras.h b/modules/gltf/tests/test_gltf_extras.h new file mode 100644 index 0000000000..96aadf3023 --- /dev/null +++ b/modules/gltf/tests/test_gltf_extras.h @@ -0,0 +1,165 @@ +/**************************************************************************/ +/* test_gltf_extras.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_GLTF_EXTRAS_H +#define TEST_GLTF_EXTRAS_H + +#include "tests/test_macros.h" + +#ifdef TOOLS_ENABLED + +#include "core/os/os.h" +#include "editor/import/3d/resource_importer_scene.h" +#include "modules/gltf/editor/editor_scene_importer_gltf.h" +#include "modules/gltf/gltf_document.h" +#include "modules/gltf/gltf_state.h" +#include "scene/3d/mesh_instance_3d.h" +#include "scene/main/window.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "scene/resources/material.h" +#include "scene/resources/packed_scene.h" + +namespace TestGltfExtras { + +static Node *_gltf_export_then_import(Node *p_root, String &p_tempfilebase) { + Ref<GLTFDocument> doc; + doc.instantiate(); + Ref<GLTFState> state; + state.instantiate(); + Error err = doc->append_from_scene(p_root, state, EditorSceneFormatImporter::IMPORT_USE_NAMED_SKIN_BINDS); + CHECK_MESSAGE(err == OK, "GLTF state generation failed."); + err = doc->write_to_filesystem(state, p_tempfilebase + ".gltf"); + CHECK_MESSAGE(err == OK, "Writing GLTF to cache dir failed."); + + // Setting up importers. + Ref<ResourceImporterScene> import_scene = memnew(ResourceImporterScene("PackedScene", true)); + ResourceFormatImporter::get_singleton()->add_importer(import_scene); + Ref<EditorSceneFormatImporterGLTF> import_gltf; + import_gltf.instantiate(); + ResourceImporterScene::add_scene_importer(import_gltf); + + // GTLF importer behaves differently outside of editor, it's too late to modify Engine::get_editor_hint + // as the registration of runtime extensions already happened, so remove them. See modules/gltf/register_types.cpp + GLTFDocument::unregister_all_gltf_document_extensions(); + + HashMap<StringName, Variant> options(20); + options["nodes/root_type"] = ""; + options["nodes/root_name"] = ""; + options["nodes/apply_root_scale"] = true; + options["nodes/root_scale"] = 1.0; + options["meshes/ensure_tangents"] = true; + options["meshes/generate_lods"] = false; + options["meshes/create_shadow_meshes"] = true; + options["meshes/light_baking"] = 1; + options["meshes/lightmap_texel_size"] = 0.2; + options["meshes/force_disable_compression"] = false; + options["skins/use_named_skins"] = true; + options["animation/import"] = true; + options["animation/fps"] = 30; + options["animation/trimming"] = false; + options["animation/remove_immutable_tracks"] = true; + options["import_script/path"] = ""; + options["_subresources"] = Dictionary(); + options["gltf/naming_version"] = 1; + + // Process gltf file, note that this generates `.scn` resource from the 2nd argument. + err = import_scene->import(p_tempfilebase + ".gltf", p_tempfilebase, options, nullptr, nullptr, nullptr); + CHECK_MESSAGE(err == OK, "GLTF import failed."); + ResourceImporterScene::remove_scene_importer(import_gltf); + + Ref<PackedScene> packed_scene = ResourceLoader::load(p_tempfilebase + ".scn", "", ResourceFormatLoader::CACHE_MODE_REPLACE, &err); + CHECK_MESSAGE(err == OK, "Loading scene failed."); + Node *p_scene = packed_scene->instantiate(); + return p_scene; +} + +TEST_CASE("[SceneTree][Node] GLTF test mesh and material meta export and import") { + // Setup scene. + Ref<StandardMaterial3D> original_material = memnew(StandardMaterial3D); + original_material->set_albedo(Color(1.0, .0, .0)); + original_material->set_name("material"); + Dictionary material_dict; + material_dict["node_type"] = "material"; + original_material->set_meta("extras", material_dict); + + Ref<PlaneMesh> original_meshdata = memnew(PlaneMesh); + original_meshdata->set_name("planemesh"); + Dictionary meshdata_dict; + meshdata_dict["node_type"] = "planemesh"; + original_meshdata->set_meta("extras", meshdata_dict); + original_meshdata->surface_set_material(0, original_material); + + MeshInstance3D *original_mesh_instance = memnew(MeshInstance3D); + original_mesh_instance->set_mesh(original_meshdata); + original_mesh_instance->set_name("mesh_instance_3d"); + Dictionary mesh_instance_dict; + mesh_instance_dict["node_type"] = "mesh_instance_3d"; + original_mesh_instance->set_meta("extras", mesh_instance_dict); + + Node3D *original = memnew(Node3D); + SceneTree::get_singleton()->get_root()->add_child(original); + original->add_child(original_mesh_instance); + original->set_name("node3d"); + Dictionary node_dict; + node_dict["node_type"] = "node3d"; + original->set_meta("extras", node_dict); + original->set_meta("meta_not_nested_under_extras", "should not propagate"); + + // Convert to GLFT and back. + String tempfile = OS::get_singleton()->get_cache_path().path_join("gltf_extras"); + Node *loaded = _gltf_export_then_import(original, tempfile); + + // Compare the results. + CHECK(loaded->get_name() == "node3d"); + CHECK(Dictionary(loaded->get_meta("extras")).size() == 1); + CHECK(Dictionary(loaded->get_meta("extras"))["node_type"] == "node3d"); + CHECK_FALSE(loaded->has_meta("meta_not_nested_under_extras")); + CHECK_FALSE(Dictionary(loaded->get_meta("extras")).has("meta_not_nested_under_extras")); + + MeshInstance3D *mesh_instance_3d = Object::cast_to<MeshInstance3D>(loaded->find_child("mesh_instance_3d", false, true)); + CHECK(mesh_instance_3d->get_name() == "mesh_instance_3d"); + CHECK(Dictionary(mesh_instance_3d->get_meta("extras"))["node_type"] == "mesh_instance_3d"); + + Ref<Mesh> mesh = mesh_instance_3d->get_mesh(); + CHECK(Dictionary(mesh->get_meta("extras"))["node_type"] == "planemesh"); + + Ref<Material> material = mesh->surface_get_material(0); + CHECK(material->get_name() == "material"); + CHECK(Dictionary(material->get_meta("extras"))["node_type"] == "material"); + + memdelete(original_mesh_instance); + memdelete(original); + memdelete(loaded); +} +} // namespace TestGltfExtras + +#endif // TOOLS_ENABLED + +#endif // TEST_GLTF_EXTRAS_H diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index 5720f844bb..394213963a 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -57,7 +57,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { mp3dec_frame_info_t frame_info; mp3d_sample_t *buf_frame = nullptr; - int samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels); + int samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels); if (samples_mixed) { p_buffer[p_frames - todo] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]); @@ -70,7 +70,7 @@ int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { if (beat_loop && (int)frames_mixed >= beat_length_frames) { for (int i = 0; i < FADE_SIZE; i++) { - samples_mixed = mp3dec_ex_read_frame(mp3d, &buf_frame, &frame_info, mp3_stream->channels); + samples_mixed = mp3dec_ex_read_frame(&mp3d, &buf_frame, &frame_info, mp3_stream->channels); loop_fade[i] = AudioFrame(buf_frame[0], buf_frame[samples_mixed - 1]); if (!samples_mixed) { break; @@ -138,7 +138,7 @@ void AudioStreamPlaybackMP3::seek(double p_time) { } frames_mixed = uint32_t(mp3_stream->sample_rate * p_time); - mp3dec_ex_seek(mp3d, (uint64_t)frames_mixed * mp3_stream->channels); + mp3dec_ex_seek(&mp3d, (uint64_t)frames_mixed * mp3_stream->channels); } void AudioStreamPlaybackMP3::tag_used_streams() { @@ -181,10 +181,7 @@ Variant AudioStreamPlaybackMP3::get_parameter(const StringName &p_name) const { } AudioStreamPlaybackMP3::~AudioStreamPlaybackMP3() { - if (mp3d) { - mp3dec_ex_close(mp3d); - memfree(mp3d); - } + mp3dec_ex_close(&mp3d); } Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() { @@ -197,9 +194,8 @@ Ref<AudioStreamPlayback> AudioStreamMP3::instantiate_playback() { mp3s.instantiate(); mp3s->mp3_stream = Ref<AudioStreamMP3>(this); - mp3s->mp3d = (mp3dec_ex_t *)memalloc(sizeof(mp3dec_ex_t)); - int errorcode = mp3dec_ex_open_buf(mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE); + int errorcode = mp3dec_ex_open_buf(&mp3s->mp3d, data.ptr(), data_len, MP3D_SEEK_TO_SAMPLE); mp3s->frames_mixed = 0; mp3s->active = false; @@ -224,15 +220,19 @@ void AudioStreamMP3::set_data(const Vector<uint8_t> &p_data) { int src_data_len = p_data.size(); const uint8_t *src_datar = p_data.ptr(); - mp3dec_ex_t mp3d; - int err = mp3dec_ex_open_buf(&mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE); - ERR_FAIL_COND_MSG(err || mp3d.info.hz == 0, "Failed to decode mp3 file. Make sure it is a valid mp3 audio file."); + mp3dec_ex_t *mp3d = memnew(mp3dec_ex_t); + int err = mp3dec_ex_open_buf(mp3d, src_datar, src_data_len, MP3D_SEEK_TO_SAMPLE); + if (err || mp3d->info.hz == 0) { + memdelete(mp3d); + ERR_FAIL_MSG("Failed to decode mp3 file. Make sure it is a valid mp3 audio file."); + } - channels = mp3d.info.channels; - sample_rate = mp3d.info.hz; - length = float(mp3d.samples) / (sample_rate * float(channels)); + channels = mp3d->info.channels; + sample_rate = mp3d->info.hz; + length = float(mp3d->samples) / (sample_rate * float(channels)); - mp3dec_ex_close(&mp3d); + mp3dec_ex_close(mp3d); + memdelete(mp3d); clear_data(); diff --git a/modules/minimp3/audio_stream_mp3.h b/modules/minimp3/audio_stream_mp3.h index 81e8f8633c..39d389b8cd 100644 --- a/modules/minimp3/audio_stream_mp3.h +++ b/modules/minimp3/audio_stream_mp3.h @@ -49,7 +49,7 @@ class AudioStreamPlaybackMP3 : public AudioStreamPlaybackResampled { bool looping_override = false; bool looping = false; - mp3dec_ex_t *mp3d = nullptr; + mp3dec_ex_t mp3d = {}; uint32_t frames_mixed = 0; bool active = false; int loops = 0; diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index b699765b8e..032d067ae4 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -86,7 +86,7 @@ namespace GodotTools.BuildLogger WriteLine(line); - string errorLine = $@"error,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + string errorLine = $@"error,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; _issuesStreamWriter.WriteLine(errorLine); @@ -101,7 +101,7 @@ namespace GodotTools.BuildLogger WriteLine(line); - string warningLine = $@"warning,{e.File.CsvEscape()},{e.LineNumber},{e.ColumnNumber}," + + string warningLine = $@"warning,{e.File?.CsvEscape() ?? string.Empty},{e.LineNumber},{e.ColumnNumber}," + $"{e.Code?.CsvEscape() ?? string.Empty},{e.Message.CsvEscape()}," + $"{e.ProjectFile?.CsvEscape() ?? string.Empty}"; _issuesStreamWriter.WriteLine(warningLine); diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index ebb2677361..d7877fa5fc 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -265,11 +265,6 @@ namespace GodotTools.Build success = Publish(buildInfo); } - if (!success) - { - ShowBuildErrorDialog("Failed to publish .NET project"); - } - return success; } diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index ede0600ac1..a5f24fb67b 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -75,7 +75,19 @@ namespace GodotTools.Export }; } - private string? _maybeLastExportError; + private void AddExceptionMessage(EditorExportPlatform platform, Exception exception) + { + string? exceptionMessage = exception.Message; + if (string.IsNullOrEmpty(exceptionMessage)) + { + exceptionMessage = $"Exception thrown: {exception.GetType().Name}"; + } + + platform.AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", exceptionMessage); + + // We also print exceptions as we receive them to stderr. + Console.Error.WriteLine(exception); + } // With this method we can override how a file is exported in the PCK public override void _ExportFile(string path, string type, string[] features) @@ -92,8 +104,8 @@ namespace GodotTools.Export if (!ProjectContainsDotNet()) { - _maybeLastExportError = $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" + - "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again."; + GetExportPlatform().AddMessage(EditorExportPlatform.ExportMessageType.Error, "Export .NET Project", $"This project contains C# files but no solution file was found at the following path: {GodotSharpDirs.ProjectSlnPath}\n" + + "A solution file is required for projects with C# files. Please ensure that the solution file exists in the specified location and try again."); throw new InvalidOperationException($"{path} is a C# file but no solution file exists."); } @@ -124,16 +136,7 @@ namespace GodotTools.Export } catch (Exception e) { - _maybeLastExportError = e.Message; - - // 'maybeLastExportError' cannot be null or empty if there was an error, so we - // must consider the possibility of exceptions being thrown without a message. - if (string.IsNullOrEmpty(_maybeLastExportError)) - _maybeLastExportError = $"Exception thrown: {e.GetType().Name}"; - - GD.PushError($"Failed to export project: {_maybeLastExportError}"); - Console.Error.WriteLine(e); - // TODO: Do something on error once _ExportBegin supports failing. + AddExceptionMessage(GetExportPlatform(), e); } } @@ -144,7 +147,9 @@ namespace GodotTools.Export if (!ProjectContainsDotNet()) return; - if (!DeterminePlatformFromFeatures(features, out string? platform)) + string osName = GetExportPlatform().GetOsName(); + + if (!TryDeterminePlatformFromOSName(osName, out string? platform)) throw new NotSupportedException("Target platform not supported."); if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS } @@ -445,25 +450,22 @@ namespace GodotTools.Export Directory.Delete(folder, recursive: true); } _tempFolders.Clear(); - - // TODO: The following is just a workaround until the export plugins can be made to abort with errors - - // We check for empty as well, because it's set to empty after hot-reloading - if (!string.IsNullOrEmpty(_maybeLastExportError)) - { - string lastExportError = _maybeLastExportError; - _maybeLastExportError = null; - - GodotSharpEditor.Instance.ShowErrorDialog(lastExportError, "Failed to export C# project"); - } } - private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, [NotNullWhen(true)] out string? platform) + /// <summary> + /// Tries to determine the platform from the export preset's platform OS name. + /// </summary> + /// <param name="osName">Name of the export operating system.</param> + /// <param name="platform">Platform name for the recognized supported platform.</param> + /// <returns> + /// <see langword="true"/> when the platform OS name is recognized as a supported platform, + /// <see langword="false"/> otherwise. + /// </returns> + private static bool TryDeterminePlatformFromOSName(string osName, [NotNullWhen(true)] out string? platform) { - foreach (var feature in features) + if (OS.PlatformFeatureMap.TryGetValue(osName, out platform)) { - if (OS.PlatformFeatureMap.TryGetValue(feature, out platform)) - return true; + return true; } platform = null; diff --git a/modules/mono/editor/hostfxr_resolver.cpp b/modules/mono/editor/hostfxr_resolver.cpp index e93c9a5faf..9c37ac810a 100644 --- a/modules/mono/editor/hostfxr_resolver.cpp +++ b/modules/mono/editor/hostfxr_resolver.cpp @@ -216,6 +216,7 @@ bool get_default_installation_dir(String &r_dotnet_root) { #endif } +#ifndef WINDOWS_ENABLED bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_root) { Error err = OK; Ref<FileAccess> f = FileAccess::open(p_file_path, FileAccess::READ, &err); @@ -233,6 +234,7 @@ bool get_install_location_from_file(const String &p_file_path, String &r_dotnet_ r_dotnet_root = line; return true; } +#endif bool get_dotnet_self_registered_dir(String &r_dotnet_root) { #if defined(WINDOWS_ENABLED) diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 33b92f6266..78983187c7 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -87,57 +87,55 @@ void NavMeshGenerator2D::sync() { return; } - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - LocalVector<WorkerThreadPool::TaskID> finished_task_ids; + LocalVector<WorkerThreadPool::TaskID> finished_task_ids; - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { - if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - finished_task_ids.push_back(E.key); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); - NavMeshGeneratorTask2D *generator_task = E.value; - DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); + NavMeshGeneratorTask2D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); - baking_navmeshes.erase(generator_task->navigation_mesh); - if (generator_task->callback.is_valid()) { - generator_emit_callback(generator_task->callback); + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); } - memdelete(generator_task); } - } - for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { - generator_tasks.erase(finished_task_id); + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } } - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator2D::cleanup() { - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - baking_navmeshes.clear(); + baking_navmeshes.clear(); - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - NavMeshGeneratorTask2D *generator_task = E.value; - memdelete(generator_task); - } - generator_tasks.clear(); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask2D *> &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask2D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser2D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser2D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); } - generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator2D::finish() { @@ -212,7 +210,7 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly baking_navmeshes.insert(p_navigation_mesh); baking_navmesh_mutex.unlock(); - generator_task_mutex.lock(); + MutexLock generator_task_lock(generator_task_mutex); NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D); generator_task->navigation_mesh = p_navigation_mesh; generator_task->source_geometry_data = p_source_geometry_data; @@ -220,14 +218,11 @@ void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref<NavigationPoly generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D"); generator_tasks.insert(generator_task->thread_task_id, generator_task); - generator_task_mutex.unlock(); } bool NavMeshGenerator2D::is_baking(Ref<NavigationPolygon> p_navigation_polygon) { - baking_navmesh_mutex.lock(); - bool baking = baking_navmeshes.has(p_navigation_polygon); - baking_navmesh_mutex.unlock(); - return baking; + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + return baking_navmeshes.has(p_navigation_polygon); } void NavMeshGenerator2D::generator_thread_bake(void *p_arg) { diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index d17724baa0..e92a9d304b 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -100,57 +100,55 @@ void NavMeshGenerator3D::sync() { return; } - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - LocalVector<WorkerThreadPool::TaskID> finished_task_ids; + LocalVector<WorkerThreadPool::TaskID> finished_task_ids; - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { - if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - finished_task_ids.push_back(E.key); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); - NavMeshGeneratorTask3D *generator_task = E.value; - DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED); + NavMeshGeneratorTask3D *generator_task = E.value; + DEV_ASSERT(generator_task->status == NavMeshGeneratorTask3D::TaskStatus::BAKING_FINISHED); - baking_navmeshes.erase(generator_task->navigation_mesh); - if (generator_task->callback.is_valid()) { - generator_emit_callback(generator_task->callback); + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); } - memdelete(generator_task); } - } - for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { - generator_tasks.erase(finished_task_id); + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } } - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator3D::cleanup() { - baking_navmesh_mutex.lock(); - generator_task_mutex.lock(); + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + { + MutexLock generator_task_lock(generator_task_mutex); - baking_navmeshes.clear(); + baking_navmeshes.clear(); - for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { - WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); - NavMeshGeneratorTask3D *generator_task = E.value; - memdelete(generator_task); - } - generator_tasks.clear(); + for (KeyValue<WorkerThreadPool::TaskID, NavMeshGeneratorTask3D *> &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask3D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); - generator_rid_rwlock.write_lock(); - for (NavMeshGeometryParser3D *parser : generator_parsers) { - generator_parser_owner.free(parser->self); + generator_rid_rwlock.write_lock(); + for (NavMeshGeometryParser3D *parser : generator_parsers) { + generator_parser_owner.free(parser->self); + } + generator_parsers.clear(); + generator_rid_rwlock.write_unlock(); } - generator_parsers.clear(); - generator_rid_rwlock.write_unlock(); - - generator_task_mutex.unlock(); - baking_navmesh_mutex.unlock(); } void NavMeshGenerator3D::finish() { @@ -226,7 +224,7 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh baking_navmeshes.insert(p_navigation_mesh); baking_navmesh_mutex.unlock(); - generator_task_mutex.lock(); + MutexLock generator_task_lock(generator_task_mutex); NavMeshGeneratorTask3D *generator_task = memnew(NavMeshGeneratorTask3D); generator_task->navigation_mesh = p_navigation_mesh; generator_task->source_geometry_data = p_source_geometry_data; @@ -234,14 +232,11 @@ void NavMeshGenerator3D::bake_from_source_geometry_data_async(Ref<NavigationMesh generator_task->status = NavMeshGeneratorTask3D::TaskStatus::BAKING_STARTED; generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator3D::generator_thread_bake, generator_task, NavMeshGenerator3D::baking_use_high_priority_threads, SNAME("NavMeshGeneratorBake3D")); generator_tasks.insert(generator_task->thread_task_id, generator_task); - generator_task_mutex.unlock(); } bool NavMeshGenerator3D::is_baking(Ref<NavigationMesh> p_navigation_mesh) { - baking_navmesh_mutex.lock(); - bool baking = baking_navmeshes.has(p_navigation_mesh); - baking_navmesh_mutex.unlock(); - return baking; + MutexLock baking_navmesh_lock(baking_navmesh_mutex); + return baking_navmeshes.has(p_navigation_mesh); } void NavMeshGenerator3D::generator_thread_bake(void *p_arg) { diff --git a/modules/navigation/nav_map.h b/modules/navigation/nav_map.h index 82e8854b7a..c18ee7155b 100644 --- a/modules/navigation/nav_map.h +++ b/modules/navigation/nav_map.h @@ -36,6 +36,7 @@ #include "core/math/math_defs.h" #include "core/object/worker_thread_pool.h" +#include "servers/navigation/navigation_globals.h" #include <KdTree2d.h> #include <KdTree3d.h> @@ -55,21 +56,21 @@ class NavMap : public NavRid { /// To find the polygons edges the vertices are displaced in a grid where /// each cell has the following cell_size and cell_height. - real_t cell_size = 0.25; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size. - real_t cell_height = 0.25; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height. + real_t cell_size = NavigationDefaults3D::navmesh_cell_size; + real_t cell_height = NavigationDefaults3D::navmesh_cell_height; // For the inter-region merging to work, internal rasterization is performed. - float merge_rasterizer_cell_size = 0.25; - float merge_rasterizer_cell_height = 0.25; + float merge_rasterizer_cell_size = NavigationDefaults3D::navmesh_cell_size; + float merge_rasterizer_cell_height = NavigationDefaults3D::navmesh_cell_height; // This value is used to control sensitivity of internal rasterizer. float merge_rasterizer_cell_scale = 1.0; bool use_edge_connections = true; /// This value is used to detect the near edges to connect. - real_t edge_connection_margin = 0.25; + real_t edge_connection_margin = NavigationDefaults3D::edge_connection_margin; /// This value is used to limit how far links search to find polygons to connect to. - real_t link_connection_radius = 1.0; + real_t link_connection_radius = NavigationDefaults3D::link_connection_radius; bool regenerate_polygons = true; bool regenerate_links = true; diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index d0c22e9e4d..4bf09d3c84 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -136,11 +136,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_h_advance(hb_font_t *p_font, vo return 0; } - if (!bm_font->face->glyph_map.has(p_glyph)) { + HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph); + if (!E) { return 0; } - return bm_font->face->glyph_map[p_glyph].advance.x * 64; + return E->value.advance.x * 64; } hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_glyph, void *p_user_data) { @@ -150,11 +151,12 @@ hb_position_t TextServerAdvanced::_bmp_get_glyph_v_advance(hb_font_t *p_font, vo return 0; } - if (!bm_font->face->glyph_map.has(p_glyph)) { + HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph); + if (!E) { return 0; } - return -bm_font->face->glyph_map[p_glyph].advance.y * 64; + return -E->value.advance.y * 64; } hb_position_t TextServerAdvanced::_bmp_get_glyph_h_kerning(hb_font_t *p_font, void *p_font_data, hb_codepoint_t p_left_glyph, hb_codepoint_t p_right_glyph, void *p_user_data) { @@ -178,11 +180,12 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_v_origin(hb_font_t *p_font, void *p return false; } - if (!bm_font->face->glyph_map.has(p_glyph)) { + HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph); + if (!E) { return false; } - *r_x = bm_font->face->glyph_map[p_glyph].advance.x * 32; + *r_x = E->value.advance.x * 32; *r_y = -bm_font->face->ascent * 64; return true; @@ -195,14 +198,15 @@ hb_bool_t TextServerAdvanced::_bmp_get_glyph_extents(hb_font_t *p_font, void *p_ return false; } - if (!bm_font->face->glyph_map.has(p_glyph)) { + HashMap<int32_t, FontGlyph>::Iterator E = bm_font->face->glyph_map.find(p_glyph); + if (!E) { return false; } r_extents->x_bearing = 0; r_extents->y_bearing = 0; - r_extents->width = bm_font->face->glyph_map[p_glyph].rect.size.x * 64; - r_extents->height = bm_font->face->glyph_map[p_glyph].rect.size.y * 64; + r_extents->width = E->value.rect.size.x * 64; + r_extents->height = E->value.rect.size.y * 64; return true; } @@ -1188,18 +1192,21 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_bitma /* Font Cache */ /*************************************************************************/ -_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const { - ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false); +_FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const { + FontForSizeAdvanced *fd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false); int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts. - FontForSizeAdvanced *fd = p_font_data->cache[p_size]; - if (fd->glyph_map.has(p_glyph)) { - return fd->glyph_map[p_glyph].found; + HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph); + if (E) { + r_glyph = E->value; + return E->value.found; } if (glyph_index == 0) { // Non graphical or invalid glyph, do not render. - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return true; } @@ -1235,7 +1242,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, int error = FT_Load_Glyph(fd->face, glyph_index, flags); if (error) { - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return false; } @@ -1339,17 +1347,22 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_glyph(FontAdvanced *p_font_data, cleanup_stroker: FT_Stroker_Done(stroker); } - fd->glyph_map[p_glyph] = gl; + E = fd->glyph_map.insert(p_glyph, gl); + r_glyph = E->value; return gl.found; } #endif - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return false; } -_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const { +_FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const { ERR_FAIL_COND_V(p_size.x <= 0, false); - if (p_font_data->cache.has(p_size)) { + + HashMap<Vector2i, FontForSizeAdvanced *>::Iterator E = p_font_data->cache.find(p_size); + if (E) { + r_cache_for_size = E->value; return true; } @@ -1840,7 +1853,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f // Init bitmap font. fd->hb_handle = _bmp_font_create(fd, nullptr); } - p_font_data->cache[p_size] = fd; + p_font_data->cache.insert(p_size, fd); + r_cache_for_size = fd; return true; } @@ -1864,9 +1878,10 @@ hb_font_t *TextServerAdvanced::_font_get_hb_handle(const RID &p_font_rid, int64_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), nullptr); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), nullptr); - return fd->cache[size]->hb_handle; + return ffsd->hb_handle; } RID TextServerAdvanced::_create_font() { @@ -1989,7 +2004,8 @@ void TextServerAdvanced::_font_set_style(const RID &p_font_rid, BitField<FontSty MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->style_flags = p_style; } @@ -1999,7 +2015,8 @@ BitField<TextServer::FontStyle> TextServerAdvanced::_font_get_style(const RID &p MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); return fd->style_flags; } @@ -2009,7 +2026,8 @@ void TextServerAdvanced::_font_set_style_name(const RID &p_font_rid, const Strin MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->style_name = p_name; } @@ -2019,7 +2037,8 @@ String TextServerAdvanced::_font_get_style_name(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String()); return fd->style_name; } @@ -2029,7 +2048,8 @@ void TextServerAdvanced::_font_set_weight(const RID &p_font_rid, int64_t p_weigh MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->weight = CLAMP(p_weight, 100, 999); } @@ -2039,7 +2059,8 @@ int64_t TextServerAdvanced::_font_get_weight(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400); return fd->weight; } @@ -2049,7 +2070,8 @@ void TextServerAdvanced::_font_set_stretch(const RID &p_font_rid, int64_t p_stre MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->stretch = CLAMP(p_stretch, 50, 200); } @@ -2059,7 +2081,8 @@ int64_t TextServerAdvanced::_font_get_stretch(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100); return fd->stretch; } @@ -2069,7 +2092,8 @@ void TextServerAdvanced::_font_set_name(const RID &p_font_rid, const String &p_n MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->font_name = p_name; } @@ -2079,7 +2103,8 @@ String TextServerAdvanced::_font_get_name(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String()); return fd->font_name; } @@ -2089,9 +2114,10 @@ Dictionary TextServerAdvanced::_font_get_ot_name_strings(const RID &p_font_rid) MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); - hb_face_t *hb_face = hb_font_get_face(fd->cache[size]->hb_handle); + hb_face_t *hb_face = hb_font_get_face(ffsd->hb_handle); unsigned int num_entries = 0; const hb_ot_name_entry_t *names = hb_ot_name_list_names(hb_face, &num_entries); @@ -2599,8 +2625,9 @@ void TextServerAdvanced::_font_set_ascent(const RID &p_font_rid, int64_t p_size, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->ascent = p_ascent; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->ascent = p_ascent; } double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const { @@ -2610,18 +2637,19 @@ double TextServerAdvanced::_font_get_ascent(const RID &p_font_rid, int64_t p_siz MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size; + return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size; + return ffsd->ascent * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->ascent; + return ffsd->ascent; } } @@ -2631,8 +2659,9 @@ void TextServerAdvanced::_font_set_descent(const RID &p_font_rid, int64_t p_size Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->descent = p_descent; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->descent = p_descent; } double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_size) const { @@ -2642,18 +2671,19 @@ double TextServerAdvanced::_font_get_descent(const RID &p_font_rid, int64_t p_si MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size; + return ffsd->descent * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size; + return ffsd->descent * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->descent; + return ffsd->descent; } } @@ -2664,8 +2694,9 @@ void TextServerAdvanced::_font_set_underline_position(const RID &p_font_rid, int MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->underline_position = p_underline_position; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->underline_position = p_underline_position; } double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const { @@ -2675,18 +2706,19 @@ double TextServerAdvanced::_font_get_underline_position(const RID &p_font_rid, i MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size; + return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size; + return ffsd->underline_position * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->underline_position; + return ffsd->underline_position; } } @@ -2697,8 +2729,9 @@ void TextServerAdvanced::_font_set_underline_thickness(const RID &p_font_rid, in MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->underline_thickness = p_underline_thickness; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->underline_thickness = p_underline_thickness; } double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const { @@ -2708,18 +2741,19 @@ double TextServerAdvanced::_font_get_underline_thickness(const RID &p_font_rid, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size; + return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size; + return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->underline_thickness; + return ffsd->underline_thickness; } } @@ -2730,13 +2764,15 @@ void TextServerAdvanced::_font_set_scale(const RID &p_font_rid, int64_t p_size, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { return; // Do not override scale for dynamic fonts, it's calculated automatically. } #endif - fd->cache[size]->scale = p_scale; + ffsd->scale = p_scale; } double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size) const { @@ -2746,18 +2782,19 @@ double TextServerAdvanced::_font_get_scale(const RID &p_font_rid, int64_t p_size MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size; + return ffsd->scale * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size; + return ffsd->scale * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->scale / fd->cache[size]->oversampling; + return ffsd->scale / ffsd->oversampling; } } @@ -2768,9 +2805,10 @@ int64_t TextServerAdvanced::_font_get_texture_count(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); - return fd->cache[size]->textures.size(); + return ffsd->textures.size(); } void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) { @@ -2779,8 +2817,9 @@ void TextServerAdvanced::_font_clear_textures(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->textures.clear(); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->textures.clear(); } void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) { @@ -2789,10 +2828,11 @@ void TextServerAdvanced::_font_remove_texture(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size()); - fd->cache[size]->textures.remove_at(p_texture_index); + ffsd->textures.remove_at(p_texture_index); } void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) { @@ -2802,13 +2842,14 @@ void TextServerAdvanced::_font_set_texture_image(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); ERR_FAIL_COND(p_texture_index < 0); - if (p_texture_index >= fd->cache[size]->textures.size()) { - fd->cache[size]->textures.resize(p_texture_index + 1); + if (p_texture_index >= ffsd->textures.size()) { + ffsd->textures.resize(p_texture_index + 1); } - ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + ShelfPackTexture &tex = ffsd->textures.write[p_texture_index]; tex.image = p_image; tex.texture_w = p_image->get_width(); @@ -2829,10 +2870,11 @@ Ref<Image> TextServerAdvanced::_font_get_texture_image(const RID &p_font_rid, co MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>()); - ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>()); + ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>()); - const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index]; + const ShelfPackTexture &tex = ffsd->textures[p_texture_index]; return tex.image; } @@ -2843,13 +2885,14 @@ void TextServerAdvanced::_font_set_texture_offsets(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); ERR_FAIL_COND(p_texture_index < 0); - if (p_texture_index >= fd->cache[size]->textures.size()) { - fd->cache[size]->textures.resize(p_texture_index + 1); + if (p_texture_index >= ffsd->textures.size()) { + ffsd->textures.resize(p_texture_index + 1); } - ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + ShelfPackTexture &tex = ffsd->textures.write[p_texture_index]; tex.shelves.clear(); for (int32_t i = 0; i < p_offsets.size(); i += 4) { tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3])); @@ -2862,10 +2905,11 @@ PackedInt32Array TextServerAdvanced::_font_get_texture_offsets(const RID &p_font MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); - ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array()); + ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array()); - const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index]; + const ShelfPackTexture &tex = ffsd->textures[p_texture_index]; PackedInt32Array ret; ret.resize(tex.shelves.size() * 4); @@ -2887,10 +2931,11 @@ PackedInt32Array TextServerAdvanced::_font_get_glyph_list(const RID &p_font_rid, MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array()); PackedInt32Array ret; - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map; for (const KeyValue<int32_t, FontGlyph> &E : gl) { ret.push_back(E.key); } @@ -2903,9 +2948,10 @@ void TextServerAdvanced::_font_clear_glyphs(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - fd->cache[size]->glyph_map.clear(); + ffsd->glyph_map.clear(); } void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) { @@ -2914,9 +2960,10 @@ void TextServerAdvanced::_font_remove_glyph(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - fd->cache[size]->glyph_map.erase(p_glyph); + ffsd->glyph_map.erase(p_glyph); } double TextServerAdvanced::_get_extra_advance(RID p_font_rid, int p_font_size) const { @@ -2940,22 +2987,22 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64 MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - Vector2 ea; if (fd->embolden != 0.0) { ea.x = fd->embolden * double(size.x) / 64.0; @@ -2963,17 +3010,17 @@ Vector2 TextServerAdvanced::_font_get_glyph_advance(const RID &p_font_rid, int64 double scale = _font_get_scale(p_font_rid, p_size); if (fd->msdf) { - return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size; + return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size; + return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size; } else { - return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); + return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); } } else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) { - return (gl[p_glyph | mod].advance + ea).round(); + return (fgl.advance + ea).round(); } else { - return gl[p_glyph | mod].advance + ea; + return fgl.advance + ea; } } @@ -2984,12 +3031,13 @@ void TextServerAdvanced::_font_set_glyph_advance(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].advance = p_advance; - gl[p_glyph].found = true; + fgl.advance = p_advance; + fgl.found = true; } Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2999,32 +3047,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_offset(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - if (fd->msdf) { - return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size; + return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size; + return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size; } else { - return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); + return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); } } else { - return gl[p_glyph | mod].rect.position; + return fgl.rect.position; } } @@ -3035,12 +3083,13 @@ void TextServerAdvanced::_font_set_glyph_offset(const RID &p_font_rid, const Vec MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].rect.position = p_offset; - gl[p_glyph].found = true; + fgl.rect.position = p_offset; + fgl.found = true; } Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -3050,32 +3099,32 @@ Vector2 TextServerAdvanced::_font_get_glyph_size(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - if (fd->msdf) { - return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size; + return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size; + return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size; } else { - return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); + return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); } } else { - return gl[p_glyph | mod].rect.size; + return fgl.rect.size; } } @@ -3086,12 +3135,13 @@ void TextServerAdvanced::_font_set_glyph_size(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].rect.size = p_gl_size; - gl[p_glyph].found = true; + fgl.rect.size = p_gl_size; + fgl.found = true; } Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -3101,22 +3151,23 @@ Rect2 TextServerAdvanced::_font_get_glyph_uv_rect(const RID &p_font_rid, const V MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Rect2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - return gl[p_glyph | mod].uv_rect; + return fgl.uv_rect; } void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) { @@ -3126,12 +3177,13 @@ void TextServerAdvanced::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].uv_rect = p_uv_rect; - gl[p_glyph].found = true; + fgl.uv_rect = p_uv_rect; + fgl.found = true; } int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -3141,22 +3193,23 @@ int64_t TextServerAdvanced::_font_get_glyph_texture_idx(const RID &p_font_rid, c MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return -1; // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - return gl[p_glyph | mod].texture_idx; + return fgl.texture_idx; } void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) { @@ -3166,12 +3219,13 @@ void TextServerAdvanced::_font_set_glyph_texture_idx(const RID &p_font_rid, cons MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].texture_idx = p_texture_idx; - gl[p_glyph].found = true; + fgl.texture_idx = p_texture_idx; + fgl.found = true; } RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -3181,27 +3235,28 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return RID(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID()); + ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID()); if (RenderingServer::get_singleton() != nullptr) { - if (gl[p_glyph | mod].texture_idx != -1) { - if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx]; + if (fgl.texture_idx != -1) { + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -3214,7 +3269,7 @@ RID TextServerAdvanced::_font_get_glyph_texture_rid(const RID &p_font_rid, const } tex.dirty = false; } - return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid(); + return ffsd->textures[fgl.texture_idx].texture->get_rid(); } } @@ -3228,27 +3283,28 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Size2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2()); + ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2()); if (RenderingServer::get_singleton() != nullptr) { - if (gl[p_glyph | mod].texture_idx != -1) { - if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx]; + if (fgl.texture_idx != -1) { + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -3261,7 +3317,7 @@ Size2 TextServerAdvanced::_font_get_glyph_texture_size(const RID &p_font_rid, co } tex.dirty = false; } - return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size(); + return ffsd->textures[fgl.texture_idx].texture->get_size(); } } @@ -3275,7 +3331,8 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); #ifdef MODULE_FREETYPE_ENABLED PackedVector3Array points; @@ -3283,20 +3340,20 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i int32_t index = p_index & 0xffffff; // Remove subpixel shifts. - int error = FT_Load_Glyph(fd->cache[size]->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + int error = FT_Load_Glyph(ffsd->face, index, FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); ERR_FAIL_COND_V(error, Dictionary()); if (fd->embolden != 0.f) { FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64). - FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength); + FT_Outline_Embolden(&ffsd->face->glyph->outline, strength); } if (fd->transform != Transform2D()) { FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536). - FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat); + FT_Outline_Transform(&ffsd->face->glyph->outline, &mat); } - double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale; + double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale; if (fd->msdf) { scale = scale * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { @@ -3306,13 +3363,13 @@ Dictionary TextServerAdvanced::_font_get_glyph_contours(const RID &p_font_rid, i scale = scale * Math::round((double)p_size / (double)fd->fixed_size); } } - for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) { - points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i]))); + for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) { + points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i]))); } - for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) { - contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]); + for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) { + contours.push_back(ffsd->face->glyph->outline.contours[i]); } - bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); + bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); Dictionary out; out["points"] = points; @@ -3331,7 +3388,8 @@ TypedArray<Vector2i> TextServerAdvanced::_font_get_kerning_list(const RID &p_fon MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>()); TypedArray<Vector2i> ret; for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) { @@ -3347,8 +3405,9 @@ void TextServerAdvanced::_font_clear_kerning_map(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map.clear(); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map.clear(); } void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) { @@ -3358,8 +3417,9 @@ void TextServerAdvanced::_font_remove_kerning(const RID &p_font_rid, int64_t p_s MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map.erase(p_glyph_pair); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map.erase(p_glyph_pair); } void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { @@ -3369,8 +3429,9 @@ void TextServerAdvanced::_font_set_kerning(const RID &p_font_rid, int64_t p_size MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning; + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map[p_glyph_pair] = p_kerning; } Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const { @@ -3380,9 +3441,10 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); - const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map; + const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map; if (kern.has(p_glyph_pair)) { if (fd->msdf) { @@ -3398,9 +3460,9 @@ Vector2 TextServerAdvanced::_font_get_kerning(const RID &p_font_rid, int64_t p_s } } else { #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { FT_Vector delta; - FT_Get_Kerning(fd->cache[size]->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta); + FT_Get_Kerning(ffsd->face, p_glyph_pair.x, p_glyph_pair.y, FT_KERNING_DEFAULT, &delta); if (fd->msdf) { return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { @@ -3426,14 +3488,15 @@ int64_t TextServerAdvanced::_font_get_glyph_index(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { if (p_variation_selector) { - return FT_Face_GetCharVariantIndex(fd->cache[size]->face, p_char, p_variation_selector); + return FT_Face_GetCharVariantIndex(ffsd->face, p_char, p_variation_selector); } else { - return FT_Get_Char_Index(fd->cache[size]->face, p_char); + return FT_Get_Char_Index(ffsd->face, p_char); } } else { return (int64_t)p_char; @@ -3449,23 +3512,24 @@ int64_t TextServerAdvanced::_font_get_char_from_glyph_index(const RID &p_font_ri MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->inv_glyph_map.is_empty()) { - FT_Face face = fd->cache[size]->face; + if (ffsd->inv_glyph_map.is_empty()) { + FT_Face face = ffsd->face; FT_UInt gindex; FT_ULong charcode = FT_Get_First_Char(face, &gindex); while (gindex != 0) { if (charcode != 0) { - fd->cache[size]->inv_glyph_map[gindex] = charcode; + ffsd->inv_glyph_map[gindex] = charcode; } charcode = FT_Get_Next_Char(face, charcode, &gindex); } } - if (fd->cache[size]->inv_glyph_map.has(p_glyph_index)) { - return fd->cache[size]->inv_glyph_map[p_glyph_index]; + if (ffsd->inv_glyph_map.has(p_glyph_index)) { + return ffsd->inv_glyph_map[p_glyph_index]; } else { return 0; } @@ -3482,17 +3546,19 @@ bool TextServerAdvanced::_font_has_char(const RID &p_font_rid, int64_t p_char) c } MutexLock lock(fd->mutex); + FontForSizeAdvanced *ffsd = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false); + } else { + ffsd = fd->cache.begin()->value; } - FontForSizeAdvanced *at_size = fd->cache.begin()->value; #ifdef MODULE_FREETYPE_ENABLED - if (at_size && at_size->face) { - return FT_Get_Char_Index(at_size->face, p_char) != 0; + if (ffsd->face) { + return FT_Get_Char_Index(ffsd->face, p_char) != 0; } #endif - return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false; + return ffsd->glyph_map.has((int32_t)p_char); } String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) const { @@ -3500,30 +3566,30 @@ String TextServerAdvanced::_font_get_supported_chars(const RID &p_font_rid) cons ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); + FontForSizeAdvanced *ffsd = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String()); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String()); + } else { + ffsd = fd->cache.begin()->value; } - FontForSizeAdvanced *at_size = fd->cache.begin()->value; String chars; #ifdef MODULE_FREETYPE_ENABLED - if (at_size && at_size->face) { + if (ffsd->face) { FT_UInt gindex; - FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex); while (gindex != 0) { if (charcode != 0) { chars = chars + String::chr(charcode); } - charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex); } return chars; } #endif - if (at_size) { - const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map; - for (const KeyValue<int32_t, FontGlyph> &E : gl) { - chars = chars + String::chr(E.key); - } + const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map; + for (const KeyValue<int32_t, FontGlyph> &E : gl) { + chars = chars + String::chr(E.key); } return chars; } @@ -3533,10 +3599,12 @@ PackedInt32Array TextServerAdvanced::_font_get_supported_glyphs(const RID &p_fon ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); + FontForSizeAdvanced *at_size = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array()); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array()); + } else { + at_size = fd->cache.begin()->value; } - FontForSizeAdvanced *at_size = fd->cache.begin()->value; PackedInt32Array glyphs; #ifdef MODULE_FREETYPE_ENABLED @@ -3567,25 +3635,27 @@ void TextServerAdvanced::_font_render_range(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); for (int64_t i = p_start; i <= p_end; i++) { #ifdef MODULE_FREETYPE_ENABLED - int32_t idx = FT_Get_Char_Index(fd->cache[size]->face, i); - if (fd->cache[size]->face) { + int32_t idx = FT_Get_Char_Index(ffsd->face, i); + if (ffsd->face) { + FontGlyph fgl; if (fd->msdf) { - _ensure_glyph(fd, size, (int32_t)idx); + _ensure_glyph(fd, size, (int32_t)idx, fgl); } else { for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) { if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl); } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); } else { - _ensure_glyph(fd, size, (int32_t)idx | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl); } } } @@ -3600,24 +3670,26 @@ void TextServerAdvanced::_font_render_glyph(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); #ifdef MODULE_FREETYPE_ENABLED int32_t idx = p_index & 0xffffff; // Remove subpixel shifts. - if (fd->cache[size]->face) { + if (ffsd->face) { + FontGlyph fgl; if (fd->msdf) { - _ensure_glyph(fd, size, (int32_t)idx); + _ensure_glyph(fd, size, (int32_t)idx, fgl); } else { for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) { if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl); } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); } else { - _ensure_glyph(fd, size, (int32_t)idx | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl); } } } @@ -3634,16 +3706,17 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); int32_t index = p_index & 0xffffff; // Remove subpixel shifts. bool lcd_aa = false; #ifdef MODULE_FREETYPE_ENABLED - if (!fd->msdf && fd->cache[size]->face) { + if (!fd->msdf && ffsd->face) { // LCD layout, bits 24, 25, 26 if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { lcd_aa = true; index = index | (layout << 24); @@ -3660,24 +3733,24 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } #endif - if (!_ensure_glyph(fd, size, index)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, index, fgl)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[index]; - if (gl.found) { - ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + if (fgl.found) { + ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size()); - if (gl.texture_idx != -1) { + if (fgl.texture_idx != -1) { Color modulate = p_color; #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { + if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { modulate.r = modulate.g = modulate.b = 1.0; } #endif if (RenderingServer::get_singleton() != nullptr) { - if (fd->cache[size]->textures[gl.texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx]; + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -3690,12 +3763,12 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } tex.dirty = false; } - RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid(); if (fd->msdf) { Point2 cpos = p_pos; - cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size; - Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size; - RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); + cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size; + Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); } else { double scale = _font_get_scale(p_font_rid, p_size); Point2 cpos = p_pos; @@ -3708,8 +3781,8 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - Vector2 gpos = gl.rect.position; - Size2 csize = gl.rect.size; + Vector2 gpos = fgl.rect.position; + Size2 csize = fgl.rect.size; if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { double gl_scale = (double)p_size / (double)fd->fixed_size; @@ -3723,9 +3796,9 @@ void TextServerAdvanced::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } cpos += gpos; if (lcd_aa) { - RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); + RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate); } else { - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false); } } } @@ -3742,16 +3815,17 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); int32_t index = p_index & 0xffffff; // Remove subpixel shifts. bool lcd_aa = false; #ifdef MODULE_FREETYPE_ENABLED - if (!fd->msdf && fd->cache[size]->face) { + if (!fd->msdf && ffsd->face) { // LCD layout, bits 24, 25, 26 if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { lcd_aa = true; index = index | (layout << 24); @@ -3768,24 +3842,24 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R } #endif - if (!_ensure_glyph(fd, size, index)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, index, fgl)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[index]; - if (gl.found) { - ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + if (fgl.found) { + ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size()); - if (gl.texture_idx != -1) { + if (fgl.texture_idx != -1) { Color modulate = p_color; #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { + if (ffsd->face && fd->cache[size]->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { modulate.r = modulate.g = modulate.b = 1.0; } #endif if (RenderingServer::get_singleton() != nullptr) { - if (fd->cache[size]->textures[gl.texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx]; + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -3798,12 +3872,12 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R } tex.dirty = false; } - RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid(); if (fd->msdf) { Point2 cpos = p_pos; - cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size; - Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size; - RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); + cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size; + Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); } else { Point2 cpos = p_pos; double scale = _font_get_scale(p_font_rid, p_size); @@ -3816,8 +3890,8 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - Vector2 gpos = gl.rect.position; - Size2 csize = gl.rect.size; + Vector2 gpos = fgl.rect.position; + Size2 csize = fgl.rect.size; if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { double gl_scale = (double)p_size / (double)fd->fixed_size; @@ -3831,9 +3905,9 @@ void TextServerAdvanced::_font_draw_glyph_outline(const RID &p_font_rid, const R } cpos += gpos; if (lcd_aa) { - RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); + RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate); } else { - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false); } } } @@ -3898,7 +3972,8 @@ bool TextServerAdvanced::_font_is_script_supported(const RID &p_font_rid, const return fd->script_support_overrides[p_script]; } else { Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), false); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), false); return fd->supported_scripts.has(hb_tag_from_string(p_script.ascii().get_data(), -1)); } } @@ -3945,7 +4020,8 @@ void TextServerAdvanced::_font_set_opentype_feature_overrides(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->feature_overrides = p_overrides; } @@ -3963,7 +4039,8 @@ Dictionary TextServerAdvanced::_font_supported_feature_list(const RID &p_font_ri MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); return fd->supported_features; } @@ -3973,7 +4050,8 @@ Dictionary TextServerAdvanced::_font_supported_variation_list(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeAdvanced *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); return fd->supported_varaitions; } @@ -4048,7 +4126,7 @@ int64_t TextServerAdvanced::_convert_pos_inv(const ShapedTextDataAdvanced *p_sd, } void TextServerAdvanced::invalidate(TextServerAdvanced::ShapedTextDataAdvanced *p_shaped, bool p_text) { - p_shaped->valid = false; + p_shaped->valid.clear(); p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; p_shaped->justification_ops_valid = false; @@ -4404,7 +4482,7 @@ bool TextServerAdvanced::_shaped_text_resize_object(const RID &p_shaped, const V sd->objects[p_key].rect.size = p_size; sd->objects[p_key].inline_align = p_inline_align; sd->objects[p_key].baseline = p_baseline; - if (sd->valid) { + if (sd->valid.is_set()) { // Recalc string metrics. sd->ascent = 0; sd->descent = 0; @@ -4548,7 +4626,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start if (sd->parent != RID()) { return _shaped_text_substr(sd->parent, p_start, p_length); } - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID()); @@ -4576,7 +4654,7 @@ RID TextServerAdvanced::_shaped_text_substr(const RID &p_shaped, int64_t p_start } bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const ShapedTextDataAdvanced *p_sd, int64_t p_start, int64_t p_length) const { - if (p_new_sd->valid) { + if (p_new_sd->valid.is_set()) { return true; } @@ -4725,7 +4803,7 @@ bool TextServerAdvanced::_shape_substr(ShapedTextDataAdvanced *p_new_sd, const S _realign(p_new_sd); } - p_new_sd->valid = true; + p_new_sd->valid.set(); return true; } @@ -4743,7 +4821,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } if (!sd->justification_ops_valid) { @@ -4900,7 +4978,7 @@ double TextServerAdvanced::_shaped_text_tab_align(const RID &p_shaped, const Pac ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } if (!sd->line_breaks_valid) { @@ -5117,7 +5195,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ ERR_FAIL_NULL_MSG(sd, "ShapedTextDataAdvanced invalid."); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped_line); } @@ -5144,7 +5222,7 @@ void TextServerAdvanced::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ Vector<ShapedTextDataAdvanced::Span> &spans = sd->spans; if (sd->parent != RID()) { ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); - ERR_FAIL_COND(!parent_sd->valid); + ERR_FAIL_COND(!parent_sd->valid.is_set()); spans = parent_sd->spans; } @@ -5355,7 +5433,7 @@ void TextServerAdvanced::_update_chars(ShapedTextDataAdvanced *p_sd) const { Vector<ShapedTextDataAdvanced::Span> &spans = p_sd->spans; if (p_sd->parent != RID()) { ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(p_sd->parent); - ERR_FAIL_COND(!parent_sd->valid); + ERR_FAIL_COND(!parent_sd->valid.is_set()); spans = parent_sd->spans; } @@ -5403,7 +5481,7 @@ PackedInt32Array TextServerAdvanced::_shaped_text_get_character_breaks(const RID ERR_FAIL_NULL_V(sd, PackedInt32Array()); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } @@ -5417,7 +5495,7 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) { ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped); } @@ -5688,7 +5766,7 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped); } if (!sd->line_breaks_valid) { @@ -6068,7 +6146,7 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } @@ -6128,7 +6206,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star gl.index = glyph_info[i].codepoint; if (gl.index != 0) { - _ensure_glyph(fd, fss, gl.index | mod); + FontGlyph fgl; + _ensure_glyph(fd, fss, gl.index | mod, fgl); if (subpos) { gl.x_off = (double)glyph_pos[i].x_offset / (64.0 / scale); } else if (p_sd->orientation == ORIENTATION_HORIZONTAL) { @@ -6240,7 +6319,7 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (sd->valid) { + if (sd->valid.is_set()) { return true; } @@ -6248,13 +6327,13 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { if (sd->parent != RID()) { _shaped_text_shape(sd->parent); ShapedTextDataAdvanced *parent_sd = shaped_owner.get_or_null(sd->parent); - ERR_FAIL_COND_V(!parent_sd->valid, false); + ERR_FAIL_COND_V(!parent_sd->valid.is_set(), false); ERR_FAIL_COND_V(!_shape_substr(sd, parent_sd, sd->start, sd->end - sd->start), false); return true; } if (sd->text.length() == 0) { - sd->valid = true; + sd->valid.set(); return true; } @@ -6447,16 +6526,16 @@ bool TextServerAdvanced::_shaped_text_shape(const RID &p_shaped) { } _realign(sd); - sd->valid = true; - return sd->valid; + sd->valid.set(); + return sd->valid.is_set(); } bool TextServerAdvanced::_shaped_text_is_ready(const RID &p_shaped) const { const ShapedTextDataAdvanced *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL_V(sd, false); - MutexLock lock(sd->mutex); - return sd->valid; + // Atomic read is safe and faster. + return sd->valid.is_set(); } const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) const { @@ -6464,7 +6543,7 @@ const Glyph *TextServerAdvanced::_shaped_text_get_glyphs(const RID &p_shaped) co ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->glyphs.ptr(); @@ -6475,7 +6554,7 @@ int64_t TextServerAdvanced::_shaped_text_get_glyph_count(const RID &p_shaped) co ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->glyphs.size(); @@ -6486,7 +6565,7 @@ const Glyph *TextServerAdvanced::_shaped_text_sort_logical(const RID &p_shaped) ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } @@ -6526,7 +6605,7 @@ Rect2 TextServerAdvanced::_shaped_text_get_object_rect(const RID &p_shaped, cons MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->objects[p_key].rect; @@ -6547,7 +6626,7 @@ int64_t TextServerAdvanced::_shaped_text_get_object_glyph(const RID &p_shaped, c MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), -1); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } const ShapedTextDataAdvanced::EmbeddedObject &obj = sd->objects[p_key]; @@ -6566,7 +6645,7 @@ Size2 TextServerAdvanced::_shaped_text_get_size(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, Size2()); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) { @@ -6581,7 +6660,7 @@ double TextServerAdvanced::_shaped_text_get_ascent(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->ascent + sd->extra_spacing[SPACING_TOP]; @@ -6592,7 +6671,7 @@ double TextServerAdvanced::_shaped_text_get_descent(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return sd->descent + sd->extra_spacing[SPACING_BOTTOM]; @@ -6603,7 +6682,7 @@ double TextServerAdvanced::_shaped_text_get_width(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } return Math::ceil(sd->text_trimmed ? sd->width_trimmed : sd->width); @@ -6614,7 +6693,7 @@ double TextServerAdvanced::_shaped_text_get_underline_position(const RID &p_shap ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } @@ -6626,7 +6705,7 @@ double TextServerAdvanced::_shaped_text_get_underline_thickness(const RID &p_sha ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerAdvanced *>(this)->_shaped_text_shape(p_shaped); } @@ -7424,10 +7503,15 @@ bool TextServerAdvanced::_is_valid_letter(uint64_t p_unicode) const { return u_isalpha(p_unicode); } +void TextServerAdvanced::_update_settings() { + lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout")); +} + TextServerAdvanced::TextServerAdvanced() { _insert_num_systems_lang(); _insert_feature_sets(); _bmp_create_font_funcs(); + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerAdvanced::_update_settings)); } void TextServerAdvanced::_cleanup() { diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index fdebb8e4cd..448be9ebe4 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -74,6 +74,7 @@ #include <godot_cpp/templates/hash_map.hpp> #include <godot_cpp/templates/hash_set.hpp> #include <godot_cpp/templates/rid_owner.hpp> +#include <godot_cpp/templates/safe_refcount.hpp> #include <godot_cpp/templates/vector.hpp> using namespace godot; @@ -85,6 +86,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" +#include "core/templates/safe_refcount.h" #include "scene/resources/image_texture.h" #include "servers/text/text_server_extension.h" @@ -151,6 +153,9 @@ class TextServerAdvanced : public TextServerExtension { HashMap<StringName, int32_t> feature_sets; HashMap<int32_t, FeatureInfo> feature_sets_inv; + SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE }; + void _update_settings(); + void _insert_num_systems_lang(); void _insert_feature_sets(); _FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag, Variant::Type p_vtype = Variant::INT, bool p_hidden = false); @@ -327,7 +332,7 @@ class TextServerAdvanced : public TextServerExtension { int extra_spacing[4] = { 0, 0, 0, 0 }; double baseline_offset = 0.0; - HashMap<Vector2i, FontForSizeAdvanced *, VariantHasher, VariantComparator> cache; + HashMap<Vector2i, FontForSizeAdvanced *> cache; bool face_init = false; HashSet<uint32_t> supported_scripts; @@ -359,8 +364,8 @@ class TextServerAdvanced : public TextServerExtension { #ifdef MODULE_FREETYPE_ENABLED _FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeAdvanced *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const; #endif - _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const; - _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const; + _FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const; + _FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size, FontForSizeAdvanced *&r_cache_for_size) const; _FORCE_INLINE_ void _font_clear_cache(FontAdvanced *p_font_data); static void _generateMTSDF_threaded(void *p_td, uint32_t p_y); @@ -487,7 +492,7 @@ class TextServerAdvanced : public TextServerExtension { /* Shaped data */ TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction. int base_para_direction = UBIDI_DEFAULT_LTR; - bool valid = false; // String is shaped. + SafeFlag valid{ false }; // String is shaped. bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted). bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string. bool sort_valid = false; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index a7ddfc719e..540ba19cac 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -624,18 +624,21 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_bitma /* Font Cache */ /*************************************************************************/ -_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const { - ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size), false); +_FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const { + FontForSizeFallback *fd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(p_font_data, p_size, fd), false); int32_t glyph_index = p_glyph & 0xffffff; // Remove subpixel shifts. - FontForSizeFallback *fd = p_font_data->cache[p_size]; - if (fd->glyph_map.has(p_glyph)) { - return fd->glyph_map[p_glyph].found; + HashMap<int32_t, FontGlyph>::Iterator E = fd->glyph_map.find(p_glyph); + if (E) { + r_glyph = E->value; + return E->value.found; } if (glyph_index == 0) { // Non graphical or invalid glyph, do not render. - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return true; } @@ -673,7 +676,8 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, int error = FT_Load_Glyph(fd->face, glyph_index, flags); if (error) { - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return false; } @@ -777,20 +781,26 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_glyph(FontFallback *p_font_data, cleanup_stroker: FT_Stroker_Done(stroker); } - fd->glyph_map[p_glyph] = gl; + E = fd->glyph_map.insert(p_glyph, gl); + r_glyph = E->value; return gl.found; } #endif - fd->glyph_map[p_glyph] = FontGlyph(); + E = fd->glyph_map.insert(p_glyph, FontGlyph()); + r_glyph = E->value; return false; } -_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const { +_FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const { ERR_FAIL_COND_V(p_size.x <= 0, false); - if (p_font_data->cache.has(p_size)) { + + HashMap<Vector2i, FontForSizeFallback *>::Iterator E = p_font_data->cache.find(p_size); + if (E) { + r_cache_for_size = E->value; return true; } + r_cache_for_size = nullptr; FontForSizeFallback *fd = memnew(FontForSizeFallback); fd->size = p_size; if (p_font_data->data_ptr && (p_font_data->data_size > 0)) { @@ -973,7 +983,9 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f ERR_FAIL_V_MSG(false, "FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); #endif } - p_font_data->cache[p_size] = fd; + + p_font_data->cache.insert(p_size, fd); + r_cache_for_size = fd; return true; } @@ -1041,7 +1053,8 @@ void TextServerFallback::_font_set_style(const RID &p_font_rid, BitField<FontSty MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->style_flags = p_style; } @@ -1119,7 +1132,8 @@ BitField<TextServer::FontStyle> TextServerFallback::_font_get_style(const RID &p MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); return fd->style_flags; } @@ -1129,7 +1143,8 @@ void TextServerFallback::_font_set_style_name(const RID &p_font_rid, const Strin MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->style_name = p_name; } @@ -1139,7 +1154,8 @@ String TextServerFallback::_font_get_style_name(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String()); return fd->style_name; } @@ -1149,7 +1165,8 @@ void TextServerFallback::_font_set_weight(const RID &p_font_rid, int64_t p_weigh MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->weight = CLAMP(p_weight, 100, 999); } @@ -1159,7 +1176,8 @@ int64_t TextServerFallback::_font_get_weight(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 400); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 400); return fd->weight; } @@ -1169,7 +1187,8 @@ void TextServerFallback::_font_set_stretch(const RID &p_font_rid, int64_t p_stre MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->stretch = CLAMP(p_stretch, 50, 200); } @@ -1179,7 +1198,8 @@ int64_t TextServerFallback::_font_get_stretch(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 100); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 100); return fd->stretch; } @@ -1189,7 +1209,8 @@ void TextServerFallback::_font_set_name(const RID &p_font_rid, const String &p_n MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->font_name = p_name; } @@ -1199,7 +1220,8 @@ String TextServerFallback::_font_get_name(const RID &p_font_rid) const { MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), String()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), String()); return fd->font_name; } @@ -1607,8 +1629,9 @@ void TextServerFallback::_font_set_ascent(const RID &p_font_rid, int64_t p_size, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->ascent = p_ascent; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->ascent = p_ascent; } double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_size) const { @@ -1618,18 +1641,19 @@ double TextServerFallback::_font_get_ascent(const RID &p_font_rid, int64_t p_siz MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->ascent * (double)p_size / (double)fd->msdf_source_size; + return ffsd->ascent * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->ascent * (double)p_size / (double)fd->fixed_size; + return ffsd->ascent * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->ascent * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->ascent * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->ascent; + return ffsd->ascent; } } @@ -1639,8 +1663,9 @@ void TextServerFallback::_font_set_descent(const RID &p_font_rid, int64_t p_size Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->descent = p_descent; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->descent = p_descent; } double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_size) const { @@ -1650,18 +1675,19 @@ double TextServerFallback::_font_get_descent(const RID &p_font_rid, int64_t p_si MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->descent * (double)p_size / (double)fd->msdf_source_size; + return ffsd->descent * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->descent * (double)p_size / (double)fd->fixed_size; + return ffsd->descent * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->descent * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->descent * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->descent; + return ffsd->descent; } } @@ -1672,8 +1698,9 @@ void TextServerFallback::_font_set_underline_position(const RID &p_font_rid, int MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->underline_position = p_underline_position; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->underline_position = p_underline_position; } double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, int64_t p_size) const { @@ -1683,18 +1710,19 @@ double TextServerFallback::_font_get_underline_position(const RID &p_font_rid, i MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->underline_position * (double)p_size / (double)fd->msdf_source_size; + return ffsd->underline_position * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->underline_position * (double)p_size / (double)fd->fixed_size; + return ffsd->underline_position * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->underline_position * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->underline_position * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->underline_position; + return ffsd->underline_position; } } @@ -1705,8 +1733,9 @@ void TextServerFallback::_font_set_underline_thickness(const RID &p_font_rid, in MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->underline_thickness = p_underline_thickness; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->underline_thickness = p_underline_thickness; } double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, int64_t p_size) const { @@ -1716,18 +1745,19 @@ double TextServerFallback::_font_get_underline_thickness(const RID &p_font_rid, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->msdf_source_size; + return ffsd->underline_thickness * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->underline_thickness * (double)p_size / (double)fd->fixed_size; + return ffsd->underline_thickness * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->underline_thickness * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->underline_thickness; + return ffsd->underline_thickness; } } @@ -1738,13 +1768,14 @@ void TextServerFallback::_font_set_scale(const RID &p_font_rid, int64_t p_size, MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { return; // Do not override scale for dynamic fonts, it's calculated automatically. } #endif - fd->cache[size]->scale = p_scale; + ffsd->scale = p_scale; } double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size) const { @@ -1754,18 +1785,19 @@ double TextServerFallback::_font_get_scale(const RID &p_font_rid, int64_t p_size MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0.0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0.0); if (fd->msdf) { - return fd->cache[size]->scale * (double)p_size / (double)fd->msdf_source_size; + return ffsd->scale * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return fd->cache[size]->scale * (double)p_size / (double)fd->fixed_size; + return ffsd->scale * (double)p_size / (double)fd->fixed_size; } else { - return fd->cache[size]->scale * Math::round((double)p_size / (double)fd->fixed_size); + return ffsd->scale * Math::round((double)p_size / (double)fd->fixed_size); } } else { - return fd->cache[size]->scale / fd->cache[size]->oversampling; + return ffsd->scale / ffsd->oversampling; } } @@ -1776,9 +1808,10 @@ int64_t TextServerFallback::_font_get_texture_count(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), 0); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), 0); - return fd->cache[size]->textures.size(); + return ffsd->textures.size(); } void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vector2i &p_size) { @@ -1787,8 +1820,9 @@ void TextServerFallback::_font_clear_textures(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->textures.clear(); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->textures.clear(); } void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index) { @@ -1797,10 +1831,11 @@ void TextServerFallback::_font_remove_texture(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - ERR_FAIL_INDEX(p_texture_index, fd->cache[size]->textures.size()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ERR_FAIL_INDEX(p_texture_index, ffsd->textures.size()); - fd->cache[size]->textures.remove_at(p_texture_index); + ffsd->textures.remove_at(p_texture_index); } void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Vector2i &p_size, int64_t p_texture_index, const Ref<Image> &p_image) { @@ -1810,13 +1845,14 @@ void TextServerFallback::_font_set_texture_image(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); ERR_FAIL_COND(p_texture_index < 0); - if (p_texture_index >= fd->cache[size]->textures.size()) { - fd->cache[size]->textures.resize(p_texture_index + 1); + if (p_texture_index >= ffsd->textures.size()) { + ffsd->textures.resize(p_texture_index + 1); } - ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + ShelfPackTexture &tex = ffsd->textures.write[p_texture_index]; tex.image = p_image; tex.texture_w = p_image->get_width(); @@ -1837,10 +1873,11 @@ Ref<Image> TextServerFallback::_font_get_texture_image(const RID &p_font_rid, co MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Ref<Image>()); - ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), Ref<Image>()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Ref<Image>()); + ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), Ref<Image>()); - const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index]; + const ShelfPackTexture &tex = ffsd->textures[p_texture_index]; return tex.image; } @@ -1851,13 +1888,14 @@ void TextServerFallback::_font_set_texture_offsets(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); ERR_FAIL_COND(p_texture_index < 0); - if (p_texture_index >= fd->cache[size]->textures.size()) { - fd->cache[size]->textures.resize(p_texture_index + 1); + if (p_texture_index >= ffsd->textures.size()) { + ffsd->textures.resize(p_texture_index + 1); } - ShelfPackTexture &tex = fd->cache[size]->textures.write[p_texture_index]; + ShelfPackTexture &tex = ffsd->textures.write[p_texture_index]; tex.shelves.clear(); for (int32_t i = 0; i < p_offsets.size(); i += 4) { tex.shelves.push_back(Shelf(p_offsets[i], p_offsets[i + 1], p_offsets[i + 2], p_offsets[i + 3])); @@ -1870,10 +1908,11 @@ PackedInt32Array TextServerFallback::_font_get_texture_offsets(const RID &p_font MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); - ERR_FAIL_INDEX_V(p_texture_index, fd->cache[size]->textures.size(), PackedInt32Array()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array()); + ERR_FAIL_INDEX_V(p_texture_index, ffsd->textures.size(), PackedInt32Array()); - const ShelfPackTexture &tex = fd->cache[size]->textures[p_texture_index]; + const ShelfPackTexture &tex = ffsd->textures[p_texture_index]; PackedInt32Array ret; ret.resize(tex.shelves.size() * 4); @@ -1895,10 +1934,11 @@ PackedInt32Array TextServerFallback::_font_get_glyph_list(const RID &p_font_rid, MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), PackedInt32Array()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), PackedInt32Array()); PackedInt32Array ret; - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map; for (const KeyValue<int32_t, FontGlyph> &E : gl) { ret.push_back(E.key); } @@ -1911,9 +1951,10 @@ void TextServerFallback::_font_clear_glyphs(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - fd->cache[size]->glyph_map.clear(); + ffsd->glyph_map.clear(); } void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) { @@ -1922,9 +1963,10 @@ void TextServerFallback::_font_remove_glyph(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - fd->cache[size]->glyph_map.erase(p_glyph); + ffsd->glyph_map.erase(p_glyph); } Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64_t p_size, int64_t p_glyph) const { @@ -1934,22 +1976,22 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64 MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - Vector2 ea; if (fd->embolden != 0.0) { ea.x = fd->embolden * double(size.x) / 64.0; @@ -1957,17 +1999,17 @@ Vector2 TextServerFallback::_font_get_glyph_advance(const RID &p_font_rid, int64 double scale = _font_get_scale(p_font_rid, p_size); if (fd->msdf) { - return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->msdf_source_size; + return (fgl.advance + ea) * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return (gl[p_glyph | mod].advance + ea) * (double)p_size / (double)fd->fixed_size; + return (fgl.advance + ea) * (double)p_size / (double)fd->fixed_size; } else { - return (gl[p_glyph | mod].advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); + return (fgl.advance + ea) * Math::round((double)p_size / (double)fd->fixed_size); } } else if ((scale == 1.0) && ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_DISABLED) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x > SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE))) { - return (gl[p_glyph | mod].advance + ea).round(); + return (fgl.advance + ea).round(); } else { - return gl[p_glyph | mod].advance + ea; + return fgl.advance + ea; } } @@ -1978,12 +2020,13 @@ void TextServerFallback::_font_set_glyph_advance(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].advance = p_advance; - gl[p_glyph].found = true; + fgl.advance = p_advance; + fgl.found = true; } Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -1993,32 +2036,32 @@ Vector2 TextServerFallback::_font_get_glyph_offset(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - if (fd->msdf) { - return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->msdf_source_size; + return fgl.rect.position * (double)p_size.x / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return gl[p_glyph | mod].rect.position * (double)p_size.x / (double)fd->fixed_size; + return fgl.rect.position * (double)p_size.x / (double)fd->fixed_size; } else { - return gl[p_glyph | mod].rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); + return fgl.rect.position * Math::round((double)p_size.x / (double)fd->fixed_size); } } else { - return gl[p_glyph | mod].rect.position; + return fgl.rect.position; } } @@ -2029,12 +2072,13 @@ void TextServerFallback::_font_set_glyph_offset(const RID &p_font_rid, const Vec MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].rect.position = p_offset; - gl[p_glyph].found = true; + fgl.rect.position = p_offset; + fgl.found = true; } Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2044,32 +2088,32 @@ Vector2 TextServerFallback::_font_get_glyph_size(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Vector2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - if (fd->msdf) { - return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->msdf_source_size; + return fgl.rect.size * (double)p_size.x / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size.x) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { - return gl[p_glyph | mod].rect.size * (double)p_size.x / (double)fd->fixed_size; + return fgl.rect.size * (double)p_size.x / (double)fd->fixed_size; } else { - return gl[p_glyph | mod].rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); + return fgl.rect.size * Math::round((double)p_size.x / (double)fd->fixed_size); } } else { - return gl[p_glyph | mod].rect.size; + return fgl.rect.size; } } @@ -2080,12 +2124,13 @@ void TextServerFallback::_font_set_glyph_size(const RID &p_font_rid, const Vecto MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].rect.size = p_gl_size; - gl[p_glyph].found = true; + fgl.rect.size = p_gl_size; + fgl.found = true; } Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2095,22 +2140,23 @@ Rect2 TextServerFallback::_font_get_glyph_uv_rect(const RID &p_font_rid, const V MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Rect2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Rect2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Rect2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - return gl[p_glyph | mod].uv_rect; + return fgl.uv_rect; } void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, const Rect2 &p_uv_rect) { @@ -2120,12 +2166,13 @@ void TextServerFallback::_font_set_glyph_uv_rect(const RID &p_font_rid, const Ve MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].uv_rect = p_uv_rect; - gl[p_glyph].found = true; + fgl.uv_rect = p_uv_rect; + fgl.found = true; } int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2135,22 +2182,23 @@ int64_t TextServerFallback::_font_get_glyph_texture_idx(const RID &p_font_rid, c MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), -1); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), -1); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return -1; // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - return gl[p_glyph | mod].texture_idx; + return fgl.texture_idx; } void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph, int64_t p_texture_idx) { @@ -2160,12 +2208,13 @@ void TextServerFallback::_font_set_glyph_texture_idx(const RID &p_font_rid, cons MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); - HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; + FontGlyph &fgl = ffsd->glyph_map[p_glyph]; - gl[p_glyph].texture_idx = p_texture_idx; - gl[p_glyph].found = true; + fgl.texture_idx = p_texture_idx; + fgl.found = true; } RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const Vector2i &p_size, int64_t p_glyph) const { @@ -2175,27 +2224,28 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), RID()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), RID()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return RID(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), RID()); + ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), RID()); if (RenderingServer::get_singleton() != nullptr) { - if (gl[p_glyph | mod].texture_idx != -1) { - if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx]; + if (fgl.texture_idx != -1) { + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -2208,7 +2258,7 @@ RID TextServerFallback::_font_get_glyph_texture_rid(const RID &p_font_rid, const } tex.dirty = false; } - return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_rid(); + return ffsd->textures[fgl.texture_idx].texture->get_rid(); } } @@ -2222,27 +2272,28 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Size2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Size2()); int mod = 0; if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { mod = (layout << 24); } } - if (!_ensure_glyph(fd, size, p_glyph | mod)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, p_glyph | mod, fgl)) { return Size2(); // Invalid or non graphicl glyph, do not display errors. } - const HashMap<int32_t, FontGlyph> &gl = fd->cache[size]->glyph_map; - ERR_FAIL_COND_V(gl[p_glyph | mod].texture_idx < -1 || gl[p_glyph | mod].texture_idx >= fd->cache[size]->textures.size(), Size2()); + ERR_FAIL_COND_V(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size(), Size2()); if (RenderingServer::get_singleton() != nullptr) { - if (gl[p_glyph | mod].texture_idx != -1) { - if (fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl[p_glyph | mod].texture_idx]; + if (fgl.texture_idx != -1) { + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -2255,7 +2306,7 @@ Size2 TextServerFallback::_font_get_glyph_texture_size(const RID &p_font_rid, co } tex.dirty = false; } - return fd->cache[size]->textures[gl[p_glyph | mod].texture_idx].texture->get_size(); + return ffsd->textures[fgl.texture_idx].texture->get_size(); } } @@ -2269,7 +2320,8 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); #ifdef MODULE_FREETYPE_ENABLED PackedVector3Array points; @@ -2277,20 +2329,20 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i int32_t index = p_index & 0xffffff; // Remove subpixel shifts. - int error = FT_Load_Glyph(fd->cache[size]->face, FT_Get_Char_Index(fd->cache[size]->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); + int error = FT_Load_Glyph(ffsd->face, FT_Get_Char_Index(ffsd->face, index), FT_LOAD_NO_BITMAP | (fd->force_autohinter ? FT_LOAD_FORCE_AUTOHINT : 0)); ERR_FAIL_COND_V(error, Dictionary()); if (fd->embolden != 0.f) { FT_Pos strength = fd->embolden * p_size * 4; // 26.6 fractional units (1 / 64). - FT_Outline_Embolden(&fd->cache[size]->face->glyph->outline, strength); + FT_Outline_Embolden(&ffsd->face->glyph->outline, strength); } if (fd->transform != Transform2D()) { FT_Matrix mat = { FT_Fixed(fd->transform[0][0] * 65536), FT_Fixed(fd->transform[0][1] * 65536), FT_Fixed(fd->transform[1][0] * 65536), FT_Fixed(fd->transform[1][1] * 65536) }; // 16.16 fractional units (1 / 65536). - FT_Outline_Transform(&fd->cache[size]->face->glyph->outline, &mat); + FT_Outline_Transform(&ffsd->face->glyph->outline, &mat); } - double scale = (1.0 / 64.0) / fd->cache[size]->oversampling * fd->cache[size]->scale; + double scale = (1.0 / 64.0) / ffsd->oversampling * ffsd->scale; if (fd->msdf) { scale = scale * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { @@ -2300,13 +2352,13 @@ Dictionary TextServerFallback::_font_get_glyph_contours(const RID &p_font_rid, i scale = scale * Math::round((double)p_size / (double)fd->fixed_size); } } - for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_points; i++) { - points.push_back(Vector3(fd->cache[size]->face->glyph->outline.points[i].x * scale, -fd->cache[size]->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(fd->cache[size]->face->glyph->outline.tags[i]))); + for (short i = 0; i < ffsd->face->glyph->outline.n_points; i++) { + points.push_back(Vector3(ffsd->face->glyph->outline.points[i].x * scale, -ffsd->face->glyph->outline.points[i].y * scale, FT_CURVE_TAG(ffsd->face->glyph->outline.tags[i]))); } - for (short i = 0; i < fd->cache[size]->face->glyph->outline.n_contours; i++) { - contours.push_back(fd->cache[size]->face->glyph->outline.contours[i]); + for (short i = 0; i < ffsd->face->glyph->outline.n_contours; i++) { + contours.push_back(ffsd->face->glyph->outline.contours[i]); } - bool orientation = (FT_Outline_Get_Orientation(&fd->cache[size]->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); + bool orientation = (FT_Outline_Get_Orientation(&ffsd->face->glyph->outline) == FT_ORIENTATION_FILL_RIGHT); Dictionary out; out["points"] = points; @@ -2325,10 +2377,11 @@ TypedArray<Vector2i> TextServerFallback::_font_get_kerning_list(const RID &p_fon MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), TypedArray<Vector2i>()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), TypedArray<Vector2i>()); TypedArray<Vector2i> ret; - for (const KeyValue<Vector2i, Vector2> &E : fd->cache[size]->kerning_map) { + for (const KeyValue<Vector2i, Vector2> &E : ffsd->kerning_map) { ret.push_back(E.key); } return ret; @@ -2341,8 +2394,9 @@ void TextServerFallback::_font_clear_kerning_map(const RID &p_font_rid, int64_t MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map.clear(); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map.clear(); } void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) { @@ -2352,8 +2406,9 @@ void TextServerFallback::_font_remove_kerning(const RID &p_font_rid, int64_t p_s MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map.erase(p_glyph_pair); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map.erase(p_glyph_pair); } void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair, const Vector2 &p_kerning) { @@ -2363,8 +2418,9 @@ void TextServerFallback::_font_set_kerning(const RID &p_font_rid, int64_t p_size MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); - fd->cache[size]->kerning_map[p_glyph_pair] = p_kerning; + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); + ffsd->kerning_map[p_glyph_pair] = p_kerning; } Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_size, const Vector2i &p_glyph_pair) const { @@ -2374,9 +2430,10 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Vector2()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Vector2()); - const HashMap<Vector2i, Vector2> &kern = fd->cache[size]->kerning_map; + const HashMap<Vector2i, Vector2> &kern = ffsd->kerning_map; if (kern.has(p_glyph_pair)) { if (fd->msdf) { @@ -2392,11 +2449,11 @@ Vector2 TextServerFallback::_font_get_kerning(const RID &p_font_rid, int64_t p_s } } else { #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face) { + if (ffsd->face) { FT_Vector delta; - int32_t glyph_a = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.x); - int32_t glyph_b = FT_Get_Char_Index(fd->cache[size]->face, p_glyph_pair.y); - FT_Get_Kerning(fd->cache[size]->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta); + int32_t glyph_a = FT_Get_Char_Index(ffsd->face, p_glyph_pair.x); + int32_t glyph_b = FT_Get_Char_Index(ffsd->face, p_glyph_pair.y); + FT_Get_Kerning(ffsd->face, glyph_a, glyph_b, FT_KERNING_DEFAULT, &delta); if (fd->msdf) { return Vector2(delta.x, delta.y) * (double)p_size / (double)fd->msdf_source_size; } else if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { @@ -2431,17 +2488,19 @@ bool TextServerFallback::_font_has_char(const RID &p_font_rid, int64_t p_char) c } MutexLock lock(fd->mutex); + FontForSizeFallback *ffsd = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), false); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), false); + } else { + ffsd = fd->cache.begin()->value; } - FontForSizeFallback *at_size = fd->cache.begin()->value; #ifdef MODULE_FREETYPE_ENABLED - if (at_size && at_size->face) { - return FT_Get_Char_Index(at_size->face, p_char) != 0; + if (ffsd->face) { + return FT_Get_Char_Index(ffsd->face, p_char) != 0; } #endif - return (at_size) ? at_size->glyph_map.has((int32_t)p_char) : false; + return ffsd->glyph_map.has((int32_t)p_char); } String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) const { @@ -2449,30 +2508,30 @@ String TextServerFallback::_font_get_supported_chars(const RID &p_font_rid) cons ERR_FAIL_NULL_V(fd, String()); MutexLock lock(fd->mutex); + FontForSizeFallback *ffsd = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), String()); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), ffsd), String()); + } else { + ffsd = fd->cache.begin()->value; } - FontForSizeFallback *at_size = fd->cache.begin()->value; String chars; #ifdef MODULE_FREETYPE_ENABLED - if (at_size && at_size->face) { + if (ffsd->face) { FT_UInt gindex; - FT_ULong charcode = FT_Get_First_Char(at_size->face, &gindex); + FT_ULong charcode = FT_Get_First_Char(ffsd->face, &gindex); while (gindex != 0) { if (charcode != 0) { chars = chars + String::chr(charcode); } - charcode = FT_Get_Next_Char(at_size->face, charcode, &gindex); + charcode = FT_Get_Next_Char(ffsd->face, charcode, &gindex); } return chars; } #endif - if (at_size) { - const HashMap<int32_t, FontGlyph> &gl = at_size->glyph_map; - for (const KeyValue<int32_t, FontGlyph> &E : gl) { - chars = chars + String::chr(E.key); - } + const HashMap<int32_t, FontGlyph> &gl = ffsd->glyph_map; + for (const KeyValue<int32_t, FontGlyph> &E : gl) { + chars = chars + String::chr(E.key); } return chars; } @@ -2482,10 +2541,12 @@ PackedInt32Array TextServerFallback::_font_get_supported_glyphs(const RID &p_fon ERR_FAIL_NULL_V(fd, PackedInt32Array()); MutexLock lock(fd->mutex); + FontForSizeFallback *at_size = nullptr; if (fd->cache.is_empty()) { - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0)), PackedInt32Array()); + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, fd->msdf ? Vector2i(fd->msdf_source_size, 0) : Vector2i(16, 0), at_size), PackedInt32Array()); + } else { + at_size = fd->cache.begin()->value; } - FontForSizeFallback *at_size = fd->cache.begin()->value; PackedInt32Array glyphs; #ifdef MODULE_FREETYPE_ENABLED @@ -2516,25 +2577,27 @@ void TextServerFallback::_font_render_range(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); for (int64_t i = p_start; i <= p_end; i++) { #ifdef MODULE_FREETYPE_ENABLED int32_t idx = i; - if (fd->cache[size]->face) { + if (ffsd->face) { + FontGlyph fgl; if (fd->msdf) { - _ensure_glyph(fd, size, (int32_t)idx); + _ensure_glyph(fd, size, (int32_t)idx, fgl); } else { for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) { if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl); } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); } else { - _ensure_glyph(fd, size, (int32_t)idx | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl); } } } @@ -2549,24 +2612,26 @@ void TextServerFallback::_font_render_glyph(const RID &p_font_rid, const Vector2 MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); #ifdef MODULE_FREETYPE_ENABLED int32_t idx = p_index & 0xffffff; // Remove subpixel shifts. - if (fd->cache[size]->face) { + if (ffsd->face) { + FontGlyph fgl; if (fd->msdf) { - _ensure_glyph(fd, size, (int32_t)idx); + _ensure_glyph(fd, size, (int32_t)idx, fgl); } else { for (int aa = 0; aa < ((fd->antialiasing == FONT_ANTIALIASING_LCD) ? FONT_LCD_SUBPIXEL_LAYOUT_MAX : 1); aa++) { if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_QUARTER) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_QUARTER_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (2 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (3 << 27) | (aa << 24), fgl); } else if ((fd->subpixel_positioning == SUBPIXEL_POSITIONING_ONE_HALF) || (fd->subpixel_positioning == SUBPIXEL_POSITIONING_AUTO && size.x <= SUBPIXEL_POSITIONING_ONE_HALF_MAX_SIZE)) { - _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24)); - _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (1 << 27) | (aa << 24), fgl); + _ensure_glyph(fd, size, (int32_t)idx | (0 << 27) | (aa << 24), fgl); } else { - _ensure_glyph(fd, size, (int32_t)idx | (aa << 24)); + _ensure_glyph(fd, size, (int32_t)idx | (aa << 24), fgl); } } } @@ -2583,16 +2648,17 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, p_size); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); int32_t index = p_index & 0xffffff; // Remove subpixel shifts. bool lcd_aa = false; #ifdef MODULE_FREETYPE_ENABLED - if (!fd->msdf && fd->cache[size]->face) { + if (!fd->msdf && ffsd->face) { // LCD layout, bits 24, 25, 26 if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { lcd_aa = true; index = index | (layout << 24); @@ -2609,24 +2675,24 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } #endif - if (!_ensure_glyph(fd, size, index)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, index, fgl)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[index]; - if (gl.found) { - ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + if (fgl.found) { + ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size()); - if (gl.texture_idx != -1) { + if (fgl.texture_idx != -1) { Color modulate = p_color; #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { + if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { modulate.r = modulate.g = modulate.b = 1.0; } #endif if (RenderingServer::get_singleton() != nullptr) { - if (fd->cache[size]->textures[gl.texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx]; + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -2639,12 +2705,12 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } tex.dirty = false; } - RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid(); if (fd->msdf) { Point2 cpos = p_pos; - cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size; - Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size; - RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); + cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size; + Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, 0, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); } else { Point2 cpos = p_pos; double scale = _font_get_scale(p_font_rid, p_size); @@ -2657,8 +2723,8 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - Vector2 gpos = gl.rect.position; - Size2 csize = gl.rect.size; + Vector2 gpos = fgl.rect.position; + Size2 csize = fgl.rect.size; if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { double gl_scale = (double)p_size / (double)fd->fixed_size; @@ -2672,9 +2738,9 @@ void TextServerFallback::_font_draw_glyph(const RID &p_font_rid, const RID &p_ca } cpos += gpos; if (lcd_aa) { - RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); + RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate); } else { - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false); } } } @@ -2691,16 +2757,17 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R MutexLock lock(fd->mutex); Vector2i size = _get_size_outline(fd, Vector2i(p_size, p_outline_size)); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); int32_t index = p_index & 0xffffff; // Remove subpixel shifts. bool lcd_aa = false; #ifdef MODULE_FREETYPE_ENABLED - if (!fd->msdf && fd->cache[size]->face) { + if (!fd->msdf && ffsd->face) { // LCD layout, bits 24, 25, 26 if (fd->antialiasing == FONT_ANTIALIASING_LCD) { - TextServer::FontLCDSubpixelLayout layout = (TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout"); + TextServer::FontLCDSubpixelLayout layout = lcd_subpixel_layout.get(); if (layout != FONT_LCD_SUBPIXEL_LAYOUT_NONE) { lcd_aa = true; index = index | (layout << 24); @@ -2717,24 +2784,24 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R } #endif - if (!_ensure_glyph(fd, size, index)) { + FontGlyph fgl; + if (!_ensure_glyph(fd, size, index, fgl)) { return; // Invalid or non-graphical glyph, do not display errors, nothing to draw. } - const FontGlyph &gl = fd->cache[size]->glyph_map[index]; - if (gl.found) { - ERR_FAIL_COND(gl.texture_idx < -1 || gl.texture_idx >= fd->cache[size]->textures.size()); + if (fgl.found) { + ERR_FAIL_COND(fgl.texture_idx < -1 || fgl.texture_idx >= ffsd->textures.size()); - if (gl.texture_idx != -1) { + if (fgl.texture_idx != -1) { Color modulate = p_color; #ifdef MODULE_FREETYPE_ENABLED - if (fd->cache[size]->face && fd->cache[size]->textures[gl.texture_idx].image.is_valid() && (fd->cache[size]->textures[gl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { + if (ffsd->face && ffsd->textures[fgl.texture_idx].image.is_valid() && (ffsd->textures[fgl.texture_idx].image->get_format() == Image::FORMAT_RGBA8) && !lcd_aa && !fd->msdf) { modulate.r = modulate.g = modulate.b = 1.0; } #endif if (RenderingServer::get_singleton() != nullptr) { - if (fd->cache[size]->textures[gl.texture_idx].dirty) { - ShelfPackTexture &tex = fd->cache[size]->textures.write[gl.texture_idx]; + if (ffsd->textures[fgl.texture_idx].dirty) { + ShelfPackTexture &tex = ffsd->textures.write[fgl.texture_idx]; Ref<Image> img = tex.image; if (fd->mipmaps && !img->has_mipmaps()) { img = tex.image->duplicate(); @@ -2747,12 +2814,12 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R } tex.dirty = false; } - RID texture = fd->cache[size]->textures[gl.texture_idx].texture->get_rid(); + RID texture = ffsd->textures[fgl.texture_idx].texture->get_rid(); if (fd->msdf) { Point2 cpos = p_pos; - cpos += gl.rect.position * (double)p_size / (double)fd->msdf_source_size; - Size2 csize = gl.rect.size * (double)p_size / (double)fd->msdf_source_size; - RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); + cpos += fgl.rect.position * (double)p_size / (double)fd->msdf_source_size; + Size2 csize = fgl.rect.size * (double)p_size / (double)fd->msdf_source_size; + RenderingServer::get_singleton()->canvas_item_add_msdf_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, p_outline_size, fd->msdf_range, (double)p_size / (double)fd->msdf_source_size); } else { Point2 cpos = p_pos; double scale = _font_get_scale(p_font_rid, p_size); @@ -2765,8 +2832,8 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R cpos.y = Math::floor(cpos.y); cpos.x = Math::floor(cpos.x); } - Vector2 gpos = gl.rect.position; - Size2 csize = gl.rect.size; + Vector2 gpos = fgl.rect.position; + Size2 csize = fgl.rect.size; if (fd->fixed_size > 0 && fd->fixed_size_scale_mode != FIXED_SIZE_SCALE_DISABLE && size.x != p_size) { if (fd->fixed_size_scale_mode == FIXED_SIZE_SCALE_ENABLED) { double gl_scale = (double)p_size / (double)fd->fixed_size; @@ -2780,9 +2847,9 @@ void TextServerFallback::_font_draw_glyph_outline(const RID &p_font_rid, const R } cpos += gpos; if (lcd_aa) { - RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate); + RenderingServer::get_singleton()->canvas_item_add_lcd_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate); } else { - RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, gl.uv_rect, modulate, false, false); + RenderingServer::get_singleton()->canvas_item_add_texture_rect_region(p_canvas, Rect2(cpos, csize), texture, fgl.uv_rect, modulate, false, false); } } } @@ -2872,7 +2939,8 @@ void TextServerFallback::_font_remove_script_support_override(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->script_support_overrides.erase(p_script); } @@ -2894,7 +2962,8 @@ void TextServerFallback::_font_set_opentype_feature_overrides(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND(!_ensure_cache_for_size(fd, size)); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND(!_ensure_cache_for_size(fd, size, ffsd)); fd->feature_overrides = p_overrides; } @@ -2916,7 +2985,8 @@ Dictionary TextServerFallback::_font_supported_variation_list(const RID &p_font_ MutexLock lock(fd->mutex); Vector2i size = _get_size(fd, 16); - ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size), Dictionary()); + FontForSizeFallback *ffsd = nullptr; + ERR_FAIL_COND_V(!_ensure_cache_for_size(fd, size, ffsd), Dictionary()); return fd->supported_varaitions; } @@ -2953,7 +3023,7 @@ void TextServerFallback::_font_set_global_oversampling(double p_oversampling) { /*************************************************************************/ void TextServerFallback::invalidate(ShapedTextDataFallback *p_shaped) { - p_shaped->valid = false; + p_shaped->valid.clear(); p_shaped->sort_valid = false; p_shaped->line_breaks_valid = false; p_shaped->justification_ops_valid = false; @@ -3192,7 +3262,7 @@ void TextServerFallback::_shaped_set_span_update_font(const RID &p_shaped, int64 span.font_size = p_size; span.features = p_opentype_features; - sd->valid = false; + sd->valid.clear(); } bool TextServerFallback::_shaped_text_add_string(const RID &p_shaped, const String &p_text, const TypedArray<RID> &p_fonts, int64_t p_size, const Dictionary &p_opentype_features, const String &p_language, const Variant &p_meta) { @@ -3288,7 +3358,7 @@ bool TextServerFallback::_shaped_text_resize_object(const RID &p_shaped, const V sd->objects[p_key].rect.size = p_size; sd->objects[p_key].inline_align = p_inline_align; sd->objects[p_key].baseline = p_baseline; - if (sd->valid) { + if (sd->valid.is_set()) { // Recalc string metrics. sd->ascent = 0; sd->descent = 0; @@ -3431,7 +3501,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start if (sd->parent != RID()) { return _shaped_text_substr(sd->parent, p_start, p_length); } - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } ERR_FAIL_COND_V(p_start < 0 || p_length < 0, RID()); @@ -3514,7 +3584,7 @@ RID TextServerFallback::_shaped_text_substr(const RID &p_shaped, int64_t p_start _realign(new_sd); } - new_sd->valid = true; + new_sd->valid.set(); return shaped_owner.make_rid(new_sd); } @@ -3532,7 +3602,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } if (!sd->justification_ops_valid) { @@ -3641,7 +3711,7 @@ double TextServerFallback::_shaped_text_tab_align(const RID &p_shaped, const Pac ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } if (!sd->line_breaks_valid) { @@ -3697,7 +3767,7 @@ bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) { ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped); } @@ -3761,7 +3831,7 @@ bool TextServerFallback::_shaped_text_update_justification_ops(const RID &p_shap ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped); } if (!sd->line_breaks_valid) { @@ -3940,7 +4010,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ ERR_FAIL_NULL_MSG(sd, "ShapedTextDataFallback invalid."); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { _shaped_text_shape(p_shaped_line); } @@ -3967,7 +4037,7 @@ void TextServerFallback::_shaped_text_overrun_trim_to_width(const RID &p_shaped_ Vector<ShapedTextDataFallback::Span> &spans = sd->spans; if (sd->parent != RID()) { ShapedTextDataFallback *parent_sd = shaped_owner.get_or_null(sd->parent); - ERR_FAIL_COND(!parent_sd->valid); + ERR_FAIL_COND(!parent_sd->valid.is_set()); spans = parent_sd->spans; } @@ -4161,7 +4231,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { ERR_FAIL_NULL_V(sd, false); MutexLock lock(sd->mutex); - if (sd->valid) { + if (sd->valid.is_set()) { return true; } @@ -4178,7 +4248,7 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { sd->glyphs.clear(); if (sd->text.length() == 0) { - sd->valid = true; + sd->valid.set(); return true; } @@ -4307,16 +4377,15 @@ bool TextServerFallback::_shaped_text_shape(const RID &p_shaped) { // Align embedded objects to baseline. _realign(sd); - sd->valid = true; - return sd->valid; + sd->valid.set(); + return sd->valid.is_set(); } bool TextServerFallback::_shaped_text_is_ready(const RID &p_shaped) const { const ShapedTextDataFallback *sd = shaped_owner.get_or_null(p_shaped); ERR_FAIL_NULL_V(sd, false); - MutexLock lock(sd->mutex); - return sd->valid; + return sd->valid.is_set(); } const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) const { @@ -4324,7 +4393,7 @@ const Glyph *TextServerFallback::_shaped_text_get_glyphs(const RID &p_shaped) co ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->glyphs.ptr(); @@ -4335,7 +4404,7 @@ int64_t TextServerFallback::_shaped_text_get_glyph_count(const RID &p_shaped) co ERR_FAIL_NULL_V(sd, 0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->glyphs.size(); @@ -4346,7 +4415,7 @@ const Glyph *TextServerFallback::_shaped_text_sort_logical(const RID &p_shaped) ERR_FAIL_NULL_V(sd, nullptr); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } @@ -4380,7 +4449,7 @@ Rect2 TextServerFallback::_shaped_text_get_object_rect(const RID &p_shaped, cons MutexLock lock(sd->mutex); ERR_FAIL_COND_V(!sd->objects.has(p_key), Rect2()); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->objects[p_key].rect; @@ -4417,7 +4486,7 @@ Size2 TextServerFallback::_shaped_text_get_size(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, Size2()); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } if (sd->orientation == TextServer::ORIENTATION_HORIZONTAL) { @@ -4432,7 +4501,7 @@ double TextServerFallback::_shaped_text_get_ascent(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->ascent + sd->extra_spacing[SPACING_TOP]; @@ -4443,7 +4512,7 @@ double TextServerFallback::_shaped_text_get_descent(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return sd->descent + sd->extra_spacing[SPACING_BOTTOM]; @@ -4454,7 +4523,7 @@ double TextServerFallback::_shaped_text_get_width(const RID &p_shaped) const { ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } return Math::ceil(sd->width); @@ -4465,7 +4534,7 @@ double TextServerFallback::_shaped_text_get_underline_position(const RID &p_shap ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } @@ -4477,7 +4546,7 @@ double TextServerFallback::_shaped_text_get_underline_thickness(const RID &p_sha ERR_FAIL_NULL_V(sd, 0.0); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } @@ -4489,7 +4558,7 @@ PackedInt32Array TextServerFallback::_shaped_text_get_character_breaks(const RID ERR_FAIL_NULL_V(sd, PackedInt32Array()); MutexLock lock(sd->mutex); - if (!sd->valid) { + if (!sd->valid.is_set()) { const_cast<TextServerFallback *>(this)->_shaped_text_shape(p_shaped); } @@ -4626,8 +4695,13 @@ PackedInt32Array TextServerFallback::_string_get_word_breaks(const String &p_str return ret; } +void TextServerFallback::_update_settings() { + lcd_subpixel_layout.set((TextServer::FontLCDSubpixelLayout)(int)GLOBAL_GET("gui/theme/lcd_subpixel_layout")); +} + TextServerFallback::TextServerFallback() { _insert_feature_sets(); + ProjectSettings::get_singleton()->connect("settings_changed", callable_mp(this, &TextServerFallback::_update_settings)); }; void TextServerFallback::_cleanup() { diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h index 1b76c6fa0f..ee1f72401f 100644 --- a/modules/text_server_fb/text_server_fb.h +++ b/modules/text_server_fb/text_server_fb.h @@ -72,6 +72,7 @@ #include <godot_cpp/templates/hash_map.hpp> #include <godot_cpp/templates/hash_set.hpp> #include <godot_cpp/templates/rid_owner.hpp> +#include <godot_cpp/templates/safe_refcount.hpp> #include <godot_cpp/templates/vector.hpp> using namespace godot; @@ -83,6 +84,7 @@ using namespace godot; #include "core/object/worker_thread_pool.h" #include "core/templates/hash_map.h" #include "core/templates/rid_owner.h" +#include "core/templates/safe_refcount.h" #include "scene/resources/image_texture.h" #include "servers/text/text_server_extension.h" @@ -116,6 +118,9 @@ class TextServerFallback : public TextServerExtension { HashMap<StringName, int32_t> feature_sets; HashMap<int32_t, StringName> feature_sets_inv; + SafeNumeric<TextServer::FontLCDSubpixelLayout> lcd_subpixel_layout{ TextServer::FontLCDSubpixelLayout::FONT_LCD_SUBPIXEL_LAYOUT_NONE }; + void _update_settings(); + void _insert_feature_sets(); _FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag); @@ -278,7 +283,7 @@ class TextServerFallback : public TextServerExtension { int extra_spacing[4] = { 0, 0, 0, 0 }; double baseline_offset = 0.0; - HashMap<Vector2i, FontForSizeFallback *, VariantHasher, VariantComparator> cache; + HashMap<Vector2i, FontForSizeFallback *> cache; bool face_init = false; Dictionary supported_varaitions; @@ -308,8 +313,8 @@ class TextServerFallback : public TextServerExtension { #ifdef MODULE_FREETYPE_ENABLED _FORCE_INLINE_ FontGlyph rasterize_bitmap(FontForSizeFallback *p_data, int p_rect_margin, FT_Bitmap p_bitmap, int p_yofs, int p_xofs, const Vector2 &p_advance, bool p_bgra) const; #endif - _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const; - _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const; + _FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph, FontGlyph &r_glyph) const; + _FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size, FontForSizeFallback *&r_cache_for_size) const; _FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data); static void _generateMTSDF_threaded(void *p_td, uint32_t p_y); @@ -432,7 +437,7 @@ class TextServerFallback : public TextServerExtension { /* Shaped data */ TextServer::Direction para_direction = DIRECTION_LTR; // Detected text direction. - bool valid = false; // String is shaped. + SafeFlag valid{ false }; // String is shaped. bool line_breaks_valid = false; // Line and word break flags are populated (and virtual zero width spaces inserted). bool justification_ops_valid = false; // Virtual elongation glyphs are added to the string. bool sort_valid = false; diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 8dc0e869d0..5bb520bd73 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -455,11 +455,15 @@ Size2i DisplayServerAndroid::window_get_size_with_decorations(DisplayServer::Win } void DisplayServerAndroid::window_set_mode(DisplayServer::WindowMode p_mode, DisplayServer::WindowID p_window) { - // Not supported on Android. + OS_Android::get_singleton()->get_godot_java()->enable_immersive_mode(p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN); } DisplayServer::WindowMode DisplayServerAndroid::window_get_mode(DisplayServer::WindowID p_window) const { - return WINDOW_MODE_FULLSCREEN; + if (OS_Android::get_singleton()->get_godot_java()->is_in_immersive_mode()) { + return WINDOW_MODE_FULLSCREEN; + } else { + return WINDOW_MODE_MAXIMIZED; + } } bool DisplayServerAndroid::window_is_maximize_allowed(DisplayServer::WindowID p_window) const { diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 0fdaca4839..f8ac591a78 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -2002,7 +2002,7 @@ String EditorExportPlatformAndroid::get_device_architecture(int p_index) const { return devices[p_index].architecture; } -Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); String can_export_error; @@ -2024,11 +2024,11 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, } const bool use_wifi_for_remote_debug = EDITOR_GET("export/android/use_wifi_for_remote_debug"); - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); const bool use_reverse = devices[p_device].api_level >= 21 && !use_wifi_for_remote_debug; if (use_reverse) { - p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST; + p_debug_flags.set_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST); } String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk"); @@ -2107,7 +2107,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, OS::get_singleton()->execute(adb, args, &output, &rv, true); print_verbose(output); - if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { int dbg_port = EDITOR_GET("network/debug/remote_port"); args.clear(); args.push_back("-s"); @@ -2122,7 +2122,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset, print_line("Reverse result: " + itos(rv)); } - if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { int fs_port = EDITOR_GET("filesystem/file_server/port"); args.clear(); @@ -2667,7 +2667,7 @@ Error EditorExportPlatformAndroid::save_apk_expansion_file(const Ref<EditorExpor return err; } -void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags) { +void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags) { String cmdline = p_preset->get("command_line/extra_args"); Vector<String> command_line_strings = cmdline.strip_edges().split(" "); for (int i = 0; i < command_line_strings.size(); i++) { @@ -2677,7 +2677,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP } } - gen_export_flags(command_line_strings, p_flags); + command_line_strings.append_array(gen_export_flags(p_flags)); bool apk_expansion = p_preset->get("apk_expansion/enable"); if (apk_expansion) { @@ -2700,7 +2700,7 @@ void EditorExportPlatformAndroid::get_command_line_flags(const Ref<EditorExportP bool immersive = p_preset->get("screen/immersive_mode"); if (immersive) { - command_line_strings.push_back("--use_immersive"); + command_line_strings.push_back("--fullscreen"); } bool debug_opengl = p_preset->get("graphics/opengl_debug"); @@ -3000,13 +3000,13 @@ bool EditorExportPlatformAndroid::_is_clean_build_required(const Ref<EditorExpor return have_plugins_changed || has_build_dir_changed || first_build; } -Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformAndroid::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { int export_format = int(p_preset->get("gradle_build/export_format")); bool should_sign = p_preset->get("package/signed"); return export_project_helper(p_preset, p_debug, p_path, export_format, should_sign, p_flags); } -Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags) { +Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String base_dir = p_path.get_base_dir(); @@ -3022,7 +3022,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP bool use_gradle_build = bool(p_preset->get("gradle_build/use_gradle_build")); String gradle_build_directory = use_gradle_build ? ExportTemplateManager::get_android_build_directory(p_preset) : ""; - bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG); + bool p_give_internet = p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT) || p_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG); bool apk_expansion = p_preset->get("apk_expansion/enable"); Vector<ABI> enabled_abis = get_enabled_abis(p_preset); @@ -3127,7 +3127,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP user_data.assets_directory = assets_directory; user_data.libs_directory = gradle_build_directory.path_join("libs"); user_data.debug = p_debug; - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { err = export_project_files(p_preset, p_debug, ignore_apk_file, &user_data, copy_gradle_so); } else { err = export_project_files(p_preset, p_debug, rename_and_store_file_in_gradle_project, &user_data, copy_gradle_so); @@ -3500,7 +3500,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP } err = OK; - if (p_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { APKExportData ed; ed.ep = &ep; ed.apk = unaligned_apk; diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index 97bbd0c7bc..708288fbf4 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -214,7 +214,7 @@ public: virtual String get_device_architecture(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual Ref<Texture2D> get_run_icon() const override; @@ -242,7 +242,7 @@ public: Error save_apk_expansion_file(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path); - void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, int p_flags, Vector<uint8_t> &r_command_line_flags); + void get_command_line_flags(const Ref<EditorExportPreset> &p_preset, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, Vector<uint8_t> &r_command_line_flags); Error sign_apk(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &export_path, EditorProgress &ep); @@ -253,9 +253,9 @@ public: static String join_list(const List<String> &p_parts, const String &p_separator); static String join_abis(const Vector<ABI> &p_parts, const String &p_separator, bool p_use_arch); - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; - Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, int p_flags); + Error export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int export_format, bool should_sign, BitField<EditorExportPlatform::DebugFlags> p_flags); virtual void get_platform_features(List<String> *r_features) const override; 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/editor/build.gradle b/platform/android/java/editor/build.gradle index f9a3e10680..b8b4233636 100644 --- a/platform/android/java/editor/build.gradle +++ b/platform/android/java/editor/build.gradle @@ -37,7 +37,7 @@ ext { // Return the keystore file used for signing the release build. getGodotKeystoreFile = { -> def keyStore = System.getenv("GODOT_ANDROID_SIGN_KEYSTORE") - if (keyStore == null) { + if (keyStore == null || keyStore.isEmpty()) { return null } return file(keyStore) 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 ba1185d647..b16e62149a 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 @@ -176,7 +176,7 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) { registerMessenger(senderId, senderMessenger) // Register ourselves to the sender so that it can communicate with us. - registerSelfTo(pm, senderMessenger, editor.getEditorId()) + registerSelfTo(pm, senderMessenger, editor.getEditorWindowInfo().windowId) } /** @@ -185,7 +185,7 @@ internal class EditorMessageDispatcher(private val editor: GodotEditor) { */ fun getMessageDispatcherPayload(): Bundle { return Bundle().apply { - putInt(KEY_EDITOR_ID, editor.getEditorId()) + putInt(KEY_EDITOR_ID, editor.getEditorWindowInfo().windowId) putParcelable(KEY_EDITOR_MESSENGER, Messenger(dispatcherHandler)) } } 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/GodotEditor.kt index 5d6da06f97..1995a38c2a 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/GodotEditor.kt @@ -40,6 +40,7 @@ import android.content.pm.PackageManager import android.os.* import android.util.Log import android.view.View +import android.view.WindowManager import android.widget.Toast import androidx.annotation.CallSuper import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen @@ -78,6 +79,8 @@ open class GodotEditor : GodotActivity() { protected val EXTRA_LAUNCH_IN_PIP = "launch_in_pip_requested" // 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" @@ -116,11 +119,16 @@ open class GodotEditor : GodotActivity() { override fun getGodotAppLayout() = R.layout.godot_editor_layout - internal open fun getEditorId() = EDITOR_MAIN_INFO.windowId + internal open fun getEditorWindowInfo() = EDITOR_MAIN_INFO override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() + // Prevent the editor window from showing in the display cutout + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && getEditorWindowInfo() == EDITOR_MAIN_INFO) { + window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER + } + // 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)) @@ -213,10 +221,24 @@ open class GodotEditor : GodotActivity() { } protected fun getNewGodotInstanceIntent(editorWindowInfo: EditorWindowInfo, args: Array<String>): Intent { + val updatedArgs = if (editorWindowInfo == EDITOR_MAIN_INFO && + godot?.isInImmersiveMode() == true && + !args.contains(FULLSCREEN_ARG) && + !args.contains(FULLSCREEN_ARG_SHORT) + ) { + // If we're launching an editor window (project manager or editor) and we're in + // fullscreen mode, we want to remain in fullscreen mode. + // This doesn't apply to the play / game window since for that window fullscreen is + // controlled by the game logic. + args + FULLSCREEN_ARG + } else { + args + } + val newInstance = Intent() .setComponent(ComponentName(this, editorWindowInfo.windowClassName)) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(EXTRA_COMMAND_LINE_PARAMS, args) + .putExtra(EXTRA_COMMAND_LINE_PARAMS, updatedArgs) val launchPolicy = resolveLaunchPolicyIfNeeded(editorWindowInfo.launchPolicy) val isPiPAvailable = if (editorWindowInfo.supportsPiPMode && hasPiPSystemFeature()) { @@ -235,7 +257,7 @@ open class GodotEditor : GodotActivity() { } } else if (launchPolicy == LaunchPolicy.SAME) { if (isPiPAvailable && - (args.contains(BREAKPOINTS_ARG) || args.contains(BREAKPOINTS_ARG_SHORT))) { + (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) } 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 33fcbf9030..6b4bf255f2 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 @@ -128,7 +128,7 @@ class GodotGame : GodotEditor() { override fun getGodotAppLayout() = R.layout.godot_game_layout - override fun getEditorId() = RUN_GAME_INFO.windowId + override fun getEditorWindowInfo() = RUN_GAME_INFO override fun overrideOrientationRequest() = false diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 49e8ffb008..38bd336e2d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -42,13 +42,16 @@ import android.hardware.Sensor import android.hardware.SensorManager import android.os.* import android.util.Log +import android.util.TypedValue import android.view.* import android.widget.FrameLayout import androidx.annotation.Keep import androidx.annotation.StringRes import androidx.core.view.ViewCompat +import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsAnimationCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import com.google.android.vending.expansion.downloader.* import org.godotengine.godot.error.Error import org.godotengine.godot.input.GodotEditText @@ -105,36 +108,26 @@ class Godot(private val context: Context) { GodotPluginRegistry.getPluginRegistry() } - private val accelerometer_enabled = AtomicBoolean(false) + private val accelerometerEnabled = AtomicBoolean(false) private val mAccelerometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) } - private val gravity_enabled = AtomicBoolean(false) + private val gravityEnabled = AtomicBoolean(false) private val mGravity: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) } - private val magnetometer_enabled = AtomicBoolean(false) + private val magnetometerEnabled = AtomicBoolean(false) private val mMagnetometer: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) } - private val gyroscope_enabled = AtomicBoolean(false) + private val gyroscopeEnabled = AtomicBoolean(false) private val mGyroscope: Sensor? by lazy { mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE) } - private val uiChangeListener = View.OnSystemUiVisibilityChangeListener { visibility: Int -> - if (visibility and View.SYSTEM_UI_FLAG_FULLSCREEN == 0) { - val decorView = requireActivity().window.decorView - decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - }} - val tts = GodotTTS(context) val directoryAccessHandler = DirectoryAccessHandler(context) val fileAccessHandler = FileAccessHandler(context) @@ -185,7 +178,7 @@ class Godot(private val context: Context) { private var xrMode = XRMode.REGULAR private var expansionPackPath: String = "" private var useApkExpansion = false - private var useImmersive = false + private val useImmersive = AtomicBoolean(false) private var useDebugOpengl = false private var darkMode = false @@ -254,15 +247,9 @@ class Godot(private val context: Context) { xrMode = XRMode.OPENXR } else if (commandLine[i] == "--debug_opengl") { useDebugOpengl = true - } else if (commandLine[i] == "--use_immersive") { - useImmersive = true - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - registerUiChangeListener() + } else if (commandLine[i] == "--fullscreen") { + useImmersive.set(true) + newArgs.add(commandLine[i]) } else if (commandLine[i] == "--use_apk_expansion") { useApkExpansion = true } else if (hasExtra && commandLine[i] == "--apk_expansion_md5") { @@ -336,6 +323,54 @@ class Godot(private val context: Context) { } /** + * Toggle immersive mode. + * Must be called from the UI thread. + */ + private fun enableImmersiveMode(enabled: Boolean, override: Boolean = false) { + val activity = getActivity() ?: return + val window = activity.window ?: return + + if (!useImmersive.compareAndSet(!enabled, enabled) && !override) { + return + } + + WindowCompat.setDecorFitsSystemWindows(window, !enabled) + val controller = WindowInsetsControllerCompat(window, window.decorView) + if (enabled) { + controller.hide(WindowInsetsCompat.Type.systemBars()) + controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } else { + val fullScreenThemeValue = TypedValue() + val hasStatusBar = if (activity.theme.resolveAttribute(android.R.attr.windowFullscreen, fullScreenThemeValue, true) && fullScreenThemeValue.type == TypedValue.TYPE_INT_BOOLEAN) { + fullScreenThemeValue.data == 0 + } else { + // Fallback to checking the editor build + !isEditorBuild() + } + + val types = if (hasStatusBar) { + WindowInsetsCompat.Type.navigationBars() or WindowInsetsCompat.Type.statusBars() + } else { + WindowInsetsCompat.Type.navigationBars() + } + controller.show(types) + } + } + + /** + * Invoked from the render thread to toggle the immersive mode. + */ + @Keep + private fun nativeEnableImmersiveMode(enabled: Boolean) { + runOnUiThread { + enableImmersiveMode(enabled) + } + } + + @Keep + fun isInImmersiveMode() = useImmersive.get() + + /** * Initializes the native layer of the Godot engine. * * This must be preceded by [onCreate] and followed by [onInitRenderView] to complete @@ -552,15 +587,7 @@ class Godot(private val context: Context) { renderView?.onActivityResumed() registerSensorsIfNeeded() - if (useImmersive) { - val window = requireActivity().window - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or // hide nav bar - View.SYSTEM_UI_FLAG_FULLSCREEN or // hide status bar - View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - } + enableImmersiveMode(useImmersive.get(), true) for (plugin in pluginRegistry.allPlugins) { plugin.onMainResume() } @@ -571,16 +598,16 @@ class Godot(private val context: Context) { return } - if (accelerometer_enabled.get() && mAccelerometer != null) { + if (accelerometerEnabled.get() && mAccelerometer != null) { mSensorManager.registerListener(godotInputHandler, mAccelerometer, SensorManager.SENSOR_DELAY_GAME) } - if (gravity_enabled.get() && mGravity != null) { + if (gravityEnabled.get() && mGravity != null) { mSensorManager.registerListener(godotInputHandler, mGravity, SensorManager.SENSOR_DELAY_GAME) } - if (magnetometer_enabled.get() && mMagnetometer != null) { + if (magnetometerEnabled.get() && mMagnetometer != null) { mSensorManager.registerListener(godotInputHandler, mMagnetometer, SensorManager.SENSOR_DELAY_GAME) } - if (gyroscope_enabled.get() && mGyroscope != null) { + if (gyroscopeEnabled.get() && mGyroscope != null) { mSensorManager.registerListener(godotInputHandler, mGyroscope, SensorManager.SENSOR_DELAY_GAME) } } @@ -696,10 +723,10 @@ class Godot(private val context: Context) { Log.v(TAG, "OnGodotMainLoopStarted") godotMainLoopStarted.set(true) - accelerometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer"))) - gravity_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity"))) - gyroscope_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope"))) - magnetometer_enabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer"))) + accelerometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_accelerometer"))) + gravityEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gravity"))) + gyroscopeEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_gyroscope"))) + magnetometerEnabled.set(java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/sensors/enable_magnetometer"))) runOnUiThread { registerSensorsIfNeeded() @@ -724,11 +751,6 @@ class Godot(private val context: Context) { primaryHost?.onGodotRestartRequested(this) } - private fun registerUiChangeListener() { - val decorView = requireActivity().window.decorView - decorView.setOnSystemUiVisibilityChangeListener(uiChangeListener) - } - fun alert( @StringRes messageResId: Int, @StringRes titleResId: Int, 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/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index f1759af54a..d3b30e4589 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -86,6 +86,8 @@ GodotJavaWrapper::GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_ _has_feature = p_env->GetMethodID(godot_class, "hasFeature", "(Ljava/lang/String;)Z"); _sign_apk = p_env->GetMethodID(godot_class, "nativeSignApk", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"); _verify_apk = p_env->GetMethodID(godot_class, "nativeVerifyApk", "(Ljava/lang/String;)I"); + _enable_immersive_mode = p_env->GetMethodID(godot_class, "nativeEnableImmersiveMode", "(Z)V"); + _is_in_immersive_mode = p_env->GetMethodID(godot_class, "isInImmersiveMode", "()Z"); } GodotJavaWrapper::~GodotJavaWrapper() { @@ -465,3 +467,21 @@ Error GodotJavaWrapper::verify_apk(const String &p_apk_path) { return ERR_UNCONFIGURED; } } + +void GodotJavaWrapper::enable_immersive_mode(bool p_enabled) { + if (_enable_immersive_mode) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL(env); + env->CallVoidMethod(godot_instance, _enable_immersive_mode, p_enabled); + } +} + +bool GodotJavaWrapper::is_in_immersive_mode() { + if (_is_in_immersive_mode) { + JNIEnv *env = get_jni_env(); + ERR_FAIL_NULL_V(env, false); + return env->CallBooleanMethod(godot_instance, _is_in_immersive_mode); + } else { + return false; + } +} diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 6b66565981..51d7f98541 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -77,6 +77,8 @@ private: jmethodID _has_feature = nullptr; jmethodID _sign_apk = nullptr; jmethodID _verify_apk = nullptr; + jmethodID _enable_immersive_mode = nullptr; + jmethodID _is_in_immersive_mode = nullptr; public: GodotJavaWrapper(JNIEnv *p_env, jobject p_activity, jobject p_godot_instance); @@ -122,6 +124,9 @@ public: // Sign and verify apks Error sign_apk(const String &p_input_path, const String &p_output_path, const String &p_keystore_path, const String &p_keystore_user, const String &p_keystore_password); Error verify_apk(const String &p_apk_path); + + void enable_immersive_mode(bool p_enabled); + bool is_in_immersive_mode(); }; #endif // JAVA_GODOT_WRAPPER_H diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index e4b5392c4e..b99e825540 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -2017,11 +2017,11 @@ Error EditorExportPlatformIOS::_export_ios_plugins(const Ref<EditorExportPreset> return OK; } -Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { return _export_project_helper(p_preset, p_debug, p_path, p_flags, false, false); } -Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick) { +Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String dest_dir = p_path.get_base_dir() + "/"; @@ -2983,7 +2983,7 @@ void EditorExportPlatformIOS::_update_preset_status() { } #endif -Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { #ifdef MACOS_ENABLED ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); @@ -3029,11 +3029,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int String host = EDITOR_GET("network/debug/remote_host"); int remote_port = (int)EDITOR_GET("network/debug/remote_port"); - if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST) { + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST)) { host = "localhost"; } - if (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT) { + if (p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT)) { int port = EDITOR_GET("filesystem/file_server/port"); String passwd = EDITOR_GET("filesystem/file_server/password"); cmd_args_list.push_back("--remote-fs"); @@ -3044,7 +3044,7 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int } } - if (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) { + if (p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG)) { cmd_args_list.push_back("--remote-debug"); cmd_args_list.push_back(get_debug_protocol() + host + ":" + String::num(remote_port)); @@ -3066,11 +3066,11 @@ Error EditorExportPlatformIOS::run(const Ref<EditorExportPreset> &p_preset, int } } - if (p_debug_flags & DEBUG_FLAG_VIEW_COLLISIONS) { + if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_COLLISIONS)) { cmd_args_list.push_back("--debug-collisions"); } - if (p_debug_flags & DEBUG_FLAG_VIEW_NAVIGATION) { + if (p_debug_flags.has_flag(DEBUG_FLAG_VIEW_NAVIGATION)) { cmd_args_list.push_back("--debug-navigation"); } diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h index 1964906c27..db7c0553dd 100644 --- a/platform/ios/export/export_plugin.h +++ b/platform/ios/export/export_plugin.h @@ -146,7 +146,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform { Error _export_additional_assets(const Ref<EditorExportPreset> &p_preset, const String &p_out_dir, const Vector<SharedObject> &p_libraries, Vector<IOSExportAsset> &r_exported_assets); Error _export_ios_plugins(const Ref<EditorExportPreset> &p_preset, IOSConfigData &p_config_data, const String &dest_dir, Vector<IOSExportAsset> &r_exported_assets, bool p_debug); - Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags, bool p_simulator, bool p_oneclick); + Error _export_project_helper(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags, bool p_simulator, bool p_oneclick); bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const; @@ -169,7 +169,7 @@ public: virtual Ref<ImageTexture> get_option_icon(int p_index) const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual bool poll_export() override { bool dc = devices_changed.is_set(); @@ -202,7 +202,7 @@ public: return list; } - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; diff --git a/platform/linuxbsd/export/export_plugin.cpp b/platform/linuxbsd/export/export_plugin.cpp index 0032b898d2..69ba742f72 100644 --- a/platform/linuxbsd/export/export_plugin.cpp +++ b/platform/linuxbsd/export/export_plugin.cpp @@ -60,7 +60,7 @@ Error EditorExportPlatformLinuxBSD::_export_debug_script(const Ref<EditorExportP return OK; } -Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformLinuxBSD::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { String custom_debug = p_preset->get("custom_template/debug"); String custom_release = p_preset->get("custom_template/release"); String arch = p_preset->get("binary_format/architecture"); @@ -458,7 +458,7 @@ void EditorExportPlatformLinuxBSD::cleanup() { cleanup_commands.clear(); } -Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { cleanup(); if (p_device) { // Stop command, cleanup only. return OK; @@ -512,8 +512,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, String cmd_args; { - Vector<String> cmd_args_list; - gen_debug_flags(cmd_args_list, p_debug_flags); + Vector<String> cmd_args_list = gen_export_flags(p_debug_flags); for (int i = 0; i < cmd_args_list.size(); i++) { if (i != 0) { cmd_args += " "; @@ -522,7 +521,7 @@ Error EditorExportPlatformLinuxBSD::run(const Ref<EditorExportPreset> &p_preset, } } - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); print_line("Creating temporary directory..."); diff --git a/platform/linuxbsd/export/export_plugin.h b/platform/linuxbsd/export/export_plugin.h index bbc55b82ce..1d9ef01d1a 100644 --- a/platform/linuxbsd/export/export_plugin.h +++ b/platform/linuxbsd/export/export_plugin.h @@ -76,7 +76,7 @@ public: virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; virtual bool get_export_option_visibility(const EditorExportPreset *p_preset, const String &p_option) const override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual String get_template_file_name(const String &p_target, const String &p_arch) const override; virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override; virtual bool is_executable(const String &p_path) const override; @@ -87,7 +87,7 @@ public: virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual void cleanup() override; EditorExportPlatformLinuxBSD(); diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index 2b98fda0d5..94a748e414 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -210,7 +210,15 @@ void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, append_dbus_string(&struct_iter, p_filter_names[i]); dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter); - const String &flt = p_filter_exts[i]; + const String &flt_orig = p_filter_exts[i]; + String flt; + for (int j = 0; j < flt_orig.length(); j++) { + if (is_unicode_letter(flt_orig[j])) { + flt += vformat("[%c%c]", String::char_lowercase(flt_orig[j]), String::char_uppercase(flt_orig[j])); + } else { + flt += flt_orig[j]; + } + } int filter_slice_count = flt.get_slice_count(","); for (int j = 0; j < filter_slice_count; j++) { dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter); diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 290b0082fc..8372600ae9 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -1505,7 +1505,7 @@ Error EditorExportPlatformMacOS::_export_debug_script(const Ref<EditorExportPres return OK; } -Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformMacOS::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String base_dir = p_path.get_base_dir(); @@ -2511,7 +2511,7 @@ void EditorExportPlatformMacOS::cleanup() { cleanup_commands.clear(); } -Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { cleanup(); if (p_device) { // Stop command, cleanup only. return OK; @@ -2573,8 +2573,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in String cmd_args; { - Vector<String> cmd_args_list; - gen_debug_flags(cmd_args_list, p_debug_flags); + Vector<String> cmd_args_list = gen_export_flags(p_debug_flags); for (int i = 0; i < cmd_args_list.size(); i++) { if (i != 0) { cmd_args += " "; @@ -2583,7 +2582,7 @@ Error EditorExportPlatformMacOS::run(const Ref<EditorExportPreset> &p_preset, in } } - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); print_line("Creating temporary directory..."); diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h index 062a2e5f95..5457c687d3 100644 --- a/platform/macos/export/export_plugin.h +++ b/platform/macos/export/export_plugin.h @@ -147,7 +147,7 @@ public: virtual bool is_executable(const String &p_path) const override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; @@ -167,7 +167,7 @@ public: virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual void cleanup() override; EditorExportPlatformMacOS(); diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index d8c1b6033d..5faab74d7b 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -130,15 +130,14 @@ void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_ } } -void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) { +void EditorExportPlatformWeb::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) { // Engine.js config Dictionary config; Array libs; for (int i = 0; i < p_shared_objects.size(); i++) { libs.push_back(p_shared_objects[i].path.get_file()); } - Vector<String> flags; - gen_export_flags(flags, p_flags & (~DEBUG_FLAG_DUMB_CLIENT)); + Vector<String> flags = gen_export_flags(p_flags & (~DEBUG_FLAG_DUMB_CLIENT)); Array args; for (int i = 0; i < flags.size(); i++) { args.push_back(flags[i]); @@ -450,7 +449,7 @@ List<String> EditorExportPlatformWeb::get_binary_extensions(const Ref<EditorExpo return list; } -Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformWeb::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String custom_debug = p_preset->get("custom_template/debug"); @@ -744,7 +743,7 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const { return ""; } -Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) { +Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { const uint16_t bind_port = EDITOR_GET("export/web/http_port"); // Resolve host if needed. const String bind_host = EDITOR_GET("export/web/http_host"); diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index 2f67d8107f..3c743e2e74 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -98,7 +98,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform { Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa); void _replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template); - void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); + void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, BitField<EditorExportPlatform::DebugFlags> p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr); Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects); Error _write_or_error(const uint8_t *p_content, int p_len, String p_path); @@ -120,14 +120,14 @@ public: virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; virtual bool poll_export() override; virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; virtual Ref<ImageTexture> get_option_icon(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_option, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual Ref<Texture2D> get_run_icon() const override; virtual void get_platform_features(List<String> *r_features) const override { diff --git a/platform/windows/crash_handler_windows_seh.cpp b/platform/windows/crash_handler_windows_seh.cpp index 2abe285d31..a6015092e8 100644 --- a/platform/windows/crash_handler_windows_seh.cpp +++ b/platform/windows/crash_handler_windows_seh.cpp @@ -118,7 +118,7 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) { HANDLE process = GetCurrentProcess(); HANDLE hThread = GetCurrentThread(); DWORD offset_from_symbol = 0; - IMAGEHLP_LINE64 line = { 0 }; + IMAGEHLP_LINE64 line = {}; std::vector<module_data> modules; DWORD cbNeeded; std::vector<HMODULE> module_handles(1); diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 82a98655c0..92ac921cee 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -381,6 +381,15 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): ## Compile/link flags + if env["use_llvm"]: + env["CC"] = "clang-cl" + env["CXX"] = "clang-cl" + env["LINK"] = "lld-link" + env["AR"] = "llvm-lib" + + env.AppendUnique(CPPDEFINES=["R128_STDC_ONLY"]) + env.extra_suffix = ".llvm" + env.extra_suffix + env["MAXLINELENGTH"] = 8192 # Windows Vista and beyond, so always applicable. if env["silence_msvc"] and not env.GetOption("clean"): @@ -465,7 +474,6 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): env.AppendUnique(CCFLAGS=["/Gd", "/GR", "/nologo"]) env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding. - env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++ # Once it was thought that only debug builds would be too large, # but this has recently stopped being true. See the mingw function # for notes on why this shouldn't be enabled for gcc @@ -590,6 +598,9 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): if env["target"] in ["editor", "template_debug"]: LIBS += ["psapi", "dbghelp"] + if env["use_llvm"]: + LIBS += [f"clang_rt.builtins-{env['arch']}"] + env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS]) if vcvars_msvc_config: @@ -605,14 +616,22 @@ def configure_msvc(env: "SConsEnvironment", vcvars_msvc_config): if env["lto"] != "none": if env["lto"] == "thin": - print_error("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") - sys.exit(255) - env.AppendUnique(CCFLAGS=["/GL"]) - env.AppendUnique(ARFLAGS=["/LTCG"]) - if env["progress"]: - env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"]) + if not env["use_llvm"]: + print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") + sys.exit(255) + + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + elif env["use_llvm"]: + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto"]) else: - env.AppendUnique(LINKFLAGS=["/LTCG"]) + env.AppendUnique(CCFLAGS=["/GL"]) + env.AppendUnique(ARFLAGS=["/LTCG"]) + if env["progress"]: + env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"]) + else: + env.AppendUnique(LINKFLAGS=["/LTCG"]) if vcvars_msvc_config: env.Prepend(CPPPATH=[p for p in str(os.getenv("INCLUDE")).split(";")]) @@ -630,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 @@ -762,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/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp index b465bd4ecd..8d3f4bb269 100644 --- a/platform/windows/export/export_plugin.cpp +++ b/platform/windows/export/export_plugin.cpp @@ -167,7 +167,7 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref<EditorExportPres } } -Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { if (p_preset->get("application/modify_resources")) { _rcedit_add_data(p_preset, p_path, false); String wrapper_path = p_path.get_basename() + ".console.exe"; @@ -178,7 +178,7 @@ Error EditorExportPlatformWindows::modify_template(const Ref<EditorExportPreset> return OK; } -Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { +Error EditorExportPlatformWindows::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) { String custom_debug = p_preset->get("custom_template/debug"); String custom_release = p_preset->get("custom_template/release"); String arch = p_preset->get("binary_format/architecture"); @@ -996,7 +996,7 @@ void EditorExportPlatformWindows::cleanup() { cleanup_commands.clear(); } -Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { +Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) { cleanup(); if (p_device) { // Stop command, cleanup only. return OK; @@ -1050,8 +1050,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, String cmd_args; { - Vector<String> cmd_args_list; - gen_debug_flags(cmd_args_list, p_debug_flags); + Vector<String> cmd_args_list = gen_export_flags(p_debug_flags); for (int i = 0; i < cmd_args_list.size(); i++) { if (i != 0) { cmd_args += " "; @@ -1060,7 +1059,7 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset, } } - const bool use_remote = (p_debug_flags & DEBUG_FLAG_REMOTE_DEBUG) || (p_debug_flags & DEBUG_FLAG_DUMB_CLIENT); + const bool use_remote = p_debug_flags.has_flag(DEBUG_FLAG_REMOTE_DEBUG) || p_debug_flags.has_flag(DEBUG_FLAG_DUMB_CLIENT); int dbg_port = EditorSettings::get_singleton()->get("network/debug/remote_port"); print_line("Creating temporary directory..."); diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h index 6ccb4a15a7..e86aac83d4 100644 --- a/platform/windows/export/export_plugin.h +++ b/platform/windows/export/export_plugin.h @@ -76,8 +76,8 @@ class EditorExportPlatformWindows : public EditorExportPlatformPC { String _get_exe_arch(const String &p_path) const; public: - virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override; - virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) override; + virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override; + virtual Error modify_template(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) override; virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override; virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; virtual void get_export_options(List<ExportOption> *r_options) const override; @@ -95,7 +95,7 @@ public: virtual int get_options_count() const override; virtual String get_option_label(int p_index) const override; virtual String get_option_tooltip(int p_index) const override; - virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) override; + virtual Error run(const Ref<EditorExportPreset> &p_preset, int p_device, BitField<EditorExportPlatform::DebugFlags> p_debug_flags) override; virtual void cleanup() override; EditorExportPlatformWindows(); 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/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp index 89a0479de3..7c60e47e64 100644 --- a/scene/2d/audio_stream_player_2d.cpp +++ b/scene/2d/audio_stream_player_2d.cpp @@ -276,10 +276,6 @@ void AudioStreamPlayer2D::_set_playing(bool p_enable) { internal->set_playing(p_enable); } -bool AudioStreamPlayer2D::_is_active() const { - return internal->is_active(); -} - void AudioStreamPlayer2D::_validate_property(PropertyInfo &p_property) const { internal->validate_property(p_property); } @@ -385,8 +381,7 @@ void AudioStreamPlayer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer2D::set_autoplay); ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer2D::is_autoplay_enabled); - ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer2D::_set_playing); - ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer2D::_is_active); + ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer2D::_set_playing); ClassDB::bind_method(D_METHOD("set_max_distance", "pixels"), &AudioStreamPlayer2D::set_max_distance); ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer2D::get_max_distance); @@ -415,7 +410,7 @@ void AudioStreamPlayer2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "1,4096,1,or_greater,exp,suffix:px"), "set_max_distance", "get_max_distance"); diff --git a/scene/2d/parallax_2d.cpp b/scene/2d/parallax_2d.cpp index 9dd9d4a376..fdb2d2cdd0 100644 --- a/scene/2d/parallax_2d.cpp +++ b/scene/2d/parallax_2d.cpp @@ -47,9 +47,18 @@ void Parallax2D::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { - autoscroll_offset += autoscroll * get_process_delta_time(); - autoscroll_offset = autoscroll_offset.posmodv(repeat_size); + Point2 offset = scroll_offset; + offset += autoscroll * get_process_delta_time(); + if (repeat_size.x) { + offset.x = Math::fposmod(offset.x, repeat_size.x); + } + + if (repeat_size.y) { + offset.y = Math::fposmod(offset.y, repeat_size.y); + } + + scroll_offset = offset; _update_scroll(); } break; @@ -106,14 +115,14 @@ void Parallax2D::_update_scroll() { scroll_ofs *= scroll_scale; if (repeat_size.x) { - real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x - autoscroll_offset.x, repeat_size.x * get_scale().x); + real_t mod = Math::fposmod(scroll_ofs.x - scroll_offset.x, repeat_size.x * get_scale().x); scroll_ofs.x = screen_offset.x - mod; } else { scroll_ofs.x = screen_offset.x + scroll_offset.x - scroll_ofs.x; } if (repeat_size.y) { - real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y - autoscroll_offset.y, repeat_size.y * get_scale().y); + real_t mod = Math::fposmod(scroll_ofs.y - scroll_offset.y, repeat_size.y * get_scale().y); scroll_ofs.y = screen_offset.y - mod; } else { scroll_ofs.y = screen_offset.y + scroll_offset.y - scroll_ofs.y; @@ -193,7 +202,6 @@ void Parallax2D::set_autoscroll(const Point2 &p_autoscroll) { } autoscroll = p_autoscroll; - autoscroll_offset = Point2(); _update_process(); _update_scroll(); diff --git a/scene/2d/parallax_2d.h b/scene/2d/parallax_2d.h index 5fbc3a20c8..f15e3fa9ff 100644 --- a/scene/2d/parallax_2d.h +++ b/scene/2d/parallax_2d.h @@ -47,7 +47,6 @@ class Parallax2D : public Node2D { Point2 limit_begin = Point2(-DEFAULT_LIMIT, -DEFAULT_LIMIT); Point2 limit_end = Point2(DEFAULT_LIMIT, DEFAULT_LIMIT); Point2 autoscroll; - Point2 autoscroll_offset; bool follow_viewport = true; bool ignore_camera_scroll = false; diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index b0ad7ec110..ba0958e74b 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -957,7 +957,7 @@ void TileMapLayer::_navigation_update(bool p_force_cleanup) { // Create a dedicated map for each layer. RID new_layer_map = ns->map_create(); // Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error. - ns->map_set_cell_size(new_layer_map, 1.0); + ns->map_set_cell_size(new_layer_map, NavigationDefaults2D::navmesh_cell_size); ns->map_set_active(new_layer_map, true); navigation_map_override = new_layer_map; } diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp index 4d3f494ccf..591528b915 100644 --- a/scene/3d/audio_stream_player_3d.cpp +++ b/scene/3d/audio_stream_player_3d.cpp @@ -596,10 +596,6 @@ void AudioStreamPlayer3D::_set_playing(bool p_enable) { internal->set_playing(p_enable); } -bool AudioStreamPlayer3D::_is_active() const { - return internal->is_active(); -} - void AudioStreamPlayer3D::_validate_property(PropertyInfo &p_property) const { internal->validate_property(p_property); } @@ -779,8 +775,7 @@ void AudioStreamPlayer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_autoplay", "enable"), &AudioStreamPlayer3D::set_autoplay); ClassDB::bind_method(D_METHOD("is_autoplay_enabled"), &AudioStreamPlayer3D::is_autoplay_enabled); - ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer3D::_set_playing); - ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer3D::_is_active); + ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer3D::_set_playing); ClassDB::bind_method(D_METHOD("set_max_distance", "meters"), &AudioStreamPlayer3D::set_max_distance); ClassDB::bind_method(D_METHOD("get_max_distance"), &AudioStreamPlayer3D::get_max_distance); @@ -830,7 +825,7 @@ void AudioStreamPlayer3D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "unit_size", PROPERTY_HINT_RANGE, "0.1,100,0.01,or_greater"), "set_unit_size", "get_unit_size"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_db", PROPERTY_HINT_RANGE, "-24,6,suffix:dB"), "set_max_db", "get_max_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "max_distance", PROPERTY_HINT_RANGE, "0,4096,0.01,or_greater,suffix:m"), "set_max_distance", "get_max_distance"); diff --git a/scene/3d/physics/vehicle_body_3d.cpp b/scene/3d/physics/vehicle_body_3d.cpp index c23032d3b9..b4c321cf5f 100644 --- a/scene/3d/physics/vehicle_body_3d.cpp +++ b/scene/3d/physics/vehicle_body_3d.cpp @@ -219,6 +219,14 @@ bool VehicleWheel3D::is_in_contact() const { return m_raycastInfo.m_isInContact; } +Vector3 VehicleWheel3D::get_contact_point() const { + return m_raycastInfo.m_contactPointWS; +} + +Vector3 VehicleWheel3D::get_contact_normal() const { + return m_raycastInfo.m_contactNormalWS; +} + Node3D *VehicleWheel3D::get_contact_body() const { return m_raycastInfo.m_groundObject; } @@ -256,6 +264,8 @@ void VehicleWheel3D::_bind_methods() { ClassDB::bind_method(D_METHOD("is_in_contact"), &VehicleWheel3D::is_in_contact); ClassDB::bind_method(D_METHOD("get_contact_body"), &VehicleWheel3D::get_contact_body); + ClassDB::bind_method(D_METHOD("get_contact_point"), &VehicleWheel3D::get_contact_point); + ClassDB::bind_method(D_METHOD("get_contact_normal"), &VehicleWheel3D::get_contact_normal); ClassDB::bind_method(D_METHOD("set_roll_influence", "roll_influence"), &VehicleWheel3D::set_roll_influence); ClassDB::bind_method(D_METHOD("get_roll_influence"), &VehicleWheel3D::get_roll_influence); diff --git a/scene/3d/physics/vehicle_body_3d.h b/scene/3d/physics/vehicle_body_3d.h index def9984440..24f120ed26 100644 --- a/scene/3d/physics/vehicle_body_3d.h +++ b/scene/3d/physics/vehicle_body_3d.h @@ -130,6 +130,10 @@ public: bool is_in_contact() const; + Vector3 get_contact_point() const; + + Vector3 get_contact_normal() const; + Node3D *get_contact_body() const; void set_roll_influence(real_t p_value); diff --git a/scene/3d/voxelizer.h b/scene/3d/voxelizer.h index 6ea1cfdbb0..08d018eee9 100644 --- a/scene/3d/voxelizer.h +++ b/scene/3d/voxelizer.h @@ -35,9 +35,8 @@ class Voxelizer { private: - enum { + enum : uint32_t { CHILD_EMPTY = 0xFFFFFFFF - }; struct Cell { diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index 20dd12f8c3..0ab4dbe470 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -947,14 +947,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 +966,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 +1083,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 +1122,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 +1157,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 +1175,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 +1209,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 +1224,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 +1235,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 +1263,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 +1297,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 +1311,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 +1322,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 +1350,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 +1384,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 +1399,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 +1410,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 +1574,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 +1693,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 +1731,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..867bbda4b3 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); 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/animation/tween.cpp b/scene/animation/tween.cpp index f8bbd704f4..e1fd8abede 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -579,6 +579,7 @@ bool PropertyTweener::step(double &r_delta) { Object *target_instance = ObjectDB::get_instance(target); if (!target_instance) { + _finish(); return false; } elapsed_time += r_delta; @@ -706,6 +707,7 @@ bool CallbackTweener::step(double &r_delta) { } if (!callback.is_valid()) { + _finish(); return false; } @@ -770,6 +772,7 @@ bool MethodTweener::step(double &r_delta) { } if (!callback.is_valid()) { + _finish(); return false; } diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp index 183c4af950..d4b44a8b69 100644 --- a/scene/audio/audio_stream_player.cpp +++ b/scene/audio/audio_stream_player.cpp @@ -154,10 +154,6 @@ void AudioStreamPlayer::_set_playing(bool p_enable) { internal->set_playing(p_enable); } -bool AudioStreamPlayer::_is_active() const { - return internal->is_active(); -} - void AudioStreamPlayer::set_stream_paused(bool p_pause) { internal->set_stream_paused(p_pause); } @@ -249,8 +245,7 @@ void AudioStreamPlayer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mix_target", "mix_target"), &AudioStreamPlayer::set_mix_target); ClassDB::bind_method(D_METHOD("get_mix_target"), &AudioStreamPlayer::get_mix_target); - ClassDB::bind_method(D_METHOD("_set_playing", "enable"), &AudioStreamPlayer::_set_playing); - ClassDB::bind_method(D_METHOD("_is_active"), &AudioStreamPlayer::_is_active); + ClassDB::bind_method(D_METHOD("set_playing", "enable"), &AudioStreamPlayer::_set_playing); ClassDB::bind_method(D_METHOD("set_stream_paused", "pause"), &AudioStreamPlayer::set_stream_paused); ClassDB::bind_method(D_METHOD("get_stream_paused"), &AudioStreamPlayer::get_stream_paused); @@ -267,7 +262,7 @@ void AudioStreamPlayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "volume_db", PROPERTY_HINT_RANGE, "-80,24,suffix:dB"), "set_volume_db", "get_volume_db"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pitch_scale", PROPERTY_HINT_RANGE, "0.01,4,0.01,or_greater"), "set_pitch_scale", "get_pitch_scale"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "_set_playing", "is_playing"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playing", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR), "set_playing", "is_playing"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoplay"), "set_autoplay", "is_autoplay_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stream_paused", PROPERTY_HINT_NONE, ""), "set_stream_paused", "get_stream_paused"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mix_target", PROPERTY_HINT_ENUM, "Stereo,Surround,Center"), "set_mix_target", "get_mix_target"); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 9bb6130742..0395dffad9 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -817,6 +817,8 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o } int line_count = 0; + // Bottom margin for text clipping. + float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM); Size2 ctrl_size = get_size(); // Draw text. for (int line = 0; line < l.text_buf->get_line_count(); line++) { @@ -824,7 +826,7 @@ int RichTextLabel::_draw_line(ItemFrame *p_frame, int p_line, const Vector2 &p_o off.y += theme_cache.line_separation; } - if (p_ofs.y + off.y >= ctrl_size.height) { + if (p_ofs.y + off.y >= ctrl_size.height - v_limit) { break; } @@ -1890,10 +1892,12 @@ void RichTextLabel::_notification(int p_what) { visible_paragraph_count = 0; visible_line_count = 0; + // Bottom margin for text clipping. + float v_limit = theme_cache.normal_style->get_margin(SIDE_BOTTOM); // New cache draw. Point2 ofs = text_rect.get_position() + Vector2(0, main->lines[from_line].offset.y - vofs); int processed_glyphs = 0; - while (ofs.y < size.height && from_line < to_line) { + while (ofs.y < size.height - v_limit && from_line < to_line) { MutexLock lock(main->lines[from_line].text_buf->get_mutex()); visible_paragraph_count++; diff --git a/scene/resources/2d/navigation_polygon.h b/scene/resources/2d/navigation_polygon.h index 86bda47ace..ed2c606c55 100644 --- a/scene/resources/2d/navigation_polygon.h +++ b/scene/resources/2d/navigation_polygon.h @@ -33,6 +33,7 @@ #include "scene/2d/node_2d.h" #include "scene/resources/navigation_mesh.h" +#include "servers/navigation/navigation_globals.h" class NavigationPolygon : public Resource { GDCLASS(NavigationPolygon, Resource); @@ -50,7 +51,7 @@ class NavigationPolygon : public Resource { // Navigation mesh Ref<NavigationMesh> navigation_mesh; - real_t cell_size = 1.0f; // Must match ProjectSettings default 2D cell_size. + real_t cell_size = NavigationDefaults2D::navmesh_cell_size; real_t border_size = 0.0f; Rect2 baking_rect; diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp index 5e4f1970ee..92246b50db 100644 --- a/scene/resources/3d/fog_material.cpp +++ b/scene/resources/3d/fog_material.cpp @@ -138,7 +138,7 @@ void FogMaterial::cleanup_shader() { } void FogMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader.is_null()) { shader = RS::get_singleton()->shader_create(); @@ -165,7 +165,6 @@ void fog() { } )"); } - shader_mutex.unlock(); } FogMaterial::FogMaterial() { diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp index 640261d615..c470db5d7f 100644 --- a/scene/resources/3d/sky_material.cpp +++ b/scene/resources/3d/sky_material.cpp @@ -269,7 +269,7 @@ void ProceduralSkyMaterial::cleanup_shader() { } void ProceduralSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -354,7 +354,6 @@ void sky() { i ? "render_mode use_debanding;" : "")); } } - shader_mutex.unlock(); } ProceduralSkyMaterial::ProceduralSkyMaterial() { @@ -463,7 +462,7 @@ void PanoramaSkyMaterial::cleanup_shader() { } void PanoramaSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -484,8 +483,6 @@ void sky() { i ? "filter_linear" : "filter_nearest")); } } - - shader_mutex.unlock(); } PanoramaSkyMaterial::PanoramaSkyMaterial() { @@ -692,7 +689,7 @@ void PhysicalSkyMaterial::cleanup_shader() { } void PhysicalSkyMaterial::_update_shader() { - shader_mutex.lock(); + MutexLock shader_lock(shader_mutex); if (shader_cache[0].is_null()) { for (int i = 0; i < 2; i++) { shader_cache[i] = RS::get_singleton()->shader_create(); @@ -785,8 +782,6 @@ void sky() { i ? "render_mode use_debanding;" : "")); } } - - shader_mutex.unlock(); } PhysicalSkyMaterial::PhysicalSkyMaterial() { 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 de6a069567..08ebacc2b3 100644 --- a/scene/resources/audio_stream_wav.cpp +++ b/scene/resources/audio_stream_wav.cpp @@ -179,17 +179,17 @@ void AudioStreamPlaybackWAV::do_resample(const Depth *p_src, AudioFrame *p_dst, if (pos != p_qoa->cache_pos) { // Prevents triple decoding on lower mix rates. for (int i = 0; i < 2; i++) { // Sign operations prevent triple decoding on backward loops, maxing prevents pop. - uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc->samples - 1); + uint32_t interp_pos = MIN(pos + (i * sign) + (sign < 0), p_qoa->desc.samples - 1); uint32_t new_data_ofs = 8 + interp_pos / QOA_FRAME_LEN * p_qoa->frame_len; 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); + qoa_decode_frame(src_ptr, p_qoa->frame_len, &p_qoa->desc, p_qoa->dec, &p_qoa->dec_len); } - uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc->channels; + uint32_t dec_idx = (interp_pos % QOA_FRAME_LEN) * p_qoa->desc.channels; if ((sign > 0 && i == 0) || (sign < 0 && i == 1)) { final = p_qoa->dec[dec_idx]; @@ -286,7 +286,7 @@ int AudioStreamPlaybackWAV::mix(AudioFrame *p_buffer, float p_rate_scale, int p_ len *= 2; break; case AudioStreamWAV::FORMAT_QOA: - len = qoa.desc->samples * qoa.desc->channels; + len = qoa.desc.samples * qoa.desc.channels; break; } @@ -484,10 +484,6 @@ void AudioStreamPlaybackWAV::set_sample_playback(const Ref<AudioSamplePlayback> AudioStreamPlaybackWAV::AudioStreamPlaybackWAV() {} AudioStreamPlaybackWAV::~AudioStreamPlaybackWAV() { - if (qoa.desc) { - memfree(qoa.desc); - } - if (qoa.dec) { memfree(qoa.dec); } @@ -557,7 +553,7 @@ double AudioStreamWAV::get_length() const { len *= 2; break; case AudioStreamWAV::FORMAT_QOA: - qoa_desc desc = { 0, 0, 0, { { { 0 }, { 0 } } } }; + qoa_desc desc = {}; qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, &desc); len = desc.samples * desc.channels; break; @@ -697,12 +693,11 @@ Ref<AudioStreamPlayback> AudioStreamWAV::instantiate_playback() { sample->base = Ref<AudioStreamWAV>(this); if (format == AudioStreamWAV::FORMAT_QOA) { - sample->qoa.desc = (qoa_desc *)memalloc(sizeof(qoa_desc)); - uint32_t ffp = qoa_decode_header((uint8_t *)data + DATA_PAD, data_bytes, sample->qoa.desc); + uint32_t ffp = qoa_decode_header((uint8_t *)data + 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.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); } @@ -765,7 +760,7 @@ void AudioStreamWAV::_bind_methods() { ClassDB::bind_method(D_METHOD("save_to_wav", "path"), &AudioStreamWAV::save_to_wav); ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_data", "get_data"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM,QOA"), "set_format", "get_format"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_mode", PROPERTY_HINT_ENUM, "Disabled,Forward,Ping-Pong,Backward"), "set_loop_mode", "get_loop_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_begin"), "set_loop_begin", "get_loop_begin"); ADD_PROPERTY(PropertyInfo(Variant::INT, "loop_end"), "set_loop_end", "get_loop_end"); diff --git a/scene/resources/audio_stream_wav.h b/scene/resources/audio_stream_wav.h index 806db675b6..47aa10e790 100644 --- a/scene/resources/audio_stream_wav.h +++ b/scene/resources/audio_stream_wav.h @@ -59,7 +59,7 @@ class AudioStreamPlaybackWAV : public AudioStreamPlayback { } ima_adpcm[2]; struct QOA_State { - qoa_desc *desc = nullptr; + qoa_desc desc = {}; uint32_t data_ofs = 0; uint32_t frame_len = 0; int16_t *dec = nullptr; diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h index 741cea0791..1b3db5bac2 100644 --- a/scene/resources/navigation_mesh.h +++ b/scene/resources/navigation_mesh.h @@ -33,6 +33,7 @@ #include "core/os/rw_lock.h" #include "scene/resources/mesh.h" +#include "servers/navigation/navigation_globals.h" class NavigationMesh : public Resource { GDCLASS(NavigationMesh, Resource); @@ -77,8 +78,8 @@ public: }; protected: - float cell_size = 0.25f; // Must match ProjectSettings default 3D cell_size and NavigationServer NavMap cell_size. - float cell_height = 0.25f; // Must match ProjectSettings default 3D cell_height and NavigationServer NavMap cell_height. + float cell_size = NavigationDefaults3D::navmesh_cell_size; + float cell_height = NavigationDefaults3D::navmesh_cell_height; float border_size = 0.0f; float agent_height = 1.5f; float agent_radius = 0.5f; diff --git a/servers/audio/effects/audio_effect_record.cpp b/servers/audio/effects/audio_effect_record.cpp index e30a8fa99e..54af4738b1 100644 --- a/servers/audio/effects/audio_effect_record.cpp +++ b/servers/audio/effects/audio_effect_record.cpp @@ -283,7 +283,7 @@ void AudioEffectRecord::_bind_methods() { ClassDB::bind_method(D_METHOD("get_format"), &AudioEffectRecord::get_format); ClassDB::bind_method(D_METHOD("get_recording"), &AudioEffectRecord::get_recording); - ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA-ADPCM"), "set_format", "get_format"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "format", PROPERTY_HINT_ENUM, "8-Bit,16-Bit,IMA ADPCM,Quite OK Audio"), "set_format", "get_format"); } AudioEffectRecord::AudioEffectRecord() { diff --git a/servers/navigation/navigation_globals.h b/servers/navigation/navigation_globals.h new file mode 100644 index 0000000000..aa54f95519 --- /dev/null +++ b/servers/navigation/navigation_globals.h @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* navigation_globals.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 NAVIGATION_GLOBALS_H +#define NAVIGATION_GLOBALS_H + +namespace NavigationDefaults3D { + +// Rasterization. + +// To find the polygons edges the vertices are displaced in a grid where +// each cell has the following cell_size and cell_height. +constexpr float navmesh_cell_size{ 0.25f }; // Must match ProjectSettings default 3D cell_size and NavigationMesh cell_size. +constexpr float navmesh_cell_height{ 0.25f }; // Must match ProjectSettings default 3D cell_height and NavigationMesh cell_height. +constexpr auto navmesh_cell_size_hint{ "0.001,100,0.001,or_greater" }; + +// Map. + +constexpr float edge_connection_margin{ 0.25f }; +constexpr float link_connection_radius{ 1.0f }; + +} //namespace NavigationDefaults3D + +namespace NavigationDefaults2D { + +// Rasterization. + +// Same as in 3D but larger since 1px is treated as 1m. +constexpr float navmesh_cell_size{ 1.0f }; // Must match ProjectSettings default 2D cell_size. +constexpr auto navmesh_cell_size_hint{ "0.001,100,0.001,or_greater" }; + +// Map. + +constexpr float edge_connection_margin{ 1.0f }; +constexpr float link_connection_radius{ 4.0f }; + +} //namespace NavigationDefaults2D + +#endif // NAVIGATION_GLOBALS_H diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index 398ff1e1f3..f4ffcf5a3e 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "scene/main/node.h" +#include "servers/navigation/navigation_globals.h" NavigationServer3D *NavigationServer3D::singleton = nullptr; @@ -227,18 +228,18 @@ NavigationServer3D::NavigationServer3D() { ERR_FAIL_COND(singleton != nullptr); singleton = this; - GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/2d/default_cell_size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), 1.0); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/2d/default_cell_size", PROPERTY_HINT_RANGE, NavigationDefaults2D::navmesh_cell_size_hint), NavigationDefaults2D::navmesh_cell_size); GLOBAL_DEF("navigation/2d/use_edge_connections", true); - GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", 1.0); - GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", 4.0); + GLOBAL_DEF_BASIC("navigation/2d/default_edge_connection_margin", NavigationDefaults2D::edge_connection_margin); + GLOBAL_DEF_BASIC("navigation/2d/default_link_connection_radius", NavigationDefaults2D::link_connection_radius); - GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/3d/default_cell_size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), 0.25); - GLOBAL_DEF_BASIC("navigation/3d/default_cell_height", 0.25); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::FLOAT, "navigation/3d/default_cell_size", PROPERTY_HINT_RANGE, NavigationDefaults3D::navmesh_cell_size_hint), NavigationDefaults3D::navmesh_cell_size); + GLOBAL_DEF_BASIC("navigation/3d/default_cell_height", NavigationDefaults3D::navmesh_cell_height); GLOBAL_DEF("navigation/3d/default_up", Vector3(0, 1, 0)); GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "navigation/3d/merge_rasterizer_cell_scale", PROPERTY_HINT_RANGE, "0.001,1,0.001,or_greater"), 1.0); GLOBAL_DEF("navigation/3d/use_edge_connections", true); - GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", 0.25); - GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", 1.0); + GLOBAL_DEF_BASIC("navigation/3d/default_edge_connection_margin", NavigationDefaults3D::edge_connection_margin); + GLOBAL_DEF_BASIC("navigation/3d/default_link_connection_radius", NavigationDefaults3D::link_connection_radius); GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_multiple_threads", true); GLOBAL_DEF("navigation/avoidance/thread_model/avoidance_use_high_priority_threads", true); diff --git a/servers/rendering/renderer_rd/storage_rd/light_storage.h b/servers/rendering/renderer_rd/storage_rd/light_storage.h index 94ab219dc2..1db58d72f9 100644 --- a/servers/rendering/renderer_rd/storage_rd/light_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/light_storage.h @@ -49,7 +49,7 @@ namespace RendererRD { class LightStorage : public RendererLightStorage { public: - enum ShadowAtlastQuadrant { + enum ShadowAtlastQuadrant : uint32_t { QUADRANT_SHIFT = 27, OMNI_LIGHT_FLAG = 1 << 26, SHADOW_INDEX_MASK = OMNI_LIGHT_FLAG - 1, 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/rendering_device.h b/servers/rendering/rendering_device.h index d8f9e2c31a..1405f585b2 100644 --- a/servers/rendering/rendering_device.h +++ b/servers/rendering/rendering_device.h @@ -377,7 +377,9 @@ public: // used for the render pipelines. struct AttachmentFormat { - enum { UNUSED_ATTACHMENT = 0xFFFFFFFF }; + enum : uint32_t { + UNUSED_ATTACHMENT = 0xFFFFFFFF + }; DataFormat format; TextureSamples samples; uint32_t usage_flags; 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/tests/core/io/test_json_native.h b/tests/core/io/test_json_native.h new file mode 100644 index 0000000000..819078ac57 --- /dev/null +++ b/tests/core/io/test_json_native.h @@ -0,0 +1,160 @@ +/**************************************************************************/ +/* test_json_native.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_JSON_NATIVE_H +#define TEST_JSON_NATIVE_H + +#include "core/io/json.h" + +namespace TestJSONNative { + +bool compare_variants(Variant variant_1, Variant variant_2, int depth = 0) { + if (depth > 100) { + return false; + } + if (variant_1.get_type() == Variant::RID && variant_2.get_type() == Variant::RID) { + return true; + } + if (variant_1.get_type() == Variant::CALLABLE || variant_2.get_type() == Variant::CALLABLE) { + return true; + } + + List<PropertyInfo> variant_1_properties; + variant_1.get_property_list(&variant_1_properties); + List<PropertyInfo> variant_2_properties; + variant_2.get_property_list(&variant_2_properties); + + if (variant_1_properties.size() != variant_2_properties.size()) { + return false; + } + + for (List<PropertyInfo>::Element *E = variant_1_properties.front(); E; E = E->next()) { + String name = E->get().name; + Variant variant_1_value = variant_1.get(name); + Variant variant_2_value = variant_2.get(name); + + if (!compare_variants(variant_1_value, variant_2_value, depth + 1)) { + return false; + } + } + + return true; +} + +TEST_CASE("[JSON][Native][SceneTree] Conversion between native and JSON formats") { + for (int variant_i = 0; variant_i < Variant::VARIANT_MAX; variant_i++) { + Variant::Type type = static_cast<Variant::Type>(variant_i); + Variant native_data; + Callable::CallError error; + + if (type == Variant::Type::INT || type == Variant::Type::FLOAT) { + Variant value = int64_t(INT64_MAX); + const Variant *args[] = { &value }; + Variant::construct(type, native_data, args, 1, error); + } else if (type == Variant::Type::OBJECT) { + Ref<JSON> json = memnew(JSON); + native_data = json; + } else if (type == Variant::Type::DICTIONARY) { + Dictionary dictionary; + dictionary["key"] = "value"; + native_data = dictionary; + } else if (type == Variant::Type::ARRAY) { + Array array; + array.push_back("element1"); + array.push_back("element2"); + native_data = array; + } else if (type == Variant::Type::PACKED_BYTE_ARRAY) { + PackedByteArray packed_array; + packed_array.push_back(1); + packed_array.push_back(2); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_INT32_ARRAY) { + PackedInt32Array packed_array; + packed_array.push_back(INT32_MIN); + packed_array.push_back(INT32_MAX); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_INT64_ARRAY) { + PackedInt64Array packed_array; + packed_array.push_back(INT64_MIN); + packed_array.push_back(INT64_MAX); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_FLOAT32_ARRAY) { + PackedFloat32Array packed_array; + packed_array.push_back(FLT_MIN); + packed_array.push_back(FLT_MAX); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_FLOAT64_ARRAY) { + PackedFloat64Array packed_array; + packed_array.push_back(DBL_MIN); + packed_array.push_back(DBL_MAX); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_STRING_ARRAY) { + PackedStringArray packed_array; + packed_array.push_back("string1"); + packed_array.push_back("string2"); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_VECTOR2_ARRAY) { + PackedVector2Array packed_array; + Vector2 vector(1.0, 2.0); + packed_array.push_back(vector); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_VECTOR3_ARRAY) { + PackedVector3Array packed_array; + Vector3 vector(1.0, 2.0, 3.0); + packed_array.push_back(vector); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_COLOR_ARRAY) { + PackedColorArray packed_array; + Color color(1.0, 1.0, 1.0); + packed_array.push_back(color); + native_data = packed_array; + } else if (type == Variant::Type::PACKED_VECTOR4_ARRAY) { + PackedVector4Array packed_array; + Vector4 vector(1.0, 2.0, 3.0, 4.0); + packed_array.push_back(vector); + native_data = packed_array; + } else { + Variant::construct(type, native_data, nullptr, 0, error); + } + Variant json_converted_from_native = JSON::from_native(native_data, true, true); + Variant variant_native_converted = JSON::to_native(json_converted_from_native, true, true); + CHECK_MESSAGE(compare_variants(native_data, variant_native_converted), + vformat("Conversion from native to JSON type %s and back successful. \nNative: %s \nNative Converted: %s \nError: %s\nConversion from native to JSON type %s successful: %s", + Variant::get_type_name(type), + native_data, + variant_native_converted, + itos(error.error), + Variant::get_type_name(type), + json_converted_from_native)); + } +} +} // namespace TestJSONNative + +#endif // TEST_JSON_NATIVE_H diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index 57bc65328a..f1bb62cb70 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -174,6 +174,31 @@ TEST_CASE("[Object] Metadata") { CHECK_MESSAGE( meta_list2.size() == 0, "The metadata list should contain 0 items after removing all metadata items."); + + Object other; + object.set_meta("conflicting_meta", "string"); + object.set_meta("not_conflicting_meta", 123); + other.set_meta("conflicting_meta", Color(0, 1, 0)); + other.set_meta("other_meta", "other"); + object.merge_meta_from(&other); + + CHECK_MESSAGE( + Color(object.get_meta("conflicting_meta")).is_equal_approx(Color(0, 1, 0)), + "String meta should be overwritten with Color after merging."); + + CHECK_MESSAGE( + int(object.get_meta("not_conflicting_meta")) == 123, + "Not conflicting meta on destination should be kept intact."); + + CHECK_MESSAGE( + object.get_meta("other_meta", String()) == "other", + "Not conflicting meta name on source should merged."); + + List<StringName> meta_list3; + object.get_meta_list(&meta_list3); + CHECK_MESSAGE( + meta_list3.size() == 3, + "The metadata list should contain 3 items after merging meta from two objects."); } TEST_CASE("[Object] Construction") { diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index b47e5b1eb9..a9f615af84 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -433,6 +433,19 @@ TEST_CASE("[String] Insertion") { String s = "Who is Frederic?"; s = s.insert(s.find("?"), " Chopin"); CHECK(s == "Who is Frederic Chopin?"); + + s = "foobar"; + CHECK(s.insert(0, "X") == "Xfoobar"); + CHECK(s.insert(-100, "X") == "foobar"); + CHECK(s.insert(6, "X") == "foobarX"); + CHECK(s.insert(100, "X") == "foobarX"); + CHECK(s.insert(2, "") == "foobar"); + + s = ""; + CHECK(s.insert(0, "abc") == "abc"); + CHECK(s.insert(100, "abc") == "abc"); + CHECK(s.insert(-100, "abc") == ""); + CHECK(s.insert(0, "") == ""); } TEST_CASE("[String] Erasing") { @@ -1811,13 +1824,25 @@ TEST_CASE("[String] SHA1/SHA256/MD5") { } TEST_CASE("[String] Join") { - String s = ", "; + String comma = ", "; + String empty = ""; Vector<String> parts; + + CHECK(comma.join(parts) == ""); + CHECK(empty.join(parts) == ""); + parts.push_back("One"); + CHECK(comma.join(parts) == "One"); + CHECK(empty.join(parts) == "One"); + parts.push_back("B"); parts.push_back("C"); - String t = s.join(parts); - CHECK(t == "One, B, C"); + CHECK(comma.join(parts) == "One, B, C"); + CHECK(empty.join(parts) == "OneBC"); + + parts.push_back(""); + CHECK(comma.join(parts) == "One, B, C, "); + CHECK(empty.join(parts) == "OneBC"); } TEST_CASE("[String] Is_*") { diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 46714a2627..7e1c431a3c 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -48,6 +48,7 @@ #include "tests/core/io/test_image.h" #include "tests/core/io/test_ip.h" #include "tests/core/io/test_json.h" +#include "tests/core/io/test_json_native.h" #include "tests/core/io/test_marshalls.h" #include "tests/core/io/test_pck_packer.h" #include "tests/core/io/test_resource.h" diff --git a/thirdparty/libwebp/patches/godot-clang-cl-fix.patch b/thirdparty/libwebp/patches/godot-clang-cl-fix.patch new file mode 100644 index 0000000000..ee4f598951 --- /dev/null +++ b/thirdparty/libwebp/patches/godot-clang-cl-fix.patch @@ -0,0 +1,19 @@ +diff --git a/thirdparty/libwebp/src/dsp/cpu.h b/thirdparty/libwebp/src/dsp/cpu.h +index c86540f280..4dbe607aec 100644 +--- a/thirdparty/libwebp/src/dsp/cpu.h ++++ b/thirdparty/libwebp/src/dsp/cpu.h +@@ -47,12 +47,12 @@ + // x86 defines. + + #if !defined(HAVE_CONFIG_H) +-#if defined(_MSC_VER) && _MSC_VER > 1310 && \ ++#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1310 && \ + (defined(_M_X64) || defined(_M_IX86)) + #define WEBP_MSC_SSE2 // Visual C++ SSE2 targets + #endif + +-#if defined(_MSC_VER) && _MSC_VER >= 1500 && \ ++#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER >= 1500 && \ + (defined(_M_X64) || defined(_M_IX86)) + #define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets + #endif diff --git a/thirdparty/libwebp/src/dsp/cpu.h b/thirdparty/libwebp/src/dsp/cpu.h index c86540f280..4dbe607aec 100644 --- a/thirdparty/libwebp/src/dsp/cpu.h +++ b/thirdparty/libwebp/src/dsp/cpu.h @@ -47,12 +47,12 @@ // x86 defines. #if !defined(HAVE_CONFIG_H) -#if defined(_MSC_VER) && _MSC_VER > 1310 && \ +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER > 1310 && \ (defined(_M_X64) || defined(_M_IX86)) #define WEBP_MSC_SSE2 // Visual C++ SSE2 targets #endif -#if defined(_MSC_VER) && _MSC_VER >= 1500 && \ +#if defined(_MSC_VER) && !defined(__clang__) && _MSC_VER >= 1500 && \ (defined(_M_X64) || defined(_M_IX86)) #define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets #endif diff --git a/thirdparty/thorvg/inc/config.h b/thirdparty/thorvg/inc/config.h index 67ebc9381e..02fec07448 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.8" #endif diff --git a/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch b/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch new file mode 100644 index 0000000000..69f4a5cf85 --- /dev/null +++ b/thirdparty/thorvg/patches/pr2702-sw_engine-handle-small-cubics.patch @@ -0,0 +1,96 @@ +From ac7d208ed8e4651c93ce1b2384070fccac9b6cb6 Mon Sep 17 00:00:00 2001 +From: Mira Grudzinska <mira@lottiefiles.com> +Date: Sun, 1 Sep 2024 22:36:18 +0200 +Subject: [PATCH] sw_engine: handle small cubics + +During the stroke's outline calculation, the function +handling small cubics set all angles to zero. Such cases +should be ignored, as further processing caused errors - +when the cubic was small but not zero, setting the angles +to zero resulted in incorrect outlines. + +@Issue: https://github.com/godotengine/godot/issues/96262 +--- + src/renderer/sw_engine/tvgSwCommon.h | 3 ++- + src/renderer/sw_engine/tvgSwMath.cpp | 19 ++++++++++++------- + src/renderer/sw_engine/tvgSwStroke.cpp | 16 +++++++++++----- + 3 files changed, 25 insertions(+), 13 deletions(-) + +diff --git a/src/renderer/sw_engine/tvgSwCommon.h b/src/renderer/sw_engine/tvgSwCommon.h +index 893e9beca..158fe8ecd 100644 +--- a/src/renderer/sw_engine/tvgSwCommon.h ++++ b/src/renderer/sw_engine/tvgSwCommon.h +@@ -491,7 +491,8 @@ SwFixed mathSin(SwFixed angle); + void mathSplitCubic(SwPoint* base); + SwFixed mathDiff(SwFixed angle1, SwFixed angle2); + SwFixed mathLength(const SwPoint& pt); +-bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); ++bool mathSmallCubic(const SwPoint* base); ++bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); + SwFixed mathMean(SwFixed angle1, SwFixed angle2); + SwPoint mathTransform(const Point* to, const Matrix& transform); + bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack); +diff --git a/src/renderer/sw_engine/tvgSwMath.cpp b/src/renderer/sw_engine/tvgSwMath.cpp +index 1093edd62..b311be05f 100644 +--- a/src/renderer/sw_engine/tvgSwMath.cpp ++++ b/src/renderer/sw_engine/tvgSwMath.cpp +@@ -44,7 +44,17 @@ SwFixed mathMean(SwFixed angle1, SwFixed angle2) + } + + +-bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) ++bool mathSmallCubic(const SwPoint* base) ++{ ++ auto d1 = base[2] - base[3]; ++ auto d2 = base[1] - base[2]; ++ auto d3 = base[0] - base[1]; ++ ++ return d1.small() && d2.small() && d3.small(); ++} ++ ++ ++bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) + { + auto d1 = base[2] - base[3]; + auto d2 = base[1] - base[2]; +@@ -52,12 +62,7 @@ bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, Sw + + if (d1.small()) { + if (d2.small()) { +- if (d3.small()) { +- angleIn = angleMid = angleOut = 0; +- return true; +- } else { +- angleIn = angleMid = angleOut = mathAtan(d3); +- } ++ angleIn = angleMid = angleOut = mathAtan(d3); + } else { + if (d3.small()) { + angleIn = angleMid = angleOut = mathAtan(d2); +diff --git a/src/renderer/sw_engine/tvgSwStroke.cpp b/src/renderer/sw_engine/tvgSwStroke.cpp +index 575d12951..4679b72cc 100644 +--- a/src/renderer/sw_engine/tvgSwStroke.cpp ++++ b/src/renderer/sw_engine/tvgSwStroke.cpp +@@ -441,11 +441,17 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl + //initialize with current direction + angleIn = angleOut = angleMid = stroke.angleIn; + +- if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) { +- if (stroke.firstPt) stroke.angleIn = angleIn; +- mathSplitCubic(arc); +- arc += 3; +- continue; ++ if (arc < limit) { ++ if (mathSmallCubic(arc)) { ++ arc -= 3; ++ continue; ++ } ++ if (!mathFlatCubic(arc, angleIn, angleMid, angleOut)) { ++ if (stroke.firstPt) stroke.angleIn = angleIn; ++ mathSplitCubic(arc); ++ arc += 3; ++ continue; ++ } + } + + if (firstArc) { diff --git a/thirdparty/thorvg/src/common/tvgMath.cpp b/thirdparty/thorvg/src/common/tvgMath.cpp index c56b32249f..0254cce9b8 100644 --- a/thirdparty/thorvg/src/common/tvgMath.cpp +++ b/thirdparty/thorvg/src/common/tvgMath.cpp @@ -133,3 +133,11 @@ 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); + if (result > 255) result = 255; + else if (result < 0) result = 0; + return static_cast<uint8_t>(result); +} diff --git a/thirdparty/thorvg/src/common/tvgMath.h b/thirdparty/thorvg/src/common/tvgMath.h index 556ed410ff..df39e3b9af 100644 --- a/thirdparty/thorvg/src/common/tvgMath.h +++ b/thirdparty/thorvg/src/common/tvgMath.h @@ -259,5 +259,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..5aab4f1b0d 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); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwCommon.h index 3229eb39ba..09b75d370b 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) { @@ -492,7 +495,8 @@ SwFixed mathSin(SwFixed angle); void mathSplitCubic(SwPoint* base); SwFixed mathDiff(SwFixed angle1, SwFixed angle2); SwFixed mathLength(const SwPoint& pt); -bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); +bool mathSmallCubic(const SwPoint* base); +bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut); SwFixed mathMean(SwFixed angle1, SwFixed angle2); SwPoint mathTransform(const Point* to, const Matrix& transform); bool mathUpdateOutlineBBox(const SwOutline* outline, const SwBBox& clipRegion, SwBBox& renderRegion, bool fastTrack); diff --git a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp index fb809c4f7e..60dbbc4fbc 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwMath.cpp @@ -44,7 +44,17 @@ SwFixed mathMean(SwFixed angle1, SwFixed angle2) } -bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) +bool mathSmallCubic(const SwPoint* base) +{ + auto d1 = base[2] - base[3]; + auto d2 = base[1] - base[2]; + auto d3 = base[0] - base[1]; + + return d1.small() && d2.small() && d3.small(); +} + + +bool mathFlatCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, SwFixed& angleOut) { auto d1 = base[2] - base[3]; auto d2 = base[1] - base[2]; @@ -52,12 +62,7 @@ bool mathSmallCubic(const SwPoint* base, SwFixed& angleIn, SwFixed& angleMid, Sw if (d1.small()) { if (d2.small()) { - if (d3.small()) { - angleIn = angleMid = angleOut = 0; - return true; - } else { - angleIn = angleMid = angleOut = mathAtan(d3); - } + angleIn = angleMid = angleOut = mathAtan(d3); } else { if (d3.small()) { angleIn = angleMid = angleOut = mathAtan(d2); 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/tvgSwStroke.cpp b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp index 75ac96be04..e0e74ce53c 100644 --- a/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp +++ b/thirdparty/thorvg/src/renderer/sw_engine/tvgSwStroke.cpp @@ -441,11 +441,17 @@ static void _cubicTo(SwStroke& stroke, const SwPoint& ctrl1, const SwPoint& ctrl //initialize with current direction angleIn = angleOut = angleMid = stroke.angleIn; - if (arc < limit && !mathSmallCubic(arc, angleIn, angleMid, angleOut)) { - if (stroke.firstPt) stroke.angleIn = angleIn; - mathSplitCubic(arc); - arc += 3; - continue; + if (arc < limit) { + if (mathSmallCubic(arc)) { + arc -= 3; + continue; + } + if (!mathFlatCubic(arc, angleIn, angleMid, angleOut)) { + if (stroke.firstPt) stroke.angleIn = angleIn; + mathSplitCubic(arc); + arc += 3; + continue; + } } if (firstArc) { 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..746b85bea6 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,7 @@ struct Text::Impl bool render(RenderMethod* renderer) { + renderer->blend(paint->blend(), true); return PP(shape)->render(renderer); } @@ -98,13 +97,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..51dc156661 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.8 +# 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 |