diff options
Diffstat (limited to 'core')
69 files changed, 1093 insertions, 516 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 10cdf5b2e7..e59f79fcc8 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -329,9 +329,9 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { String path = p_value; if (path.begins_with("*")) { autoload.is_singleton = true; - autoload.path = path.substr(1); + autoload.path = path.substr(1).simplify_path(); } else { - autoload.path = path; + autoload.path = path.simplify_path(); } add_autoload(autoload); } else if (p_name.operator String().begins_with("global_group/")) { @@ -1480,6 +1480,8 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("animation/warnings/check_angle_interpolation_type_conflicting", true); GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "audio/buses/default_bus_layout", PROPERTY_HINT_FILE, "*.tres"), "res://default_bus_layout.tres"); + GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/default_playback_type", PROPERTY_HINT_ENUM, "Stream,Sample"), 0); + GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/default_playback_type.web", PROPERTY_HINT_ENUM, "Stream,Sample"), 1); GLOBAL_DEF_RST("audio/general/text_to_speech", false); GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/2d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f); GLOBAL_DEF_RST(PropertyInfo(Variant::FLOAT, "audio/general/3d_panning_strength", PROPERTY_HINT_RANGE, "0,2,0.01"), 0.5f); @@ -1513,6 +1515,7 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "display/window/stretch/scale_mode", PROPERTY_HINT_ENUM, "fractional,integer"), "fractional"); GLOBAL_DEF(PropertyInfo(Variant::INT, "debug/settings/profiler/max_functions", PROPERTY_HINT_RANGE, "128,65535,1"), 16384); + GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "debug/settings/profiler/max_timestamp_query_elements", PROPERTY_HINT_RANGE, "256,65535,1"), 256); GLOBAL_DEF(PropertyInfo(Variant::BOOL, "compression/formats/zstd/long_distance_matching"), Compression::zstd_long_distance_matching); GLOBAL_DEF(PropertyInfo(Variant::INT, "compression/formats/zstd/compression_level", PROPERTY_HINT_RANGE, "1,22,1"), Compression::zstd_level); diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 0996db9d89..a1b7b81111 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/crypto/crypto_core.h" #include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" #include "core/io/file_access_compressed.h" #include "core/io/file_access_encrypted.h" #include "core/io/marshalls.h" @@ -193,6 +194,18 @@ void ResourceSaver::_bind_methods() { ////// OS ////// +PackedByteArray OS::get_entropy(int p_bytes) { + PackedByteArray pba; + pba.resize(p_bytes); + Error err = ::OS::get_singleton()->get_entropy(pba.ptrw(), p_bytes); + ERR_FAIL_COND_V(err != OK, PackedByteArray()); + return pba; +} + +String OS::get_system_ca_certificates() { + return ::OS::get_singleton()->get_system_ca_certificates(); +} + PackedStringArray OS::get_connected_midi_inputs() { return ::OS::get_singleton()->get_connected_midi_inputs(); } @@ -572,6 +585,8 @@ String OS::get_unique_id() const { OS *OS::singleton = nullptr; void OS::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_entropy", "size"), &OS::get_entropy); + ClassDB::bind_method(D_METHOD("get_system_ca_certificates"), &OS::get_system_ca_certificates); ClassDB::bind_method(D_METHOD("get_connected_midi_inputs"), &OS::get_connected_midi_inputs); ClassDB::bind_method(D_METHOD("open_midi_inputs"), &OS::open_midi_inputs); ClassDB::bind_method(D_METHOD("close_midi_inputs"), &OS::close_midi_inputs); @@ -1919,6 +1934,16 @@ void EngineDebugger::send_message(const String &p_msg, const Array &p_data) { ::EngineDebugger::get_singleton()->send_message(p_msg, p_data); } +void EngineDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { + ERR_FAIL_COND_MSG(!::EngineDebugger::is_active(), "Can't send debug. No active debugger"); + ::EngineDebugger::get_singleton()->debug(p_can_continue, p_is_error_breakpoint); +} + +void EngineDebugger::script_debug(ScriptLanguage *p_lang, bool p_can_continue, bool p_is_error_breakpoint) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't send debug. No active debugger"); + ::EngineDebugger::get_script_debugger()->debug(p_lang, p_can_continue, p_is_error_breakpoint); +} + Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) { Callable &capture = *(Callable *)p_user; if (!capture.is_valid()) { @@ -1935,6 +1960,56 @@ Error EngineDebugger::call_capture(void *p_user, const String &p_cmd, const Arra return OK; } +void EngineDebugger::line_poll() { + ERR_FAIL_COND_MSG(!::EngineDebugger::is_active(), "Can't poll. No active debugger"); + ::EngineDebugger::get_singleton()->line_poll(); +} + +void EngineDebugger::set_lines_left(int p_lines) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't set lines left. No active debugger"); + ::EngineDebugger::get_script_debugger()->set_lines_left(p_lines); +} + +int EngineDebugger::get_lines_left() const { + ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), 0, "Can't get lines left. No active debugger"); + return ::EngineDebugger::get_script_debugger()->get_lines_left(); +} + +void EngineDebugger::set_depth(int p_depth) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't set depth. No active debugger"); + ::EngineDebugger::get_script_debugger()->set_depth(p_depth); +} + +int EngineDebugger::get_depth() const { + ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), 0, "Can't get depth. No active debugger"); + return ::EngineDebugger::get_script_debugger()->get_depth(); +} + +bool EngineDebugger::is_breakpoint(int p_line, const StringName &p_source) const { + ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), false, "Can't check breakpoint. No active debugger"); + return ::EngineDebugger::get_script_debugger()->is_breakpoint(p_line, p_source); +} + +bool EngineDebugger::is_skipping_breakpoints() const { + ERR_FAIL_COND_V_MSG(!::EngineDebugger::get_script_debugger(), false, "Can't check skipping breakpoint. No active debugger"); + return ::EngineDebugger::get_script_debugger()->is_skipping_breakpoints(); +} + +void EngineDebugger::insert_breakpoint(int p_line, const StringName &p_source) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't insert breakpoint. No active debugger"); + ::EngineDebugger::get_script_debugger()->insert_breakpoint(p_line, p_source); +} + +void EngineDebugger::remove_breakpoint(int p_line, const StringName &p_source) { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't remove breakpoint. No active debugger"); + ::EngineDebugger::get_script_debugger()->remove_breakpoint(p_line, p_source); +} + +void EngineDebugger::clear_breakpoints() { + ERR_FAIL_COND_MSG(!::EngineDebugger::get_script_debugger(), "Can't clear breakpoints. No active debugger"); + ::EngineDebugger::get_script_debugger()->clear_breakpoints(); +} + EngineDebugger::~EngineDebugger() { for (const KeyValue<StringName, Callable> &E : captures) { ::EngineDebugger::unregister_message_capture(E.key); @@ -1960,7 +2035,23 @@ void EngineDebugger::_bind_methods() { ClassDB::bind_method(D_METHOD("unregister_message_capture", "name"), &EngineDebugger::unregister_message_capture); ClassDB::bind_method(D_METHOD("has_capture", "name"), &EngineDebugger::has_capture); + ClassDB::bind_method(D_METHOD("line_poll"), &EngineDebugger::line_poll); + ClassDB::bind_method(D_METHOD("send_message", "message", "data"), &EngineDebugger::send_message); + ClassDB::bind_method(D_METHOD("debug", "can_continue", "is_error_breakpoint"), &EngineDebugger::debug, DEFVAL(true), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("script_debug", "language", "can_continue", "is_error_breakpoint"), &EngineDebugger::script_debug, DEFVAL(true), DEFVAL(false)); + + ClassDB::bind_method(D_METHOD("set_lines_left", "lines"), &EngineDebugger::set_lines_left); + ClassDB::bind_method(D_METHOD("get_lines_left"), &EngineDebugger::get_lines_left); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &EngineDebugger::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &EngineDebugger::get_depth); + + ClassDB::bind_method(D_METHOD("is_breakpoint", "line", "source"), &EngineDebugger::is_breakpoint); + ClassDB::bind_method(D_METHOD("is_skipping_breakpoints"), &EngineDebugger::is_skipping_breakpoints); + ClassDB::bind_method(D_METHOD("insert_breakpoint", "line", "source"), &EngineDebugger::insert_breakpoint); + ClassDB::bind_method(D_METHOD("remove_breakpoint", "line", "source"), &EngineDebugger::remove_breakpoint); + ClassDB::bind_method(D_METHOD("clear_breakpoints"), &EngineDebugger::clear_breakpoints); } } // namespace core_bind diff --git a/core/core_bind.h b/core/core_bind.h index 148e0ad83e..b142a2fbbd 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -134,6 +134,9 @@ public: RENDERING_DRIVER_D3D12, }; + PackedByteArray get_entropy(int p_bytes); + String get_system_ca_certificates(); + virtual PackedStringArray get_connected_midi_inputs(); virtual void open_midi_inputs(); virtual void close_midi_inputs(); @@ -576,9 +579,25 @@ public: bool has_capture(const StringName &p_name); void send_message(const String &p_msg, const Array &p_data); + void debug(bool p_can_continue = true, bool p_is_error_breakpoint = false); + void script_debug(ScriptLanguage *p_lang, bool p_can_continue = true, bool p_is_error_breakpoint = false); static Error call_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured); + void line_poll(); + + void set_lines_left(int p_lines); + int get_lines_left() const; + + void set_depth(int p_depth); + int get_depth() const; + + bool is_breakpoint(int p_line, const StringName &p_source) const; + bool is_skipping_breakpoints() const; + void insert_breakpoint(int p_line, const StringName &p_source); + void remove_breakpoint(int p_line, const StringName &p_source); + void clear_breakpoints(); + EngineDebugger() { singleton = this; } ~EngineDebugger(); }; diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp index 7fef819159..d3d0079410 100644 --- a/core/crypto/crypto.cpp +++ b/core/crypto/crypto.cpp @@ -72,31 +72,26 @@ void X509Certificate::_bind_methods() { Ref<TLSOptions> TLSOptions::client(Ref<X509Certificate> p_trusted_chain, const String &p_common_name_override) { Ref<TLSOptions> opts; opts.instantiate(); + opts->mode = MODE_CLIENT; opts->trusted_ca_chain = p_trusted_chain; opts->common_name = p_common_name_override; - opts->verify_mode = TLS_VERIFY_FULL; return opts; } Ref<TLSOptions> TLSOptions::client_unsafe(Ref<X509Certificate> p_trusted_chain) { Ref<TLSOptions> opts; opts.instantiate(); + opts->mode = MODE_CLIENT_UNSAFE; opts->trusted_ca_chain = p_trusted_chain; - if (p_trusted_chain.is_null()) { - opts->verify_mode = TLS_VERIFY_NONE; - } else { - opts->verify_mode = TLS_VERIFY_CERT; - } return opts; } Ref<TLSOptions> TLSOptions::server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate) { Ref<TLSOptions> opts; opts.instantiate(); - opts->server_mode = true; + opts->mode = MODE_SERVER; opts->own_certificate = p_own_certificate; opts->private_key = p_own_key; - opts->verify_mode = TLS_VERIFY_NONE; return opts; } @@ -104,6 +99,13 @@ void TLSOptions::_bind_methods() { ClassDB::bind_static_method("TLSOptions", D_METHOD("client", "trusted_chain", "common_name_override"), &TLSOptions::client, DEFVAL(Ref<X509Certificate>()), DEFVAL(String())); ClassDB::bind_static_method("TLSOptions", D_METHOD("client_unsafe", "trusted_chain"), &TLSOptions::client_unsafe, DEFVAL(Ref<X509Certificate>())); ClassDB::bind_static_method("TLSOptions", D_METHOD("server", "key", "certificate"), &TLSOptions::server); + + ClassDB::bind_method(D_METHOD("is_server"), &TLSOptions::is_server); + ClassDB::bind_method(D_METHOD("is_unsafe_client"), &TLSOptions::is_unsafe_client); + ClassDB::bind_method(D_METHOD("get_common_name_override"), &TLSOptions::get_common_name_override); + ClassDB::bind_method(D_METHOD("get_trusted_ca_chain"), &TLSOptions::get_trusted_ca_chain); + ClassDB::bind_method(D_METHOD("get_private_key"), &TLSOptions::get_private_key); + ClassDB::bind_method(D_METHOD("get_own_certificate"), &TLSOptions::get_own_certificate); } /// HMACContext diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h index fbd01be86d..16649422cf 100644 --- a/core/crypto/crypto.h +++ b/core/crypto/crypto.h @@ -72,17 +72,15 @@ public: class TLSOptions : public RefCounted { GDCLASS(TLSOptions, RefCounted); -public: - enum TLSVerifyMode { - TLS_VERIFY_NONE = 0, - TLS_VERIFY_CERT = 1, - TLS_VERIFY_FULL = 2, +private: + enum Mode { + MODE_CLIENT = 0, + MODE_CLIENT_UNSAFE = 1, + MODE_SERVER = 2, }; -private: - bool server_mode = false; + Mode mode = MODE_CLIENT; String common_name; - TLSVerifyMode verify_mode = TLS_VERIFY_FULL; Ref<X509Certificate> trusted_ca_chain; Ref<X509Certificate> own_certificate; Ref<CryptoKey> private_key; @@ -95,12 +93,12 @@ public: static Ref<TLSOptions> client_unsafe(Ref<X509Certificate> p_trusted_chain); static Ref<TLSOptions> server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate); - TLSVerifyMode get_verify_mode() const { return verify_mode; } - String get_common_name() const { return common_name; } + String get_common_name_override() const { return common_name; } Ref<X509Certificate> get_trusted_ca_chain() const { return trusted_ca_chain; } Ref<X509Certificate> get_own_certificate() const { return own_certificate; } Ref<CryptoKey> get_private_key() const { return private_key; } - bool is_server() const { return server_mode; } + bool is_server() const { return mode == MODE_SERVER; } + bool is_unsafe_client() const { return mode == MODE_CLIENT_UNSAFE; } }; class HMACContext : public RefCounted { diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp index bd30da3047..e2ed7245a2 100644 --- a/core/debugger/remote_debugger.cpp +++ b/core/debugger/remote_debugger.cpp @@ -39,6 +39,7 @@ #include "core/io/resource_loader.h" #include "core/object/script_language.h" #include "core/os/os.h" +#include "servers/display_server.h" class RemoteDebugger::PerformanceProfiler : public EngineProfiler { Object *performance = nullptr; @@ -539,7 +540,7 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) { OS::get_singleton()->delay_usec(10000); if (Thread::get_caller_id() == Thread::get_main_id()) { // If this is a busy loop on the main thread, events still need to be processed. - OS::get_singleton()->process_and_drop_events(); + DisplayServer::get_singleton()->force_process_and_drop_events(); } } } diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 47628e4ea0..8e2366fc95 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -795,7 +795,7 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb // because that's what we want to check to see if it's changed. library_path = actual_lib_path.get_base_dir().path_join(p_path.get_file()); } else { - library_path = p_path; + library_path = actual_lib_path; } ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path); diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 98f5cb4d02..85f83eecfd 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -805,12 +805,24 @@ static void gdextension_string_new_with_utf8_chars_and_len(GDExtensionUninitiali dest->parse_utf8(p_contents, p_size); } +static GDExtensionInt gdextension_string_new_with_utf8_chars_and_len2(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) { + memnew_placement(r_dest, String); + String *dest = reinterpret_cast<String *>(r_dest); + return (GDExtensionInt)dest->parse_utf8(p_contents, p_size); +} + static void gdextension_string_new_with_utf16_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_char_count) { memnew_placement(r_dest, String); String *dest = reinterpret_cast<String *>(r_dest); dest->parse_utf16(p_contents, p_char_count); } +static GDExtensionInt gdextension_string_new_with_utf16_chars_and_len2(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_char_count, GDExtensionBool p_default_little_endian) { + memnew_placement(r_dest, String); + String *dest = reinterpret_cast<String *>(r_dest); + return (GDExtensionInt)dest->parse_utf16(p_contents, p_char_count, p_default_little_endian); +} + static void gdextension_string_new_with_utf32_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_char_count) { memnew_placement(r_dest, String((const char32_t *)p_contents, p_char_count)); } @@ -962,6 +974,16 @@ static uint64_t gdextension_file_access_get_buffer(GDExtensionConstObjectPtr p_i return fa->get_buffer(p_dst, p_length); } +static uint8_t *gdextension_image_ptrw(GDExtensionObjectPtr p_instance) { + Image *img = (Image *)p_instance; + return img->ptrw(); +} + +static const uint8_t *gdextension_image_ptr(GDExtensionObjectPtr p_instance) { + Image *img = (Image *)p_instance; + return img->ptr(); +} + static int64_t gdextension_worker_thread_pool_add_native_group_task(GDExtensionObjectPtr p_instance, void (*p_func)(void *, uint32_t), void *p_userdata, int p_elements, int p_tasks, GDExtensionBool p_high_priority, GDExtensionConstStringPtr p_description) { WorkerThreadPool *p = (WorkerThreadPool *)p_instance; const String *description = (const String *)p_description; @@ -1598,7 +1620,9 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(string_new_with_wide_chars); REGISTER_INTERFACE_FUNC(string_new_with_latin1_chars_and_len); REGISTER_INTERFACE_FUNC(string_new_with_utf8_chars_and_len); + REGISTER_INTERFACE_FUNC(string_new_with_utf8_chars_and_len2); REGISTER_INTERFACE_FUNC(string_new_with_utf16_chars_and_len); + REGISTER_INTERFACE_FUNC(string_new_with_utf16_chars_and_len2); REGISTER_INTERFACE_FUNC(string_new_with_utf32_chars_and_len); REGISTER_INTERFACE_FUNC(string_new_with_wide_chars_and_len); REGISTER_INTERFACE_FUNC(string_to_latin1_chars); @@ -1684,6 +1708,8 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(editor_remove_plugin); REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars); REGISTER_INTERFACE_FUNC(editor_help_load_xml_from_utf8_chars_and_len); + REGISTER_INTERFACE_FUNC(image_ptrw); + REGISTER_INTERFACE_FUNC(image_ptr); } #undef REGISTER_INTERFACE_FUNCTION diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index 6fe6b8df20..fce377f967 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -1582,6 +1582,7 @@ typedef void (*GDExtensionInterfaceStringNewWithLatin1CharsAndLen)(GDExtensionUn /** * @name string_new_with_utf8_chars_and_len * @since 4.1 + * @deprecated in Godot 4.3. Use `string_new_with_utf8_chars_and_len2` instead. * * Creates a String from a UTF-8 encoded C string with the given length. * @@ -1592,8 +1593,23 @@ typedef void (*GDExtensionInterfaceStringNewWithLatin1CharsAndLen)(GDExtensionUn typedef void (*GDExtensionInterfaceStringNewWithUtf8CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size); /** + * @name string_new_with_utf8_chars_and_len2 + * @since 4.3 + * + * Creates a String from a UTF-8 encoded C string with the given length. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a UTF-8 encoded C string. + * @param p_size The number of bytes (not code units). + * + * @return Error code signifying if the operation successful. + */ +typedef GDExtensionInt (*GDExtensionInterfaceStringNewWithUtf8CharsAndLen2)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size); + +/** * @name string_new_with_utf16_chars_and_len * @since 4.1 + * @deprecated in Godot 4.3. Use `string_new_with_utf16_chars_and_len2` instead. * * Creates a String from a UTF-16 encoded C string with the given length. * @@ -1604,6 +1620,21 @@ typedef void (*GDExtensionInterfaceStringNewWithUtf8CharsAndLen)(GDExtensionUnin typedef void (*GDExtensionInterfaceStringNewWithUtf16CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_char_count); /** + * @name string_new_with_utf16_chars_and_len2 + * @since 4.3 + * + * Creates a String from a UTF-16 encoded C string with the given length. + * + * @param r_dest A pointer to a Variant to hold the newly created String. + * @param p_contents A pointer to a UTF-16 encoded C string. + * @param p_size The number of characters (not bytes). + * @param p_default_little_endian If true, UTF-16 use little endian. + * + * @return Error code signifying if the operation successful. + */ +typedef GDExtensionInt (*GDExtensionInterfaceStringNewWithUtf16CharsAndLen2)(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_char_count, GDExtensionBool p_default_little_endian); + +/** * @name string_new_with_utf32_chars_and_len * @since 4.1 * @@ -1899,6 +1930,36 @@ typedef void (*GDExtensionInterfaceFileAccessStoreBuffer)(GDExtensionObjectPtr p */ typedef uint64_t (*GDExtensionInterfaceFileAccessGetBuffer)(GDExtensionConstObjectPtr p_instance, uint8_t *p_dst, uint64_t p_length); +/* INTERFACE: Image Utilities */ + +/** + * @name image_ptrw + * @since 4.3 + * + * Returns writable pointer to internal Image buffer. + * + * @param p_instance A pointer to a Image object. + * + * @return Pointer to internal Image buffer. + * + * @see Image::ptrw() + */ +typedef uint8_t *(*GDExtensionInterfaceImagePtrw)(GDExtensionObjectPtr p_instance); + +/** + * @name image_ptr + * @since 4.3 + * + * Returns read only pointer to internal Image buffer. + * + * @param p_instance A pointer to a Image object. + * + * @return Pointer to internal Image buffer. + * + * @see Image::ptr() + */ +typedef const uint8_t *(*GDExtensionInterfaceImagePtr)(GDExtensionObjectPtr p_instance); + /* INTERFACE: WorkerThreadPool Utilities */ /** @@ -2739,12 +2800,16 @@ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassVirtualMethod)(G * * Registers an integer constant on an extension class in the ClassDB. * + * Note about registering bitfield values (if p_is_bitfield is true): even though p_constant_value is signed, language bindings are + * advised to treat bitfields as uint64_t, since this is generally clearer and can prevent mistakes like using -1 for setting all bits. + * Language APIs should thus provide an abstraction that registers bitfields (uint64_t) separately from regular constants (int64_t). + * * @param p_library A pointer the library received by the GDExtension's entry point function. * @param p_class_name A pointer to a StringName with the class name. * @param p_enum_name A pointer to a StringName with the enum name. * @param p_constant_name A pointer to a StringName with the constant name. * @param p_constant_value The constant value. - * @param p_is_bitfield Whether or not this is a bit field. + * @param p_is_bitfield Whether or not this constant is part of a bitfield. */ typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassIntegerConstant)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield); diff --git a/core/input/input.cpp b/core/input/input.cpp index 56f616fac4..91378591b0 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -758,12 +758,13 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em bool was_pressed = action_state.cache.pressed; _update_action_cache(E.key, action_state); + // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. if (action_state.cache.pressed && !was_pressed) { - action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); } if (!action_state.cache.pressed && was_pressed) { - action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); } } @@ -889,8 +890,9 @@ void Input::action_press(const StringName &p_action, float p_strength) { // Create or retrieve existing action. ActionState &action_state = action_states[p_action]; + // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. if (!action_state.cache.pressed) { - action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); } action_state.exact = true; @@ -907,7 +909,8 @@ void Input::action_release(const StringName &p_action) { action_state.cache.pressed = 0; action_state.cache.strength = 0.0; action_state.cache.raw_strength = 0.0; - action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. + action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); action_state.device_states.clear(); action_state.exact = true; @@ -1022,7 +1025,7 @@ void Input::parse_input_event(const Ref<InputEvent> &p_event) { if (buffered_events.is_empty() || !buffered_events.back()->get()->accumulate(p_event)) { buffered_events.push_back(p_event); } - } else if (use_input_buffering) { + } else if (agile_input_event_flushing) { buffered_events.push_back(p_event); } else { _parse_input_event_impl(p_event, false); @@ -1053,12 +1056,12 @@ void Input::flush_buffered_events() { } } -bool Input::is_using_input_buffering() { - return use_input_buffering; +bool Input::is_agile_input_event_flushing() { + return agile_input_event_flushing; } -void Input::set_use_input_buffering(bool p_enable) { - use_input_buffering = p_enable; +void Input::set_agile_input_event_flushing(bool p_enable) { + agile_input_event_flushing = p_enable; } void Input::set_use_accumulated_input(bool p_enable) { diff --git a/core/input/input.h b/core/input/input.h index 4daea0c9e8..89e48f53d7 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -128,7 +128,7 @@ private: bool emulate_touch_from_mouse = false; bool emulate_mouse_from_touch = false; - bool use_input_buffering = false; + bool agile_input_event_flushing = false; bool use_accumulated_input = true; int mouse_from_touch_index = -1; @@ -367,8 +367,8 @@ public: void flush_frame_parsed_events(); #endif void flush_buffered_events(); - bool is_using_input_buffering(); - void set_use_input_buffering(bool p_enable); + bool is_agile_input_event_flushing(); + void set_agile_input_event_flushing(bool p_enable); void set_use_accumulated_input(bool p_enable); bool is_using_accumulated_input(); diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 178d02b987..ddeee9d765 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -636,6 +636,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::A | KeyModifierMask::CTRL)); inputs.push_back(InputEventKey::create_reference(Key::LEFT | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::HOME)); default_builtin_cache.insert("ui_text_caret_line_start.macos", inputs); inputs = List<Ref<InputEvent>>(); @@ -645,6 +646,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::E | KeyModifierMask::CTRL)); inputs.push_back(InputEventKey::create_reference(Key::RIGHT | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::END)); default_builtin_cache.insert("ui_text_caret_line_end.macos", inputs); // Text Caret Movement Page Up/Down @@ -665,6 +667,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::UP | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::HOME | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_caret_document_start.macos", inputs); inputs = List<Ref<InputEvent>>(); @@ -673,6 +676,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() { inputs = List<Ref<InputEvent>>(); inputs.push_back(InputEventKey::create_reference(Key::DOWN | KeyModifierMask::CMD_OR_CTRL)); + inputs.push_back(InputEventKey::create_reference(Key::END | KeyModifierMask::CMD_OR_CTRL)); default_builtin_cache.insert("ui_text_caret_document_end.macos", inputs); // Text Caret Addition Below/Above diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 5df67b1103..2f3fe4deeb 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -339,6 +339,8 @@ String DirAccess::get_full_path(const String &p_path, AccessType p_access) { } Error DirAccess::copy(const String &p_from, const String &p_to, int p_chmod_flags) { + ERR_FAIL_COND_V_MSG(p_from == p_to, ERR_INVALID_PARAMETER, "Source and destination path are equal."); + //printf("copy %s -> %s\n",p_from.ascii().get_data(),p_to.ascii().get_data()); Error err; { diff --git a/core/io/image.cpp b/core/io/image.cpp index 5498b448d7..d0598e4dc6 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -300,10 +300,10 @@ int Image::get_format_block_size(Format p_format) { return 1; } -void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const { +void Image::_get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const { int w = width; int h = height; - int ofs = 0; + int64_t ofs = 0; int pixel_size = get_format_pixel_size(format); int pixel_rshift = get_format_pixel_rshift(format); @@ -315,7 +315,7 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt int bw = w % block != 0 ? w + (block - w % block) : w; int bh = h % block != 0 ? h + (block - h % block) : h; - int s = bw * bh; + int64_t s = bw * bh; s *= pixel_size; s >>= pixel_rshift; @@ -329,37 +329,30 @@ void Image::_get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_widt r_height = h; } -int Image::get_mipmap_offset(int p_mipmap) const { +int64_t Image::get_mipmap_offset(int p_mipmap) const { ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); return ofs; } -int Image::get_mipmap_byte_size(int p_mipmap) const { - ERR_FAIL_INDEX_V(p_mipmap, get_mipmap_count() + 1, -1); - - int ofs, w, h; - _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); - int ofs2; - _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); - return ofs2 - ofs; -} - -void Image::get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const { - int ofs, w, h; +void Image::get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const { + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); - int ofs2; + int64_t ofs2; _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w, h); r_ofs = ofs; r_size = ofs2 - ofs; } -void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const { - int ofs; +void Image::get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const { + int64_t ofs; _get_mipmap_offset_and_size(p_mipmap, ofs, w, h); - int ofs2, w2, h2; + int64_t ofs2; + int w2, h2; _get_mipmap_offset_and_size(p_mipmap + 1, ofs2, w2, h2); r_ofs = ofs; r_size = ofs2 - ofs; @@ -538,8 +531,8 @@ void Image::convert(Format p_new_format) { } } - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; new_img.get_mipmap_offset_and_size(mip, mip_offset, mip_size); memcpy(new_img.data.ptrw() + mip_offset, new_mip->data.ptr(), mip_size); @@ -555,8 +548,8 @@ void Image::convert(Format p_new_format) { int conversion_type = format | p_new_format << 8; for (int mip = 0; mip < mipmap_count; mip++) { - int mip_offset = 0; - int mip_size = 0; + int64_t mip_offset = 0; + int64_t mip_size = 0; int mip_width = 0; int mip_height = 0; get_mipmap_offset_size_and_dimensions(mip, mip_offset, mip_size, mip_width, mip_height); @@ -1151,7 +1144,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { if (i == 0) { // Read from the first mipmap that will be interpolated // (if both levels are the same, we will not interpolate, but at least we'll sample from the right level) - int offs; + int64_t offs; _get_mipmap_offset_and_size(mip1, offs, src_width, src_height); src_ptr = r_ptr + offs; } else if (!interpolate_mipmaps) { @@ -1159,7 +1152,7 @@ void Image::resize(int p_width, int p_height, Interpolation p_interpolation) { break; } else { // Switch to read from the second mipmap that will be interpolated - int offs; + int64_t offs; _get_mipmap_offset_and_size(mip2, offs, src_width, src_height); src_ptr = r_ptr + offs; // Switch to write to the second destination image @@ -1599,9 +1592,9 @@ void Image::flip_x() { } /// Get mipmap size and offset. -int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) { +int64_t Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps, int *r_mm_width, int *r_mm_height) { // Data offset in mipmaps (including the original texture). - int size = 0; + int64_t size = 0; int w = p_width; int h = p_height; @@ -1623,7 +1616,7 @@ int Image::_get_dst_image_size(int p_width, int p_height, Format p_format, int & int bw = w % block != 0 ? w + (block - w % block) : w; int bh = h % block != 0 ? h + (block - h % block) : h; - int s = bw * bh; + int64_t s = bw * bh; s *= pixsize; s >>= pixshift; @@ -1837,7 +1830,8 @@ Error Image::generate_mipmaps(bool p_renormalize) { int prev_w = width; for (int i = 1; i <= mmcount; i++) { - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(i, ofs, w, h); switch (format) { @@ -1993,7 +1987,8 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con uint8_t *base_ptr = data.ptrw(); for (int i = 1; i <= mmcount; i++) { - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(i, ofs, w, h); uint8_t *ptr = &base_ptr[ofs]; @@ -2102,21 +2097,6 @@ Error Image::generate_mipmap_roughness(RoughnessChannel p_roughness_channel, con _set_color_at_ofs(ptr, pixel_ofs, c); } } -#if 0 - { - int size = get_mipmap_byte_size(i); - print_line("size for mimpap " + itos(i) + ": " + itos(size)); - Vector<uint8_t> imgdata; - imgdata.resize(size); - - - uint8_t* wr = imgdata.ptrw(); - memcpy(wr.ptr(), ptr, size); - wr = uint8_t*(); - Ref<Image> im = Image::create_from_data(w, h, false, format, imgdata); - im->save_png("res://mipmap_" + itos(i) + ".png"); - } -#endif } return OK; @@ -2131,7 +2111,8 @@ void Image::clear_mipmaps() { return; } - int ofs, w, h; + int64_t ofs; + int w, h; _get_mipmap_offset_and_size(1, ofs, w, h); data.resize(ofs); @@ -2176,7 +2157,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum."); int mm = 0; - int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); data.resize(size); { @@ -2202,7 +2183,7 @@ void Image::initialize_data(int p_width, int p_height, bool p_use_mipmaps, Forma ERR_FAIL_INDEX_MSG(p_format, FORMAT_MAX, "The Image format specified (" + itos(p_format) + ") is out of range. See Image's Format enum."); int mm; - int size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); + int64_t size = _get_dst_image_size(p_width, p_height, p_format, mm, p_use_mipmaps ? -1 : 0); if (unlikely(p_data.size() != size)) { String description_mipmaps = get_format_name(p_format) + " "; @@ -2405,7 +2386,7 @@ bool Image::is_invisible() const { return false; } - int len = data.size(); + int64_t len = data.size(); if (len == 0) { return true; @@ -2445,7 +2426,7 @@ bool Image::is_invisible() const { } Image::AlphaMode Image::detect_alpha() const { - int len = data.size(); + int64_t len = data.size(); if (len == 0) { return ALPHA_NONE; @@ -2579,7 +2560,7 @@ Vector<uint8_t> Image::save_webp_to_buffer(const bool p_lossy, const float p_qua return save_webp_buffer_func(Ref<Image>((Image *)this), p_lossy, p_quality); } -int Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) { +int64_t Image::get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps) { int mm; return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmaps ? -1 : 0); } @@ -2597,7 +2578,7 @@ Size2i Image::get_image_mipmap_size(int p_width, int p_height, Format p_format, return ret; } -int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { +int64_t Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap) { if (p_mipmap <= 0) { return 0; } @@ -2605,7 +2586,7 @@ int Image::get_image_mipmap_offset(int p_width, int p_height, Format p_format, i return _get_dst_image_size(p_width, p_height, p_format, mm, p_mipmap - 1); } -int Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) { +int64_t Image::get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h) { if (p_mipmap <= 0) { r_w = p_width; r_h = p_height; @@ -3312,7 +3293,7 @@ uint8_t *Image::ptrw() { return data.ptrw(); } -int64_t Image::data_size() const { +int64_t Image::get_data_size() const { return data.size(); } @@ -3427,6 +3408,7 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("has_mipmaps"), &Image::has_mipmaps); ClassDB::bind_method(D_METHOD("get_format"), &Image::get_format); ClassDB::bind_method(D_METHOD("get_data"), &Image::get_data); + ClassDB::bind_method(D_METHOD("get_data_size"), &Image::get_data_size); ClassDB::bind_method(D_METHOD("convert", "format"), &Image::convert); @@ -3443,7 +3425,10 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("generate_mipmaps", "renormalize"), &Image::generate_mipmaps, DEFVAL(false)); ClassDB::bind_method(D_METHOD("clear_mipmaps"), &Image::clear_mipmaps); +#ifndef DISABLE_DEPRECATED ClassDB::bind_static_method("Image", D_METHOD("create", "width", "height", "use_mipmaps", "format"), &Image::create_empty); +#endif + ClassDB::bind_static_method("Image", D_METHOD("create_empty", "width", "height", "use_mipmaps", "format"), &Image::create_empty); ClassDB::bind_static_method("Image", D_METHOD("create_from_data", "width", "height", "use_mipmaps", "format", "data"), &Image::create_from_data); ClassDB::bind_method(D_METHOD("set_data", "width", "height", "use_mipmaps", "format", "data"), &Image::set_data); @@ -3638,9 +3623,10 @@ Ref<Image> Image::rgbe_to_srgb() { return new_image; } -Ref<Image> Image::get_image_from_mipmap(int p_mipamp) const { - int ofs, size, w, h; - get_mipmap_offset_size_and_dimensions(p_mipamp, ofs, size, w, h); +Ref<Image> Image::get_image_from_mipmap(int p_mipmap) const { + int64_t ofs, size; + int w, h; + get_mipmap_offset_size_and_dimensions(p_mipmap, ofs, size, w, h); Vector<uint8_t> new_data; new_data.resize(size); diff --git a/core/io/image.h b/core/io/image.h index daddfac59d..d55cc39dbb 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -195,9 +195,9 @@ private: data = p_image.data; } - _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data + _FORCE_INLINE_ void _get_mipmap_offset_and_size(int p_mipmap, int64_t &r_offset, int &r_width, int &r_height) const; //get where the mipmap begins in data - static int _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr); + static int64_t _get_dst_image_size(int p_width, int p_height, Format p_format, int &r_mipmaps, int p_mipmaps = -1, int *r_mm_width = nullptr, int *r_mm_height = nullptr); bool _can_modify(Format p_format) const; _FORCE_INLINE_ void _get_clipped_src_and_dest_rects(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest, Rect2i &r_clipped_src_rect, Rect2i &r_clipped_dest_rect) const; @@ -238,10 +238,12 @@ public: */ Format get_format() const; - int get_mipmap_byte_size(int p_mipmap) const; //get where the mipmap begins in data - int get_mipmap_offset(int p_mipmap) const; //get where the mipmap begins in data - void get_mipmap_offset_and_size(int p_mipmap, int &r_ofs, int &r_size) const; //get where the mipmap begins in data - void get_mipmap_offset_size_and_dimensions(int p_mipmap, int &r_ofs, int &r_size, int &w, int &h) const; //get where the mipmap begins in data + /** + * Get where the mipmap begins in data. + */ + int64_t get_mipmap_offset(int p_mipmap) const; + void get_mipmap_offset_and_size(int p_mipmap, int64_t &r_ofs, int64_t &r_size) const; + void get_mipmap_offset_size_and_dimensions(int p_mipmap, int64_t &r_ofs, int64_t &r_size, int &w, int &h) const; enum Image3DValidateError { VALIDATE_3D_OK, @@ -351,11 +353,11 @@ public: static int get_format_block_size(Format p_format); static void get_format_min_pixel_size(Format p_format, int &r_w, int &r_h); - static int get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false); + static int64_t get_image_data_size(int p_width, int p_height, Format p_format, bool p_mipmaps = false); static int get_image_required_mipmaps(int p_width, int p_height, Format p_format); static Size2i get_image_mipmap_size(int p_width, int p_height, Format p_format, int p_mipmap); - static int get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap); - static int get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h); + static int64_t get_image_mipmap_offset(int p_width, int p_height, Format p_format, int p_mipmap); + static int64_t get_image_mipmap_offset_and_dimensions(int p_width, int p_height, Format p_format, int p_mipmap, int &r_w, int &r_h); enum CompressMode { COMPRESS_S3TC, @@ -383,7 +385,7 @@ public: void srgb_to_linear(); void normal_map_to_xy(); Ref<Image> rgbe_to_srgb(); - Ref<Image> get_image_from_mipmap(int p_mipamp) const; + Ref<Image> get_image_from_mipmap(int p_mipmap) const; void bump_map_to_normal_map(float bump_scale = 1.0); void blit_rect(const Ref<Image> &p_src, const Rect2i &p_src_rect, const Point2i &p_dest); @@ -429,7 +431,7 @@ public: const uint8_t *ptr() const; uint8_t *ptrw(); - int64_t data_size() const; + int64_t get_data_size() const; void adjust_bcs(float p_brightness, float p_contrast, float p_saturation); diff --git a/core/io/logger.cpp b/core/io/logger.cpp index 1476b8ccac..a24277fe72 100644 --- a/core/io/logger.cpp +++ b/core/io/logger.cpp @@ -212,7 +212,7 @@ void RotatedFileLogger::logv(const char *p_format, va_list p_list, bool p_err) { // Strip ANSI escape codes (such as those inserted by `print_rich()`) // before writing to file, as text editors cannot display those // correctly. - file->store_string(strip_ansi_regex->sub(String(buf), "", true)); + file->store_string(strip_ansi_regex->sub(String::utf8(buf), "", true)); #else file->store_buffer((uint8_t *)buf, len); #endif // MODULE_REGEX_ENABLED diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index c0d18d0120..67469de5cc 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -1315,10 +1315,12 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo if (array.is_typed()) { Ref<Script> script = array.get_typed_script(); if (script.is_valid()) { - header |= HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT; + header |= p_full_objects ? HEADER_DATA_FIELD_TYPED_ARRAY_SCRIPT : HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME; } else if (array.get_typed_class_name() != StringName()) { header |= HEADER_DATA_FIELD_TYPED_ARRAY_CLASS_NAME; } else { + // No need to check `p_full_objects` since for `Variant::OBJECT` + // `array.get_typed_class_name()` should be non-empty. header |= HEADER_DATA_FIELD_TYPED_ARRAY_BUILTIN; } } @@ -1783,12 +1785,18 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo Variant variant = array.get_typed_script(); Ref<Script> script = variant; if (script.is_valid()) { - String path = script->get_path(); - ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for an array type."); - _encode_string(path, buf, r_len); + if (p_full_objects) { + String path = script->get_path(); + ERR_FAIL_COND_V_MSG(path.is_empty() || !path.begins_with("res://"), ERR_UNAVAILABLE, "Failed to encode a path to a custom script for an array type."); + _encode_string(path, buf, r_len); + } else { + _encode_string(EncodedObjectAsID::get_class_static(), buf, r_len); + } } else if (array.get_typed_class_name() != StringName()) { - _encode_string(array.get_typed_class_name(), buf, r_len); + _encode_string(p_full_objects ? array.get_typed_class_name().operator String() : EncodedObjectAsID::get_class_static(), buf, r_len); } else { + // No need to check `p_full_objects` since for `Variant::OBJECT` + // `array.get_typed_class_name()` should be non-empty. if (buf) { encode_uint32(array.get_typed_builtin(), buf); buf += 4; diff --git a/core/io/packet_peer_udp.cpp b/core/io/packet_peer_udp.cpp index 32030146bb..fae3de2a98 100644 --- a/core/io/packet_peer_udp.cpp +++ b/core/io/packet_peer_udp.cpp @@ -106,7 +106,7 @@ Error PacketPeerUDP::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { } uint32_t size = 0; - uint8_t ipv6[16]; + uint8_t ipv6[16] = {}; rb.read(ipv6, 16, true); packet_ip.set_ipv6(ipv6); rb.read((uint8_t *)&packet_port, 4, true); diff --git a/core/io/resource.cpp b/core/io/resource.cpp index 1ecfd8366d..432adb88da 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -40,8 +40,8 @@ #include <stdio.h> void Resource::emit_changed() { - if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { - // Let the connection happen on the main thread, later, since signals are not thread-safe. + if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) { + // Let the connection happen on the call queue, later, since signals are not thread-safe. call_deferred("emit_signal", CoreStringName(changed)); } else { emit_signal(CoreStringName(changed)); @@ -166,8 +166,8 @@ bool Resource::editor_can_reload_from_file() { } void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) { - if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { - // Let the check and connection happen on the main thread, later, since signals are not thread-safe. + if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) { + // Let the check and connection happen on the call queue, later, since signals are not thread-safe. callable_mp(this, &Resource::connect_changed).call_deferred(p_callable, p_flags); return; } @@ -177,8 +177,8 @@ void Resource::connect_changed(const Callable &p_callable, uint32_t p_flags) { } void Resource::disconnect_changed(const Callable &p_callable) { - if (ResourceLoader::is_within_load() && !Thread::is_main_thread()) { - // Let the check and disconnection happen on the main thread, later, since signals are not thread-safe. + if (ResourceLoader::is_within_load() && MessageQueue::get_main_singleton() != MessageQueue::get_singleton() && !MessageQueue::get_singleton()->is_flushing()) { + // Let the check and disconnection happen on the call queue, later, since signals are not thread-safe. callable_mp(this, &Resource::disconnect_changed).call_deferred(p_callable); return; } diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index ab460c5f4c..f71257fa76 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -749,44 +749,54 @@ Error ResourceLoaderBinary::load() { String t = get_unicode_string(); Ref<Resource> res; + Resource *r = nullptr; - if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) { - //use the existing one - Ref<Resource> cached = ResourceCache::get_ref(path); - if (cached->get_class() == t) { - cached->reset_state(); - res = cached; - } + MissingResource *missing_resource = nullptr; + + if (main) { + res = ResourceLoader::get_resource_ref_override(local_path); + r = res.ptr(); } + if (!r) { + if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) { + //use the existing one + Ref<Resource> cached = ResourceCache::get_ref(path); + if (cached->get_class() == t) { + cached->reset_state(); + res = cached; + } + } - MissingResource *missing_resource = nullptr; + if (res.is_null()) { + //did not replace + + Object *obj = ClassDB::instantiate(t); + if (!obj) { + if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { + //create a missing resource + missing_resource = memnew(MissingResource); + missing_resource->set_original_class(t); + missing_resource->set_recording_properties(true); + obj = missing_resource; + } else { + error = ERR_FILE_CORRUPT; + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + "."); + } + } - if (res.is_null()) { - //did not replace - - Object *obj = ClassDB::instantiate(t); - if (!obj) { - if (ResourceLoader::is_creating_missing_resources_if_class_unavailable_enabled()) { - //create a missing resource - missing_resource = memnew(MissingResource); - missing_resource->set_original_class(t); - missing_resource->set_recording_properties(true); - obj = missing_resource; - } else { + r = Object::cast_to<Resource>(obj); + if (!r) { + String obj_class = obj->get_class(); error = ERR_FILE_CORRUPT; - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource of unrecognized type in file: " + t + "."); + memdelete(obj); //bye + ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + "."); } - } - Resource *r = Object::cast_to<Resource>(obj); - if (!r) { - String obj_class = obj->get_class(); - error = ERR_FILE_CORRUPT; - memdelete(obj); //bye - ERR_FAIL_V_MSG(ERR_FILE_CORRUPT, local_path + ":Resource type in resource field not a resource, type is: " + obj_class + "."); + res = Ref<Resource>(r); } + } - res = Ref<Resource>(r); + if (r) { if (!path.is_empty()) { if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); // If got here because the resource with same path has different type, replace it. diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index fcf4a727ca..9e6f3ba314 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -118,9 +118,21 @@ Error ResourceFormatImporter::_get_path_and_type(const String &p_path, PathAndTy } #endif - if (r_path_and_type.path.is_empty() || r_path_and_type.type.is_empty()) { + if (r_path_and_type.type.is_empty()) { return ERR_FILE_CORRUPT; } + if (r_path_and_type.path.is_empty()) { + // Some importers may not write files to the .godot folder, so the path can be empty. + if (r_path_and_type.importer.is_empty()) { + return ERR_FILE_CORRUPT; + } + + // It's only invalid if the extension for the importer is not empty. + Ref<ResourceImporter> importer = get_importer_by_name(r_path_and_type.importer); + if (importer.is_null() || !importer->get_save_extension().is_empty()) { + return ERR_FILE_CORRUPT; + } + } return OK; } @@ -410,6 +422,7 @@ void ResourceFormatImporter::get_importers_for_extension(const String &p_extensi for (const String &F : local_exts) { if (p_extension.to_lower() == F) { r_importers->push_back(importers[i]); + break; } } } diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index c3c37aa89d..d606db620c 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -40,6 +40,7 @@ #include "core/string/print_string.h" #include "core/string/translation.h" #include "core/variant/variant_parser.h" +#include "servers/rendering_server.h" #ifdef DEBUG_LOAD_THREADED #define print_lt(m_text) print_line(m_text) @@ -272,6 +273,7 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin } load_paths_stack->resize(load_paths_stack->size() - 1); + res_ref_overrides.erase(load_nesting); load_nesting--; if (!res.is_null()) { @@ -302,37 +304,33 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { thread_load_mutex.unlock(); // Thread-safe either if it's the current thread or a brand new one. - CallQueue *mq_override = nullptr; + CallQueue *own_mq_override = nullptr; if (load_nesting == 0) { load_paths_stack = memnew(Vector<String>); - if (!load_task.dependent_path.is_empty()) { - load_paths_stack->push_back(load_task.dependent_path); - } if (!Thread::is_main_thread()) { - mq_override = memnew(CallQueue); - MessageQueue::set_thread_singleton_override(mq_override); + // Let the caller thread use its own, for added flexibility. Provide one otherwise. + if (MessageQueue::get_singleton() == MessageQueue::get_main_singleton()) { + own_mq_override = memnew(CallQueue); + MessageQueue::set_thread_singleton_override(own_mq_override); + } set_current_thread_safe_for_nodes(true); } - } else { - DEV_ASSERT(load_task.dependent_path.is_empty()); } // -- - if (!Thread::is_main_thread()) { - set_current_thread_safe_for_nodes(true); - } - - Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress); - if (mq_override) { - mq_override->flush(); + Error load_err = OK; + Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_err, load_task.use_sub_threads, &load_task.progress); + if (MessageQueue::get_singleton() != MessageQueue::get_main_singleton()) { + MessageQueue::get_singleton()->flush(); } thread_load_mutex.lock(); load_task.resource = res; - load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0 + load_task.progress = 1.0; // It was fully loaded at this point, so force progress to 1.0. + load_task.error = load_err; if (load_task.error != OK) { load_task.status = THREAD_LOAD_FAILED; } else { @@ -394,8 +392,9 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { thread_load_mutex.unlock(); if (load_nesting == 0) { - if (mq_override) { - memdelete(mq_override); + if (own_mq_override) { + MessageQueue::set_thread_singleton_override(nullptr); + memdelete(own_mq_override); } memdelete(load_paths_stack); } @@ -457,24 +456,23 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode) { String local_path = _validate_local_path(p_path); + bool ignoring_cache = p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP; + Ref<LoadToken> load_token; - bool must_not_register = false; ThreadLoadTask unregistered_load_task; // Once set, must be valid up to the call to do the load. ThreadLoadTask *load_task_ptr = nullptr; bool run_on_current_thread = false; { MutexLock thread_load_lock(thread_load_mutex); - if (thread_load_tasks.has(local_path)) { + if (!ignoring_cache && thread_load_tasks.has(local_path)) { load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token); - if (!load_token.is_valid()) { + if (load_token.is_valid()) { + return load_token; + } else { // The token is dying (reached 0 on another thread). // Ensure it's killed now so the path can be safely reused right away. thread_load_tasks[local_path].load_token->clear(); - } else { - if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { - return load_token; - } } } @@ -503,19 +501,19 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, } } - // If we want to ignore cache, but there's another task loading it, we can't add this one to the map and we also have to finish unconditionally synchronously. - must_not_register = thread_load_tasks.has(local_path) && p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE; - if (must_not_register) { + // Cache-ignoring tasks aren't registered in the map and so must finish within scope. + if (ignoring_cache) { load_token->local_path.clear(); unregistered_load_task = load_task; + load_task_ptr = &unregistered_load_task; } else { - thread_load_tasks[local_path] = load_task; + DEV_ASSERT(!thread_load_tasks.has(local_path)); + HashMap<String, ResourceLoader::ThreadLoadTask>::Iterator E = thread_load_tasks.insert(local_path, load_task); + load_task_ptr = &E->value; } - - load_task_ptr = must_not_register ? &unregistered_load_task : &thread_load_tasks[local_path]; } - run_on_current_thread = must_not_register || p_thread_mode == LOAD_THREAD_FROM_CURRENT; + run_on_current_thread = ignoring_cache || p_thread_mode == LOAD_THREAD_FROM_CURRENT; if (run_on_current_thread) { load_task_ptr->thread_id = Thread::get_caller_id(); @@ -526,7 +524,7 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, if (run_on_current_thread) { _thread_load_function(load_task_ptr); - if (must_not_register) { + if (ignoring_cache) { load_token->res_if_unregistered = load_task_ptr->resource; } } @@ -557,27 +555,44 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) { } ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) { - MutexLock thread_load_lock(thread_load_mutex); + bool ensure_progress = false; + ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS; + { + MutexLock thread_load_lock(thread_load_mutex); - if (!user_load_tokens.has(p_path)) { - print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); - return THREAD_LOAD_INVALID_RESOURCE; - } + if (!user_load_tokens.has(p_path)) { + print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected."); + return THREAD_LOAD_INVALID_RESOURCE; + } - String local_path = _validate_local_path(p_path); - if (!thread_load_tasks.has(local_path)) { + String local_path = _validate_local_path(p_path); + if (!thread_load_tasks.has(local_path)) { #ifdef DEV_ENABLED - CRASH_NOW(); + CRASH_NOW(); #endif - // On non-dev, be defensive and at least avoid crashing (at this point at least). - return THREAD_LOAD_INVALID_RESOURCE; + // On non-dev, be defensive and at least avoid crashing (at this point at least). + return THREAD_LOAD_INVALID_RESOURCE; + } + + ThreadLoadTask &load_task = thread_load_tasks[local_path]; + status = load_task.status; + if (r_progress) { + *r_progress = _dependency_get_progress(local_path); + } + + // Support userland polling in a loop on the main thread. + if (Thread::is_main_thread() && status == THREAD_LOAD_IN_PROGRESS) { + uint64_t frame = Engine::get_singleton()->get_process_frames(); + if (frame == load_task.last_progress_check_main_thread_frame) { + ensure_progress = true; + } else { + load_task.last_progress_check_main_thread_frame = frame; + } + } } - ThreadLoadTask &load_task = thread_load_tasks[local_path]; - ThreadLoadStatus status; - status = load_task.status; - if (r_progress) { - *r_progress = _dependency_get_progress(local_path); + if (ensure_progress) { + _ensure_load_progress(); } return status; @@ -608,6 +623,21 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e } return Ref<Resource>(); } + + // Support userland requesting on the main thread before the load is reported to be complete. + if (Thread::is_main_thread() && !load_token->local_path.is_empty()) { + const ThreadLoadTask &load_task = thread_load_tasks[load_token->local_path]; + while (load_task.status == THREAD_LOAD_IN_PROGRESS) { + thread_load_lock.~MutexLock(); + bool exit = !_ensure_load_progress(); + OS::get_singleton()->delay_usec(1000); + new (&thread_load_lock) MutexLock(thread_load_mutex); + if (exit) { + break; + } + } + } + res = _load_complete_inner(*load_token, r_error, thread_load_lock); if (load_token->unreference()) { memdelete(load_token); @@ -656,39 +686,50 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro return Ref<Resource>(); } - if (load_task.task_id != 0) { + bool loader_is_wtp = load_task.task_id != 0; + Error wtp_task_err = FAILED; + if (loader_is_wtp) { // Loading thread is in the worker pool. + load_task.awaited = true; thread_load_mutex.unlock(); - Error err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); - if (err == ERR_BUSY) { - // The WorkerThreadPool has reported that the current task wants to await on an older one. - // That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of - // resource loading that means that the task to wait for can be restarted here to break the - // cycle, with as much recursion into this process as needed. - // When the stack is eventually unrolled, the original load will have been notified to go on. - // CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's - // an ongoing load for that resource and wait for it again. This value forces a new load. - Ref<ResourceLoader::LoadToken> token = _load_start(load_task.local_path, load_task.type_hint, LOAD_THREAD_DISTRIBUTE, ResourceFormatLoader::CACHE_MODE_IGNORE); - Ref<Resource> resource = _load_complete(*token.ptr(), &err); - if (r_error) { - *r_error = err; + wtp_task_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id); + } + + if (load_task.status == THREAD_LOAD_IN_PROGRESS) { // If early errored, awaiting would deadlock. + if (loader_is_wtp) { + if (wtp_task_err == ERR_BUSY) { + // The WorkerThreadPool has reported that the current task wants to await on an older one. + // That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of + // resource loading that means that the task to wait for can be restarted here to break the + // cycle, with as much recursion into this process as needed. + // When the stack is eventually unrolled, the original load will have been notified to go on. + // CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's + // an ongoing load for that resource and wait for it again. This value forces a new load. + Ref<ResourceLoader::LoadToken> token = _load_start(load_task.local_path, load_task.type_hint, LOAD_THREAD_DISTRIBUTE, ResourceFormatLoader::CACHE_MODE_IGNORE); + Ref<Resource> resource = _load_complete(*token.ptr(), &wtp_task_err); + if (r_error) { + *r_error = wtp_task_err; + } + thread_load_mutex.lock(); + return resource; + } else { + DEV_ASSERT(wtp_task_err == OK); + thread_load_mutex.lock(); } - thread_load_mutex.lock(); - return resource; } else { - DEV_ASSERT(err == OK); - thread_load_mutex.lock(); - load_task.awaited = true; + // Loading thread is main or user thread. + if (!load_task.cond_var) { + load_task.cond_var = memnew(ConditionVariable); + } + do { + load_task.cond_var->wait(p_thread_load_lock); + DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count()); + } while (load_task.cond_var); } } else { - // Loading thread is main or user thread. - if (!load_task.cond_var) { - load_task.cond_var = memnew(ConditionVariable); + if (loader_is_wtp) { + thread_load_mutex.lock(); } - do { - load_task.cond_var->wait(p_thread_load_lock); - DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count()); - } while (load_task.cond_var); } } @@ -715,6 +756,51 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro } } +bool ResourceLoader::_ensure_load_progress() { + // Some servers may need a new engine iteration to allow the load to progress. + // Since the only known one is the rendering server (in single thread mode), let's keep it simple and just sync it. + // This may be refactored in the future to support other servers and have less coupling. + if (OS::get_singleton()->get_render_thread_mode() == OS::RENDER_SEPARATE_THREAD) { + return false; // Not needed. + } + RenderingServer::get_singleton()->sync(); + return true; +} + +Ref<Resource> ResourceLoader::ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type) { + ERR_FAIL_COND_V(load_nesting == 0, Ref<Resource>()); // It makes no sense to use this from nesting level 0. + const String &local_path = _validate_local_path(p_path); + HashMap<String, Ref<Resource>> &overrides = res_ref_overrides[load_nesting - 1]; + HashMap<String, Ref<Resource>>::Iterator E = overrides.find(local_path); + if (E) { + return E->value; + } else { + Object *obj = ClassDB::instantiate(p_res_type); + ERR_FAIL_NULL_V(obj, Ref<Resource>()); + Ref<Resource> res(obj); + if (!res.is_valid()) { + memdelete(obj); + ERR_FAIL_V(Ref<Resource>()); + } + overrides[local_path] = res; + return res; + } +} + +Ref<Resource> ResourceLoader::get_resource_ref_override(const String &p_path) { + DEV_ASSERT(p_path == _validate_local_path(p_path)); + HashMap<int, HashMap<String, Ref<Resource>>>::Iterator E = res_ref_overrides.find(load_nesting); + if (!E) { + return nullptr; + } + HashMap<String, Ref<Resource>>::Iterator F = E->value.find(p_path); + if (!F) { + return nullptr; + } + + return F->value; +} + bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) { String local_path = _validate_local_path(p_path); @@ -1207,6 +1293,7 @@ bool ResourceLoader::timestamp_on_load = false; thread_local int ResourceLoader::load_nesting = 0; thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0; thread_local Vector<String> *ResourceLoader::load_paths_stack; +thread_local HashMap<int, HashMap<String, Ref<Resource>>> ResourceLoader::res_ref_overrides; template <> thread_local uint32_t SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::count = 0; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 11abb4dc18..5f1831f0d9 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -170,10 +170,10 @@ private: LoadToken *load_token = nullptr; String local_path; String remapped_path; - String dependent_path; String type_hint; float progress = 0.0f; float max_reported_progress = 0.0f; + uint64_t last_progress_check_main_thread_frame = UINT64_MAX; ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS; ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE; Error error = OK; @@ -187,6 +187,7 @@ private: static thread_local int load_nesting; static thread_local WorkerThreadPool::TaskID caller_task_id; + static thread_local HashMap<int, HashMap<String, Ref<Resource>>> res_ref_overrides; // Outermost key is nesting level. static thread_local Vector<String> *load_paths_stack; // A pointer to avoid broken TLS implementations from double-running the destructor. static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex; static HashMap<String, ThreadLoadTask> thread_load_tasks; @@ -196,6 +197,8 @@ private: static float _dependency_get_progress(const String &p_path); + static bool _ensure_load_progress(); + public: static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE); static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr); @@ -272,6 +275,9 @@ public: static void set_create_missing_resources_if_class_unavailable(bool p_enable); _FORCE_INLINE_ static bool is_creating_missing_resources_if_class_unavailable_enabled() { return create_missing_resources_if_class_unavailable; } + static Ref<Resource> ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type); + static Ref<Resource> get_resource_ref_override(const String &p_path); + static bool is_cleaning_tasks(); static void initialize(); diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index f272407869..984bb1c9c1 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -122,6 +122,10 @@ AStarGrid2D::CellShape AStarGrid2D::get_cell_shape() const { } void AStarGrid2D::update() { + if (!dirty) { + return; + } + points.clear(); const int32_t end_x = region.get_end().x; diff --git a/core/math/aabb.h b/core/math/aabb.h index 9a74266ff7..cb358ca7ef 100644 --- a/core/math/aabb.h +++ b/core/math/aabb.h @@ -41,7 +41,7 @@ class Variant; -struct _NO_DISCARD_ AABB { +struct [[nodiscard]] AABB { Vector3 position; Vector3 size; diff --git a/core/math/basis.h b/core/math/basis.h index 918cbc18d4..5c1a5fbdda 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -34,7 +34,7 @@ #include "core/math/quaternion.h" #include "core/math/vector3.h" -struct _NO_DISCARD_ Basis { +struct [[nodiscard]] Basis { Vector3 rows[3] = { Vector3(1, 0, 0), Vector3(0, 1, 0), diff --git a/core/math/color.h b/core/math/color.h index 65d7377c1c..e17b8c9fd7 100644 --- a/core/math/color.h +++ b/core/math/color.h @@ -35,7 +35,7 @@ class String; -struct _NO_DISCARD_ Color { +struct [[nodiscard]] Color { union { struct { float r; diff --git a/core/math/face3.h b/core/math/face3.h index 3dd47d0226..519dcb6414 100644 --- a/core/math/face3.h +++ b/core/math/face3.h @@ -36,7 +36,7 @@ #include "core/math/transform_3d.h" #include "core/math/vector3.h" -struct _NO_DISCARD_ Face3 { +struct [[nodiscard]] Face3 { enum Side { SIDE_OVER, SIDE_UNDER, diff --git a/core/math/plane.h b/core/math/plane.h index 8159f25342..6529fea60a 100644 --- a/core/math/plane.h +++ b/core/math/plane.h @@ -35,7 +35,7 @@ class Variant; -struct _NO_DISCARD_ Plane { +struct [[nodiscard]] Plane { Vector3 normal; real_t d = 0; diff --git a/core/math/projection.h b/core/math/projection.h index f3ed9d7b1c..5af43561c0 100644 --- a/core/math/projection.h +++ b/core/math/projection.h @@ -43,7 +43,7 @@ struct Rect2; struct Transform3D; struct Vector2; -struct _NO_DISCARD_ Projection { +struct [[nodiscard]] Projection { enum Planes { PLANE_NEAR, PLANE_FAR, diff --git a/core/math/quaternion.h b/core/math/quaternion.h index 868a2916f5..655e55e0a2 100644 --- a/core/math/quaternion.h +++ b/core/math/quaternion.h @@ -35,7 +35,7 @@ #include "core/math/vector3.h" #include "core/string/ustring.h" -struct _NO_DISCARD_ Quaternion { +struct [[nodiscard]] Quaternion { union { struct { real_t x; diff --git a/core/math/rect2.h b/core/math/rect2.h index b4069ae86a..9cb341b689 100644 --- a/core/math/rect2.h +++ b/core/math/rect2.h @@ -38,7 +38,7 @@ class String; struct Rect2i; struct Transform2D; -struct _NO_DISCARD_ Rect2 { +struct [[nodiscard]] Rect2 { Point2 position; Size2 size; diff --git a/core/math/rect2i.h b/core/math/rect2i.h index a1338da0bb..5f3a3d54f5 100644 --- a/core/math/rect2i.h +++ b/core/math/rect2i.h @@ -37,7 +37,7 @@ class String; struct Rect2; -struct _NO_DISCARD_ Rect2i { +struct [[nodiscard]] Rect2i { Point2i position; Size2i size; diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h index 4ec2dc119c..476577508f 100644 --- a/core/math/transform_2d.h +++ b/core/math/transform_2d.h @@ -38,7 +38,7 @@ class String; -struct _NO_DISCARD_ Transform2D { +struct [[nodiscard]] Transform2D { // Warning #1: basis of Transform2D is stored differently from Basis. In terms of columns array, the basis matrix looks like "on paper": // M = (columns[0][0] columns[1][0]) // (columns[0][1] columns[1][1]) diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h index 7d89b86c75..b1de233445 100644 --- a/core/math/transform_3d.h +++ b/core/math/transform_3d.h @@ -36,7 +36,7 @@ #include "core/math/plane.h" #include "core/templates/vector.h" -struct _NO_DISCARD_ Transform3D { +struct [[nodiscard]] Transform3D { Basis basis; Vector3 origin; diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp index 7cfe880b5a..6a564b0ca7 100644 --- a/core/math/transform_interpolator.cpp +++ b/core/math/transform_interpolator.cpp @@ -33,44 +33,14 @@ #include "core/math/transform_2d.h" void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) { - // Extract parameters. - Vector2 p1 = p_prev.get_origin(); - Vector2 p2 = p_curr.get_origin(); - // Special case for physics interpolation, if flipping, don't interpolate basis. // If the determinant polarity changes, the handedness of the coordinate system changes. if (_sign(p_prev.determinant()) != _sign(p_curr.determinant())) { r_result.columns[0] = p_curr.columns[0]; r_result.columns[1] = p_curr.columns[1]; - r_result.set_origin(p1.lerp(p2, p_fraction)); + r_result.set_origin(p_prev.get_origin().lerp(p_curr.get_origin(), p_fraction)); return; } - real_t r1 = p_prev.get_rotation(); - real_t r2 = p_curr.get_rotation(); - - Size2 s1 = p_prev.get_scale(); - Size2 s2 = p_curr.get_scale(); - - // Slerp rotation. - Vector2 v1(Math::cos(r1), Math::sin(r1)); - Vector2 v2(Math::cos(r2), Math::sin(r2)); - - real_t dot = v1.dot(v2); - - dot = CLAMP(dot, -1, 1); - - Vector2 v; - - if (dot > 0.9995f) { - v = v1.lerp(v2, p_fraction).normalized(); // Linearly interpolate to avoid numerical precision issues. - } else { - real_t angle = p_fraction * Math::acos(dot); - Vector2 v3 = (v2 - v1 * dot).normalized(); - v = v1 * Math::cos(angle) + v3 * Math::sin(angle); - } - - // Construct matrix. - r_result = Transform2D(Math::atan2(v.y, v.x), p1.lerp(p2, p_fraction)); - r_result.scale_basis(s1.lerp(s2, p_fraction)); + r_result = p_prev.interpolate_with(p_curr, p_fraction); } diff --git a/core/math/vector2.h b/core/math/vector2.h index 8851942cdd..edb47db6fd 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -37,7 +37,7 @@ class String; struct Vector2i; -struct _NO_DISCARD_ Vector2 { +struct [[nodiscard]] Vector2 { static const int AXIS_COUNT = 2; enum Axis { diff --git a/core/math/vector2i.h b/core/math/vector2i.h index aca9ae8272..fff9b0a658 100644 --- a/core/math/vector2i.h +++ b/core/math/vector2i.h @@ -37,7 +37,7 @@ class String; struct Vector2; -struct _NO_DISCARD_ Vector2i { +struct [[nodiscard]] Vector2i { static const int AXIS_COUNT = 2; enum Axis { diff --git a/core/math/vector3.h b/core/math/vector3.h index 2313eb557a..14bc44c4e7 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -39,7 +39,7 @@ struct Basis; struct Vector2; struct Vector3i; -struct _NO_DISCARD_ Vector3 { +struct [[nodiscard]] Vector3 { static const int AXIS_COUNT = 3; enum Axis { diff --git a/core/math/vector3i.h b/core/math/vector3i.h index 035cfcf9e2..40d0700bf7 100644 --- a/core/math/vector3i.h +++ b/core/math/vector3i.h @@ -37,7 +37,7 @@ class String; struct Vector3; -struct _NO_DISCARD_ Vector3i { +struct [[nodiscard]] Vector3i { static const int AXIS_COUNT = 3; enum Axis { diff --git a/core/math/vector4.h b/core/math/vector4.h index f69b4752bb..8632f69f57 100644 --- a/core/math/vector4.h +++ b/core/math/vector4.h @@ -38,7 +38,7 @@ class String; struct Vector4i; -struct _NO_DISCARD_ Vector4 { +struct [[nodiscard]] Vector4 { static const int AXIS_COUNT = 4; enum Axis { diff --git a/core/math/vector4i.h b/core/math/vector4i.h index 8a9c580bc1..a9036d684a 100644 --- a/core/math/vector4i.h +++ b/core/math/vector4i.h @@ -37,7 +37,7 @@ class String; struct Vector4; -struct _NO_DISCARD_ Vector4i { +struct [[nodiscard]] Vector4i { static const int AXIS_COUNT = 4; enum Axis { diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index fe4345aa0d..ceeb04b8ea 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -76,6 +76,21 @@ class PlaceholderExtensionInstance { StringName class_name; HashMap<StringName, Variant> properties; + // Checks if a property is from a runtime class, and not a non-runtime base class. + bool is_runtime_property(const StringName &p_property_name) { + StringName current_class_name = class_name; + + while (ClassDB::is_class_runtime(current_class_name)) { + if (ClassDB::has_property(current_class_name, p_property_name, true)) { + return true; + } + + current_class_name = ClassDB::get_parent_class(current_class_name); + } + + return false; + } + public: PlaceholderExtensionInstance(const StringName &p_class_name) { class_name = p_class_name; @@ -83,27 +98,24 @@ public: ~PlaceholderExtensionInstance() {} - void set(const StringName &p_name, const Variant &p_value) { - bool is_default_valid = false; - Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); - - // If there's a default value, then we know it's a valid property. - if (is_default_valid) { + void set(const StringName &p_name, const Variant &p_value, bool &r_valid) { + r_valid = is_runtime_property(p_name); + if (r_valid) { properties[p_name] = p_value; } } - Variant get(const StringName &p_name) { + Variant get(const StringName &p_name, bool &r_valid) { const Variant *value = properties.getptr(p_name); Variant ret; if (value) { ret = *value; + r_valid = true; } else { - bool is_default_valid = false; - Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name, &is_default_valid); - if (is_default_valid) { - ret = default_value; + r_valid = is_runtime_property(p_name); + if (r_valid) { + ret = ClassDB::class_get_default_property_value(class_name, p_name); } } @@ -115,10 +127,10 @@ public: const StringName &name = *(StringName *)p_name; const Variant &value = *(const Variant *)p_value; - self->set(name, value); + bool valid = false; + self->set(name, value, valid); - // We have to return true so Godot doesn't try to call the real setter function. - return true; + return valid; } static GDExtensionBool placeholder_instance_get(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { @@ -126,10 +138,10 @@ public: const StringName &name = *(StringName *)p_name; Variant *value = (Variant *)r_ret; - *value = self->get(name); + bool valid = false; + *value = self->get(name, valid); - // We have to return true so Godot doesn't try to call the real getter function. - return true; + return valid; } static const GDExtensionPropertyInfo *placeholder_instance_get_property_list(GDExtensionClassInstancePtr p_instance, uint32_t *r_count) { @@ -172,9 +184,9 @@ public: static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata) { ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata; - // Find the closest native parent. + // Find the closest native parent, that isn't a runtime class. ClassDB::ClassInfo *native_parent = ti->inherits_ptr; - while (native_parent->gdextension) { + while (native_parent->gdextension || native_parent->is_runtime) { native_parent = native_parent->inherits_ptr; } ERR_FAIL_NULL_V(native_parent->creation_func, nullptr); @@ -671,6 +683,21 @@ bool ClassDB::can_instantiate(const StringName &p_class) { return (!ti->disabled && ti->creation_func != nullptr && !(ti->gdextension && !ti->gdextension->create_instance)); } +bool ClassDB::is_abstract(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + if (!ti) { + if (!ScriptServer::is_global_class(p_class)) { + ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'."); + } + String path = ScriptServer::get_global_class_path(p_class); + Ref<Script> scr = ResourceLoader::load(path); + return scr.is_valid() && scr->is_valid() && scr->is_abstract(); + } + return ti->creation_func == nullptr && (!ti->gdextension || ti->gdextension->create_instance == nullptr); +} + bool ClassDB::is_virtual(const StringName &p_class) { OBJTYPE_RLOCK; @@ -1952,6 +1979,14 @@ bool ClassDB::is_class_reloadable(const StringName &p_class) { return ti->reloadable; } +bool ClassDB::is_class_runtime(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'."); + return ti->is_runtime; +} + void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) { if (resource_base_extensions.has(p_extension)) { return; @@ -2063,6 +2098,11 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) { ClassInfo *parent = classes.getptr(p_extension->parent_class_name); +#ifdef TOOLS_ENABLED + // @todo This is a limitation of the current implementation, but it should be possible to remove. + ERR_FAIL_COND_MSG(p_extension->is_runtime && parent->gdextension && !parent->is_runtime, "Extension runtime class " + String(p_extension->class_name) + " cannot descend from " + parent->name + " which isn't also a runtime class"); +#endif + ClassInfo c; c.api = p_extension->editor_class ? API_EDITOR_EXTENSION : API_EXTENSION; c.gdextension = p_extension; diff --git a/core/object/class_db.h b/core/object/class_db.h index 37a864c109..228b82b588 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -287,6 +287,7 @@ public: static bool class_exists(const StringName &p_class); static bool is_parent_class(const StringName &p_class, const StringName &p_inherits); static bool can_instantiate(const StringName &p_class); + static bool is_abstract(const StringName &p_class); static bool is_virtual(const StringName &p_class); static Object *instantiate(const StringName &p_class); static Object *instantiate_no_placeholders(const StringName &p_class); @@ -460,6 +461,7 @@ public: static bool is_class_exposed(const StringName &p_class); static bool is_class_reloadable(const StringName &p_class); + static bool is_class_runtime(const StringName &p_class); static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class); static void get_resource_base_extensions(List<String> *p_extensions); diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index 762bab75e7..4b0b1ce63d 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -481,10 +481,7 @@ CallQueue::~CallQueue() { if (!allocator_is_custom) { memdelete(allocator); } - // This is done here to avoid a circular dependency between the safety checks and the thread singleton pointer. - if (this == MessageQueue::thread_singleton) { - MessageQueue::thread_singleton = nullptr; - } + DEV_ASSERT(!is_current_thread_override); } ////////////////////// @@ -493,7 +490,6 @@ CallQueue *MessageQueue::main_singleton = nullptr; thread_local CallQueue *MessageQueue::thread_singleton = nullptr; void MessageQueue::set_thread_singleton_override(CallQueue *p_thread_singleton) { - DEV_ASSERT(p_thread_singleton); // To unset the thread singleton, don't call this with nullptr, but just memfree() it. #ifdef DEV_ENABLED if (thread_singleton) { thread_singleton->is_current_thread_override = false; diff --git a/core/object/object.cpp b/core/object/object.cpp index 303624e6d7..e4d1a8fc9a 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -236,20 +236,12 @@ void Object::set(const StringName &p_name, const Variant &p_value, bool *r_valid } if (_extension && _extension->set) { -// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it. -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wignored-qualifiers" -#endif - if (_extension->set(_extension_instance, (const GDExtensionStringNamePtr)&p_name, (const GDExtensionVariantPtr)&p_value)) { + if (_extension->set(_extension_instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionConstVariantPtr)&p_value)) { if (r_valid) { *r_valid = true; } return; } -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif } // Try built-in setter. @@ -323,21 +315,12 @@ Variant Object::get(const StringName &p_name, bool *r_valid) const { } } if (_extension && _extension->get) { -// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it. -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wignored-qualifiers" -#endif - - if (_extension->get(_extension_instance, (const GDExtensionStringNamePtr)&p_name, (GDExtensionVariantPtr)&ret)) { + if (_extension->get(_extension_instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionVariantPtr)&ret)) { if (r_valid) { *r_valid = true; } return ret; } -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif } // Try built-in getter. @@ -575,19 +558,11 @@ bool Object::property_can_revert(const StringName &p_name) const { } } -// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it. -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wignored-qualifiers" -#endif if (_extension && _extension->property_can_revert) { - if (_extension->property_can_revert(_extension_instance, (const GDExtensionStringNamePtr)&p_name)) { + if (_extension->property_can_revert(_extension_instance, (GDExtensionConstStringNamePtr)&p_name)) { return true; } } -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif return _property_can_revertv(p_name); } @@ -601,19 +576,11 @@ Variant Object::property_get_revert(const StringName &p_name) const { } } -// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it. -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wignored-qualifiers" -#endif if (_extension && _extension->property_get_revert) { - if (_extension->property_get_revert(_extension_instance, (const GDExtensionStringNamePtr)&p_name, (GDExtensionVariantPtr)&ret)) { + if (_extension->property_get_revert(_extension_instance, (GDExtensionConstStringNamePtr)&p_name, (GDExtensionVariantPtr)&ret)) { return ret; } } -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif if (_property_get_revertv(p_name, ret)) { return ret; @@ -796,7 +763,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_ } if (is_ref_counted()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - ERR_FAIL_V_MSG(Variant(), "Can't 'free' a reference."); + ERR_FAIL_V_MSG(Variant(), "Can't free a RefCounted object."); } if (_lock_index.get() > 1) { @@ -923,6 +890,7 @@ void Object::notification(int p_notification, bool p_reversed) { } String Object::to_string() { + // Keep this method in sync with `Node::to_string`. if (script_instance) { bool valid; String ret = script_instance->to_string(&valid); diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index eb7d560a5d..0b528e908a 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -697,7 +697,13 @@ bool PlaceHolderScriptInstance::has_method(const StringName &p_method) const { } if (script.is_valid()) { - return script->has_method(p_method); + Ref<Script> scr = script; + while (scr.is_valid()) { + if (scr->has_method(p_method)) { + return true; + } + scr = scr->get_base_script(); + } } return false; } diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp index a18ef8d4d7..7b643e4637 100644 --- a/core/object/script_language_extension.cpp +++ b/core/object/script_language_extension.cpp @@ -132,6 +132,7 @@ void ScriptLanguageExtension::_bind_methods() { GDVIRTUAL_BIND(_debug_get_stack_level_line, "level"); GDVIRTUAL_BIND(_debug_get_stack_level_function, "level"); + GDVIRTUAL_BIND(_debug_get_stack_level_source, "level"); GDVIRTUAL_BIND(_debug_get_stack_level_locals, "level", "max_subitems", "max_depth"); GDVIRTUAL_BIND(_debug_get_stack_level_members, "level", "max_subitems", "max_depth"); GDVIRTUAL_BIND(_debug_get_stack_level_instance, "level"); diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index 8fd26c3d2c..c9344f5799 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -646,7 +646,7 @@ public: virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override { int ret = 0; - GDVIRTUAL_REQUIRED_CALL(_profiling_get_accumulated_data, p_info_arr, p_info_max, ret); + GDVIRTUAL_REQUIRED_CALL(_profiling_get_frame_data, p_info_arr, p_info_max, ret); return ret; } diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index 0f7884305a..4d67cd930e 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -159,11 +159,10 @@ void UndoRedo::add_do_method(const Callable &p_callable) { do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object)); } do_op.type = Operation::TYPE_METHOD; - // There's no `get_method()` for custom callables, so use `operator String()` instead. - if (p_callable.is_custom()) { + do_op.name = p_callable.get_method(); + if (do_op.name == StringName()) { + // There's no `get_method()` for custom callables, so use `operator String()` instead. do_op.name = static_cast<String>(p_callable); - } else { - do_op.name = p_callable.get_method(); } actions.write[current_action + 1].do_ops.push_back(do_op); @@ -191,11 +190,10 @@ void UndoRedo::add_undo_method(const Callable &p_callable) { } undo_op.type = Operation::TYPE_METHOD; undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends; - // There's no `get_method()` for custom callables, so use `operator String()` instead. - if (p_callable.is_custom()) { + undo_op.name = p_callable.get_method(); + if (undo_op.name == StringName()) { + // There's no `get_method()` for custom callables, so use `operator String()` instead. undo_op.name = static_cast<String>(p_callable); - } else { - undo_op.name = p_callable.get_method(); } actions.write[current_action + 1].undo_ops.push_back(undo_op); diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index 9c9e0fa899..a873bc1f09 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -33,7 +33,6 @@ #include "core/object/script_language.h" #include "core/os/os.h" #include "core/os/thread_safe.h" -#include "core/templates/command_queue_mt.h" WorkerThreadPool::Task *const WorkerThreadPool::ThreadData::YIELDING = (Task *)1; @@ -46,18 +45,23 @@ void WorkerThreadPool::Task::free_template_userdata() { WorkerThreadPool *WorkerThreadPool::singleton = nullptr; -thread_local CommandQueueMT *WorkerThreadPool::flushing_cmd_queue = nullptr; +#ifdef THREADS_ENABLED +thread_local uintptr_t WorkerThreadPool::unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES] = {}; +#endif void WorkerThreadPool::_process_task(Task *p_task) { #ifdef THREADS_ENABLED int pool_thread_index = thread_ids[Thread::get_caller_id()]; ThreadData &curr_thread = threads[pool_thread_index]; Task *prev_task = nullptr; // In case this is recursively called. + bool safe_for_nodes_backup = is_current_thread_safe_for_nodes(); + CallQueue *call_queue_backup = MessageQueue::get_singleton() != MessageQueue::get_main_singleton() ? MessageQueue::get_singleton() : nullptr; { - // Tasks must start with this unset. They are free to set-and-forget otherwise. + // Tasks must start with these at default values. They are free to set-and-forget otherwise. set_current_thread_safe_for_nodes(false); + MessageQueue::set_thread_singleton_override(nullptr); // Since the WorkerThreadPool is started before the script server, // its pre-created threads can't have ScriptServer::thread_enter() called on them early. // Therefore, we do it late at the first opportunity, so in case the task @@ -169,6 +173,7 @@ void WorkerThreadPool::_process_task(Task *p_task) { } set_current_thread_safe_for_nodes(safe_for_nodes_backup); + MessageQueue::set_thread_singleton_override(call_queue_backup); #endif } @@ -393,16 +398,17 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { task->waiting_user++; } - task_mutex.unlock(); - if (caller_pool_thread) { + task_mutex.unlock(); _wait_collaboratively(caller_pool_thread, task); + task_mutex.lock(); task->waiting_pool--; if (task->waiting_pool == 0 && task->waiting_user == 0) { tasks.erase(p_task_id); task_allocator.free(task); } } else { + task_mutex.unlock(); task->done_semaphore.wait(); task_mutex.lock(); task->waiting_user--; @@ -410,12 +416,40 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) { tasks.erase(p_task_id); task_allocator.free(task); } - task_mutex.unlock(); } + task_mutex.unlock(); return OK; } +void WorkerThreadPool::_lock_unlockable_mutexes() { +#ifdef THREADS_ENABLED + for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { + if (unlockable_mutexes[i]) { + if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) { + ((Mutex *)unlockable_mutexes[i])->lock(); + } else { + ((BinaryMutex *)(unlockable_mutexes[i] & ~1))->lock(); + } + } + } +#endif +} + +void WorkerThreadPool::_unlock_unlockable_mutexes() { +#ifdef THREADS_ENABLED + for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { + if (unlockable_mutexes[i]) { + if ((((uintptr_t)unlockable_mutexes[i]) & 1) == 0) { + ((Mutex *)unlockable_mutexes[i])->unlock(); + } else { + ((BinaryMutex *)(unlockable_mutexes[i] & ~1))->unlock(); + } + } + } +#endif +} + void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task) { // Keep processing tasks until the condition to stop waiting is met. @@ -423,6 +457,7 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T while (true) { Task *task_to_process = nullptr; + bool relock_unlockables = false; { MutexLock lock(task_mutex); bool was_signaled = p_caller_pool_thread->signaled; @@ -460,13 +495,9 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T if (!task_to_process) { p_caller_pool_thread->awaited_task = p_task; - if (flushing_cmd_queue) { - flushing_cmd_queue->unlock(); - } + _unlock_unlockable_mutexes(); + relock_unlockables = true; p_caller_pool_thread->cond_var.wait(lock); - if (flushing_cmd_queue) { - flushing_cmd_queue->lock(); - } DEV_ASSERT(exit_threads || p_caller_pool_thread->signaled || IS_WAIT_OVER); p_caller_pool_thread->awaited_task = nullptr; @@ -474,6 +505,10 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T } } + if (relock_unlockables) { + _lock_unlockable_mutexes(); + } + if (task_to_process) { _process_task(task_to_process); } @@ -600,13 +635,9 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { { Group *group = *groupp; - if (flushing_cmd_queue) { - flushing_cmd_queue->unlock(); - } + _unlock_unlockable_mutexes(); group->done_semaphore.wait(); - if (flushing_cmd_queue) { - flushing_cmd_queue->lock(); - } + _lock_unlockable_mutexes(); uint32_t max_users = group->tasks_used + 1; // Add 1 because the thread waiting for it is also user. Read before to avoid another thread freeing task after increment. uint32_t finished_users = group->finished.increment(); // fetch happens before inc, so increment later. @@ -630,16 +661,41 @@ int WorkerThreadPool::get_thread_index() { return singleton->thread_ids.has(tid) ? singleton->thread_ids[tid] : -1; } -void WorkerThreadPool::thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue) { - ERR_FAIL_COND(flushing_cmd_queue != nullptr); - flushing_cmd_queue = p_queue; +#ifdef THREADS_ENABLED +uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(Mutex *p_mutex) { + return _thread_enter_unlock_allowance_zone(p_mutex, false); +} + +uint32_t WorkerThreadPool::thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex) { + return _thread_enter_unlock_allowance_zone(p_mutex, true); } -void WorkerThreadPool::thread_exit_command_queue_mt_flush() { - ERR_FAIL_NULL(flushing_cmd_queue); - flushing_cmd_queue = nullptr; +uint32_t WorkerThreadPool::_thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary) { + for (uint32_t i = 0; i < MAX_UNLOCKABLE_MUTEXES; i++) { + if (unlikely((unlockable_mutexes[i] & ~1) == (uintptr_t)p_mutex)) { + // Already registered in the current thread. + return UINT32_MAX; + } + if (!unlockable_mutexes[i]) { + unlockable_mutexes[i] = (uintptr_t)p_mutex; + if (p_is_binary) { + unlockable_mutexes[i] |= 1; + } + return i; + } + } + ERR_FAIL_V_MSG(UINT32_MAX, "No more unlockable mutex slots available. Engine bug."); } +void WorkerThreadPool::thread_exit_unlock_allowance_zone(uint32_t p_zone_id) { + if (p_zone_id == UINT32_MAX) { + return; + } + DEV_ASSERT(unlockable_mutexes[p_zone_id]); + unlockable_mutexes[p_zone_id] = 0; +} +#endif + void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) { ERR_FAIL_COND(threads.size() > 0); if (p_thread_count < 0) { diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index a9cf260a0f..8774143abf 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -41,8 +41,6 @@ #include "core/templates/rid.h" #include "core/templates/safe_refcount.h" -class CommandQueueMT; - class WorkerThreadPool : public Object { GDCLASS(WorkerThreadPool, Object) public: @@ -163,7 +161,10 @@ private: static WorkerThreadPool *singleton; - static thread_local CommandQueueMT *flushing_cmd_queue; +#ifdef THREADS_ENABLED + static const uint32_t MAX_UNLOCKABLE_MUTEXES = 2; + static thread_local uintptr_t unlockable_mutexes[MAX_UNLOCKABLE_MUTEXES]; +#endif TaskID _add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description); GroupID _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); @@ -190,6 +191,13 @@ private: void _wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task); +#ifdef THREADS_ENABLED + static uint32_t _thread_enter_unlock_allowance_zone(void *p_mutex, bool p_is_binary); +#endif + + void _lock_unlockable_mutexes(); + void _unlock_unlockable_mutexes(); + protected: static void _bind_methods(); @@ -232,8 +240,14 @@ public: static WorkerThreadPool *get_singleton() { return singleton; } static int get_thread_index(); - static void thread_enter_command_queue_mt_flush(CommandQueueMT *p_queue); - static void thread_exit_command_queue_mt_flush(); +#ifdef THREADS_ENABLED + static uint32_t thread_enter_unlock_allowance_zone(Mutex *p_mutex); + static uint32_t thread_enter_unlock_allowance_zone(BinaryMutex *p_mutex); + static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id); +#else + static uint32_t thread_enter_unlock_allowance_zone(void *p_mutex) { return UINT32_MAX; } + static void thread_exit_unlock_allowance_zone(uint32_t p_zone_id) {} +#endif void init(int p_thread_count = -1, float p_low_priority_task_ratio = 0.3); void finish(); diff --git a/core/os/midi_driver.cpp b/core/os/midi_driver.cpp index 6870c84b49..6c748b1498 100644 --- a/core/os/midi_driver.cpp +++ b/core/os/midi_driver.cpp @@ -38,88 +38,167 @@ MIDIDriver *MIDIDriver::get_singleton() { return singleton; } -void MIDIDriver::set_singleton() { +MIDIDriver::MIDIDriver() { singleton = this; } -void MIDIDriver::receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length) { - Ref<InputEventMIDI> event; - event.instantiate(); - event->set_device(device_index); - uint32_t param_position = 1; - - if (length >= 1) { - if (data[0] >= 0xF0) { - // channel does not apply to system common messages - event->set_channel(0); - event->set_message(MIDIMessage(data[0])); - last_received_message = data[0]; - } else if ((data[0] & 0x80) == 0x00) { - // running status - event->set_channel(last_received_message & 0xF); - event->set_message(MIDIMessage(last_received_message >> 4)); - param_position = 0; +MIDIDriver::MessageCategory MIDIDriver::Parser::category(uint8_t p_midi_fragment) { + if (p_midi_fragment >= 0xf8) { + return MessageCategory::RealTime; + } else if (p_midi_fragment >= 0xf0) { + // System Exclusive begin/end are specified as System Common Category + // messages, but we separate them here and give them their own categories + // as their behavior is significantly different. + if (p_midi_fragment == 0xf0) { + return MessageCategory::SysExBegin; + } else if (p_midi_fragment == 0xf7) { + return MessageCategory::SysExEnd; + } + return MessageCategory::SystemCommon; + } else if (p_midi_fragment >= 0x80) { + return MessageCategory::Voice; + } + return MessageCategory::Data; +} + +MIDIMessage MIDIDriver::Parser::status_to_msg_enum(uint8_t p_status_byte) { + if (p_status_byte & 0x80) { + if (p_status_byte < 0xf0) { + return MIDIMessage(p_status_byte >> 4); } else { - event->set_channel(data[0] & 0xF); - event->set_message(MIDIMessage(data[0] >> 4)); - param_position = 1; - last_received_message = data[0]; + return MIDIMessage(p_status_byte); } } + return MIDIMessage::NONE; +} - switch (event->get_message()) { - case MIDIMessage::AFTERTOUCH: - if (length >= 2 + param_position) { - event->set_pitch(data[param_position]); - event->set_pressure(data[param_position + 1]); - } - break; +size_t MIDIDriver::Parser::expected_data(uint8_t p_status_byte) { + return expected_data(status_to_msg_enum(p_status_byte)); +} +size_t MIDIDriver::Parser::expected_data(MIDIMessage p_msg_type) { + switch (p_msg_type) { + case MIDIMessage::NOTE_OFF: + case MIDIMessage::NOTE_ON: + case MIDIMessage::AFTERTOUCH: case MIDIMessage::CONTROL_CHANGE: - if (length >= 2 + param_position) { - event->set_controller_number(data[param_position]); - event->set_controller_value(data[param_position + 1]); - } - break; + case MIDIMessage::PITCH_BEND: + case MIDIMessage::SONG_POSITION_POINTER: + return 2; + case MIDIMessage::PROGRAM_CHANGE: + case MIDIMessage::CHANNEL_PRESSURE: + case MIDIMessage::QUARTER_FRAME: + case MIDIMessage::SONG_SELECT: + return 1; + default: + return 0; + } +} - case MIDIMessage::NOTE_ON: +uint8_t MIDIDriver::Parser::channel(uint8_t p_status_byte) { + if (category(p_status_byte) == MessageCategory::Voice) { + return p_status_byte & 0x0f; + } + return 0; +} + +void MIDIDriver::send_event(int p_device_index, uint8_t p_status, + const uint8_t *p_data, size_t p_data_len) { + const MIDIMessage msg = Parser::status_to_msg_enum(p_status); + ERR_FAIL_COND(p_data_len < Parser::expected_data(msg)); + + Ref<InputEventMIDI> event; + event.instantiate(); + event->set_device(p_device_index); + event->set_channel(Parser::channel(p_status)); + event->set_message(msg); + switch (msg) { case MIDIMessage::NOTE_OFF: - if (length >= 2 + param_position) { - event->set_pitch(data[param_position]); - event->set_velocity(data[param_position + 1]); - } + case MIDIMessage::NOTE_ON: + event->set_pitch(p_data[0]); + event->set_velocity(p_data[1]); break; - - case MIDIMessage::PITCH_BEND: - if (length >= 2 + param_position) { - event->set_pitch((data[param_position + 1] << 7) | data[param_position]); - } + case MIDIMessage::AFTERTOUCH: + event->set_pitch(p_data[0]); + event->set_pressure(p_data[1]); + break; + case MIDIMessage::CONTROL_CHANGE: + event->set_controller_number(p_data[0]); + event->set_controller_value(p_data[1]); break; - case MIDIMessage::PROGRAM_CHANGE: - if (length >= 1 + param_position) { - event->set_instrument(data[param_position]); - } + event->set_instrument(p_data[0]); break; - case MIDIMessage::CHANNEL_PRESSURE: - if (length >= 1 + param_position) { - event->set_pressure(data[param_position]); - } + event->set_pressure(p_data[0]); + break; + case MIDIMessage::PITCH_BEND: + event->set_pitch((p_data[1] << 7) | p_data[0]); break; + // QUARTER_FRAME, SONG_POSITION_POINTER, and SONG_SELECT not yet implemented. default: break; } - - Input *id = Input::get_singleton(); - id->parse_input_event(event); + Input::get_singleton()->parse_input_event(event); } -PackedStringArray MIDIDriver::get_connected_inputs() { - PackedStringArray list; - return list; +void MIDIDriver::Parser::parse_fragment(uint8_t p_fragment) { + switch (category(p_fragment)) { + case MessageCategory::RealTime: + // Real-Time messages are single byte messages that can + // occur at any point and do not interrupt other messages. + // We pass them straight through. + MIDIDriver::send_event(device_index, p_fragment); + break; + + case MessageCategory::SysExBegin: + status_byte = p_fragment; + skipping_sys_ex = true; + break; + + case MessageCategory::SysExEnd: + status_byte = 0; + skipping_sys_ex = false; + break; + + case MessageCategory::Voice: + case MessageCategory::SystemCommon: + skipping_sys_ex = false; // If we were in SysEx, assume it was aborted. + received_data_len = 0; + status_byte = 0; + ERR_FAIL_COND(expected_data(p_fragment) > DATA_BUFFER_SIZE); + if (expected_data(p_fragment) == 0) { + // No data bytes needed, post it now. + MIDIDriver::send_event(device_index, p_fragment); + } else { + status_byte = p_fragment; + } + break; + + case MessageCategory::Data: + // We don't currently process SysEx messages, so ignore their data. + if (!skipping_sys_ex) { + const size_t expected = expected_data(status_byte); + if (received_data_len < expected) { + data_buffer[received_data_len] = p_fragment; + received_data_len++; + if (received_data_len == expected) { + MIDIDriver::send_event(device_index, status_byte, + data_buffer, expected); + received_data_len = 0; + // Voice messages can use 'running status', sending further + // messages without resending their status byte. + // For other messages types we clear the cached status byte. + if (category(status_byte) != MessageCategory::Voice) { + status_byte = 0; + } + } + } + } + break; + } } -MIDIDriver::MIDIDriver() { - set_singleton(); +PackedStringArray MIDIDriver::get_connected_inputs() const { + return connected_input_names; } diff --git a/core/os/midi_driver.h b/core/os/midi_driver.h index cad3d8189e..ddce63f9c8 100644 --- a/core/os/midi_driver.h +++ b/core/os/midi_driver.h @@ -42,19 +42,73 @@ class MIDIDriver { static MIDIDriver *singleton; static uint8_t last_received_message; +protected: + // Categories of message for parser logic. + enum class MessageCategory { + Data, + Voice, + SysExBegin, + SystemCommon, // excluding System Exclusive Begin/End + SysExEnd, + RealTime, + }; + + // Convert midi data to InputEventMIDI and send it to Input. + // p_data_len is the length of the buffer passed at p_data, this must be + // at least equal to the data required by the passed message type, but + // may be larger. Only the required data will be read. + static void send_event(int p_device_index, uint8_t p_status, + const uint8_t *p_data = nullptr, size_t p_data_len = 0); + + class Parser { + public: + Parser() = default; + Parser(int p_device_index) : + device_index{ p_device_index } {} + virtual ~Parser() = default; + + // Push a byte of MIDI stream. Any completed messages will be + // forwarded to MIDIDriver::send_event. + void parse_fragment(uint8_t p_fragment); + + static MessageCategory category(uint8_t p_midi_fragment); + + // If the byte is a Voice Message status byte return the contained + // channel number, otherwise zero. + static uint8_t channel(uint8_t p_status_byte); + + // If the byte is a status byte for a message with a fixed number of + // additional data bytes, return the number expected, otherwise zero. + static size_t expected_data(uint8_t p_status_byte); + static size_t expected_data(MIDIMessage p_msg_type); + + // If the fragment is a status byte return the message type + // represented, otherwise MIDIMessage::NONE. + static MIDIMessage status_to_msg_enum(uint8_t p_status_byte); + + private: + int device_index = 0; + + static constexpr size_t DATA_BUFFER_SIZE = 2; + + uint8_t status_byte = 0; + uint8_t data_buffer[DATA_BUFFER_SIZE] = { 0 }; + size_t received_data_len = 0; + bool skipping_sys_ex = false; + }; + + PackedStringArray connected_input_names; + public: static MIDIDriver *get_singleton(); - void set_singleton(); + + MIDIDriver(); + virtual ~MIDIDriver() = default; virtual Error open() = 0; virtual void close() = 0; - virtual PackedStringArray get_connected_inputs(); - - static void receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length); - - MIDIDriver(); - virtual ~MIDIDriver() {} + PackedStringArray get_connected_inputs() const; }; #endif // MIDI_DRIVER_H diff --git a/core/os/os.cpp b/core/os/os.cpp index fa7f23ded0..642de11a9f 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -247,7 +247,10 @@ String OS::get_safe_dir_name(const String &p_dir_name, bool p_allow_paths) const for (int i = 0; i < invalid_chars.size(); i++) { safe_dir_name = safe_dir_name.replace(invalid_chars[i], "-"); } - return safe_dir_name; + + // Trim trailing periods from the returned value as it's not valid for folder names on Windows. + // This check is still applied on non-Windows platforms so the returned value is consistent across platforms. + return safe_dir_name.rstrip("."); } // Path to data, config, cache, etc. OS-specific folders @@ -513,6 +516,10 @@ bool OS::has_feature(const String &p_feature) { if (p_feature == "threads") { return true; } +#else + if (p_feature == "nothreads") { + return true; + } #endif if (_check_internal_feature_support(p_feature)) { diff --git a/core/os/os.h b/core/os/os.h index 63cc6ed50e..91e0ce9379 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -328,8 +328,6 @@ public: virtual void benchmark_end_measure(const String &p_context, const String &p_what); virtual void benchmark_dump(); - virtual void process_and_drop_events() {} - virtual Error setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path); enum PreferredTextureFormat { diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 8a55e4de8f..c0a86e9fb7 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -444,8 +444,8 @@ void unregister_core_types() { unregister_global_constants(); - ClassDB::cleanup(); ResourceCache::clear(); + ClassDB::cleanup(); CoreStringNames::free(); StringName::cleanup(); diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 344fe42fa0..432016284a 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -752,10 +752,10 @@ StringName TranslationServer::tool_translate(const StringName &p_message, const if (tool_translation.is_valid()) { StringName r = tool_translation->get_message(p_message, p_context); if (r) { - return editor_pseudolocalization ? tool_pseudolocalize(r) : r; + return r; } } - return editor_pseudolocalization ? tool_pseudolocalize(p_message) : p_message; + return p_message; } StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { @@ -856,10 +856,6 @@ void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) { } } -void TranslationServer::set_editor_pseudolocalization(bool p_enabled) { - editor_pseudolocalization = p_enabled; -} - void TranslationServer::reload_pseudolocalization() { pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents"); pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels"); diff --git a/core/string/translation.h b/core/string/translation.h index 78d6721347..0a7eacc45f 100644 --- a/core/string/translation.h +++ b/core/string/translation.h @@ -94,7 +94,6 @@ class TranslationServer : public Object { bool pseudolocalization_fake_bidi_enabled = false; bool pseudolocalization_override_enabled = false; bool pseudolocalization_skip_placeholders_enabled = false; - bool editor_pseudolocalization = false; float expansion_ratio = 0.0; String pseudolocalization_prefix; String pseudolocalization_suffix; @@ -170,7 +169,6 @@ public: bool is_pseudolocalization_enabled() const; void set_pseudolocalization_enabled(bool p_enabled); - void set_editor_pseudolocalization(bool p_enabled); void reload_pseudolocalization(); String standardize_locale(const String &p_locale) const; diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp index 06fd4717d7..8e275505b0 100644 --- a/core/string/translation_po.cpp +++ b/core/string/translation_po.cpp @@ -140,43 +140,87 @@ int TranslationPO::_get_plural_index(int p_n) const { input_val.clear(); input_val.push_back(p_n); - Variant result; - for (int i = 0; i < equi_tests.size(); i++) { - Error err = expr->parse(equi_tests[i], input_name); - ERR_FAIL_COND_V_MSG(err != OK, 0, "Cannot parse expression. Error: " + expr->get_error_text()); + return _eq_test(equi_tests, 0); +} - result = expr->execute(input_val); - ERR_FAIL_COND_V_MSG(expr->has_execute_failed(), 0, "Cannot evaluate expression."); +int TranslationPO::_eq_test(const Ref<EQNode> &p_node, const Variant &p_result) const { + if (p_node.is_valid()) { + Error err = expr->parse(p_node->regex, input_name); + ERR_FAIL_COND_V_MSG(err != OK, 0, vformat("Cannot parse expression \"%s\". Error: %s", p_node->regex, expr->get_error_text())); - // Last expression. Variant result will either map to a bool or an integer, in both cases returning it will give the correct plural index. - if (i + 1 == equi_tests.size()) { - return result; - } + Variant result = expr->execute(input_val); + ERR_FAIL_COND_V_MSG(expr->has_execute_failed(), 0, vformat("Cannot evaluate expression \"%s\".", p_node->regex)); if (bool(result)) { - return i; + return _eq_test(p_node->left, result); + } else { + return _eq_test(p_node->right, result); } + } else { + return p_result; + } +} + +int TranslationPO::_find_unquoted(const String &p_src, char32_t p_chr) const { + const int len = p_src.length(); + if (len == 0) { + return -1; } - ERR_FAIL_V_MSG(0, "Unexpected. Function should have returned. Please report this bug."); + const char32_t *src = p_src.get_data(); + bool in_quote = false; + for (int i = 0; i < len; i++) { + if (in_quote) { + if (src[i] == ')') { + in_quote = false; + } + } else { + if (src[i] == '(') { + in_quote = true; + } else if (src[i] == p_chr) { + return i; + } + } + } + + return -1; } -void TranslationPO::_cache_plural_tests(const String &p_plural_rule) { +void TranslationPO::_cache_plural_tests(const String &p_plural_rule, Ref<EQNode> &p_node) { // Some examples of p_plural_rule passed in can have the form: // "n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5" (Arabic) // "n >= 2" (French) // When evaluating the last, especially careful with this one. // "n != 1" (English) - int first_ques_mark = p_plural_rule.find("?"); + + String rule = p_plural_rule; + if (rule.begins_with("(") && rule.ends_with(")")) { + int bcount = 0; + for (int i = 1; i < rule.length() - 1 && bcount >= 0; i++) { + if (rule[i] == '(') { + bcount++; + } else if (rule[i] == ')') { + bcount--; + } + } + if (bcount == 0) { + rule = rule.substr(1, rule.length() - 2); + } + } + + int first_ques_mark = _find_unquoted(rule, '?'); + int first_colon = _find_unquoted(rule, ':'); + if (first_ques_mark == -1) { - equi_tests.push_back(p_plural_rule.strip_edges()); + p_node->regex = rule.strip_edges(); return; } - String equi_test = p_plural_rule.substr(0, first_ques_mark).strip_edges(); - equi_tests.push_back(equi_test); + p_node->regex = rule.substr(0, first_ques_mark).strip_edges(); - String after_colon = p_plural_rule.substr(p_plural_rule.find(":") + 1, p_plural_rule.length()); - _cache_plural_tests(after_colon); + p_node->left.instantiate(); + _cache_plural_tests(rule.substr(first_ques_mark + 1, first_colon - first_ques_mark - 1).strip_edges(), p_node->left); + p_node->right.instantiate(); + _cache_plural_tests(rule.substr(first_colon + 1).strip_edges(), p_node->right); } void TranslationPO::set_plural_rule(const String &p_plural_rule) { @@ -188,12 +232,12 @@ void TranslationPO::set_plural_rule(const String &p_plural_rule) { int expression_start = p_plural_rule.find("=", first_semi_col) + 1; int second_semi_col = p_plural_rule.rfind(";"); - plural_rule = p_plural_rule.substr(expression_start, second_semi_col - expression_start); + plural_rule = p_plural_rule.substr(expression_start, second_semi_col - expression_start).strip_edges(); // Setup the cache to make evaluating plural rule faster later on. - plural_rule = plural_rule.replacen("(", ""); - plural_rule = plural_rule.replacen(")", ""); - _cache_plural_tests(plural_rule); + equi_tests.instantiate(); + _cache_plural_tests(plural_rule, equi_tests); + expr.instantiate(); input_name.push_back("n"); } diff --git a/core/string/translation_po.h b/core/string/translation_po.h index 73f9b33a87..ba820c6ee4 100644 --- a/core/string/translation_po.h +++ b/core/string/translation_po.h @@ -50,7 +50,17 @@ class TranslationPO : public Translation { String plural_rule; // Cache temporary variables related to _get_plural_index() to make it faster - Vector<String> equi_tests; + class EQNode : public RefCounted { + public: + String regex; + Ref<EQNode> left; + Ref<EQNode> right; + }; + Ref<EQNode> equi_tests; + + int _find_unquoted(const String &p_src, char32_t p_chr) const; + int _eq_test(const Ref<EQNode> &p_node, const Variant &p_result) const; + Vector<String> input_name; mutable Ref<Expression> expr; mutable Array input_val; @@ -59,7 +69,7 @@ class TranslationPO : public Translation { mutable int last_plural_n = -1; // Set it to an impossible value at the beginning. mutable int last_plural_mapped_index = 0; - void _cache_plural_tests(const String &p_plural_rule); + void _cache_plural_tests(const String &p_plural_rule, Ref<EQNode> &p_node); int _get_plural_index(int p_n) const; Vector<String> _get_message_list() const override; diff --git a/core/templates/command_queue_mt.h b/core/templates/command_queue_mt.h index 349404d75b..1e6c6e42a9 100644 --- a/core/templates/command_queue_mt.h +++ b/core/templates/command_queue_mt.h @@ -364,26 +364,30 @@ class CommandQueueMT { lock(); - WorkerThreadPool::thread_enter_command_queue_mt_flush(this); + uint32_t allowance_id = WorkerThreadPool::thread_enter_unlock_allowance_zone(&mutex); while (flush_read_ptr < command_mem.size()) { uint64_t size = *(uint64_t *)&command_mem[flush_read_ptr]; flush_read_ptr += 8; CommandBase *cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); cmd->call(); + + // Handle potential realloc due to the command and unlock allowance. + cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); + if (unlikely(cmd->sync)) { sync_head++; unlock(); // Give an opportunity to awaiters right away. sync_cond_var.notify_all(); lock(); + // Handle potential realloc happened during unlock. + cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); } - // If the command involved reallocating the buffer, the address may have changed. - cmd = reinterpret_cast<CommandBase *>(&command_mem[flush_read_ptr]); cmd->~CommandBase(); flush_read_ptr += size; } - WorkerThreadPool::thread_exit_command_queue_mt_flush(); + WorkerThreadPool::thread_exit_unlock_allowance_zone(allowance_id); command_mem.clear(); flush_read_ptr = 0; diff --git a/core/typedefs.h b/core/typedefs.h index 2b90a911cd..0de803293d 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -71,12 +71,7 @@ #endif #endif -// No discard allows the compiler to flag warnings if we don't use the return value of functions / classes -#ifndef _NO_DISCARD_ -#define _NO_DISCARD_ [[nodiscard]] -#endif - -// In some cases _NO_DISCARD_ will get false positives, +// In some cases [[nodiscard]] will get false positives, // we can prevent the warning in specific cases by preceding the call with a cast. #ifndef _ALLOW_DISCARD_ #define _ALLOW_DISCARD_ (void) diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 3685515db5..54cd1eda2f 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -235,7 +235,7 @@ void Array::assign(const Array &p_array) { for (int i = 0; i < size; i++) { const Variant &element = source[i]; if (element.get_type() != Variant::NIL && (element.get_type() != Variant::OBJECT || !typed.validate_object(element, "assign"))) { - ERR_FAIL_MSG(vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(element.get_type()), Variant::get_type_name(typed.type))); } } _p->array = p_array._p->array; @@ -258,11 +258,11 @@ void Array::assign(const Array &p_array) { continue; } if (!Variant::can_convert_strict(value->get_type(), typed.type)) { - ERR_FAIL_MSG("Unable to convert array index " + itos(i) + " from '" + Variant::get_type_name(value->get_type()) + "' to '" + Variant::get_type_name(typed.type) + "'."); + ERR_FAIL_MSG(vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } Callable::CallError ce; Variant::construct(typed.type, data[i], &value, 1, ce); - ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } } else if (Variant::can_convert_strict(source_typed.type, typed.type)) { // from primitives to different convertible primitives @@ -270,7 +270,7 @@ void Array::assign(const Array &p_array) { const Variant *value = source + i; Callable::CallError ce; Variant::construct(typed.type, data[i], &value, 1, ce); - ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %i from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); + ERR_FAIL_COND_MSG(ce.error, vformat(R"(Unable to convert array index %d from "%s" to "%s".)", i, Variant::get_type_name(value->get_type()), Variant::get_type_name(typed.type))); } } else { ERR_FAIL_MSG(vformat(R"(Cannot assign contents of "Array[%s]" to "Array[%s]".)", Variant::get_type_name(source_typed.type), Variant::get_type_name(typed.type))); diff --git a/core/variant/typed_array.h b/core/variant/typed_array.h index e00947ed1e..07bf8afa7b 100644 --- a/core/variant/typed_array.h +++ b/core/variant/typed_array.h @@ -46,10 +46,15 @@ public: _ref(p_array); } _FORCE_INLINE_ TypedArray(const Variant &p_variant) : - Array(Array(p_variant), Variant::OBJECT, T::get_class_static(), Variant()) { + TypedArray(Array(p_variant)) { } - _FORCE_INLINE_ TypedArray(const Array &p_array) : - Array(p_array, Variant::OBJECT, T::get_class_static(), Variant()) { + _FORCE_INLINE_ TypedArray(const Array &p_array) { + set_typed(Variant::OBJECT, T::get_class_static(), Variant()); + if (is_same_typed(p_array)) { + _ref(p_array); + } else { + assign(p_array); + } } _FORCE_INLINE_ TypedArray() { set_typed(Variant::OBJECT, T::get_class_static(), Variant()); @@ -78,10 +83,15 @@ struct VariantInternalAccessor<const TypedArray<T> &> { _ref(p_array); \ } \ _FORCE_INLINE_ TypedArray(const Variant &p_variant) : \ - Array(Array(p_variant), m_variant_type, StringName(), Variant()) { \ + TypedArray(Array(p_variant)) { \ } \ - _FORCE_INLINE_ TypedArray(const Array &p_array) : \ - Array(p_array, m_variant_type, StringName(), Variant()) { \ + _FORCE_INLINE_ TypedArray(const Array &p_array) { \ + set_typed(m_variant_type, StringName(), Variant()); \ + if (is_same_typed(p_array)) { \ + _ref(p_array); \ + } else { \ + assign(p_array); \ + } \ } \ _FORCE_INLINE_ TypedArray() { \ set_typed(m_variant_type, StringName(), Variant()); \ diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 30a8facd67..c1ef31c784 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -951,7 +951,7 @@ bool Variant::is_zero() const { return *reinterpret_cast<const ::RID *>(_data._mem) == ::RID(); } case OBJECT: { - return get_validated_object() == nullptr; + return _get_obj().obj == nullptr; } case CALLABLE: { return reinterpret_cast<const Callable *>(_data._mem)->is_null(); diff --git a/core/variant/variant.h b/core/variant/variant.h index f352af24da..1cb3580c01 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -857,7 +857,7 @@ String vformat(const String &p_text, const VarArgs... p_args) { bool error = false; String fmt = p_text.sprintf(args_array, &error); - ERR_FAIL_COND_V_MSG(error, String(), fmt); + ERR_FAIL_COND_V_MSG(error, String(), String("Formatting error in string \"") + p_text + "\": " + fmt + "."); return fmt; } diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 0157232d5e..5e402937cf 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -2265,6 +2265,7 @@ static void _register_variant_builtin_methods_misc() { bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant())); bind_method(Dictionary, make_read_only, sarray(), varray()); bind_method(Dictionary, is_read_only, sarray(), varray()); + bind_method(Dictionary, recursive_equal, sarray("dictionary", "recursion_count"), varray()); } static void _register_variant_builtin_methods_array() { diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h index 0b94d79a97..ac39a4135f 100644 --- a/core/variant/variant_op.h +++ b/core/variant/variant_op.h @@ -548,14 +548,14 @@ public: class OperatorEvaluatorEqualObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const ObjectID &a = VariantInternal::get_object_id(&p_left); - const ObjectID &b = VariantInternal::get_object_id(&p_right); + const Object *a = p_left.get_validated_object(); + const Object *b = p_right.get_validated_object(); *r_ret = a == b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const ObjectID &a = VariantInternal::get_object_id(left); - const ObjectID &b = VariantInternal::get_object_id(right); + const Object *a = left->get_validated_object(); + const Object *b = right->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -567,12 +567,12 @@ public: class OperatorEvaluatorEqualObjectNil { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const Object *a = p_left.operator Object *(); + const Object *a = p_left.get_validated_object(); *r_ret = a == nullptr; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const Object *a = left->operator Object *(); + const Object *a = left->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == nullptr; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -584,12 +584,12 @@ public: class OperatorEvaluatorEqualNilObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const Object *b = p_right.operator Object *(); + const Object *b = p_right.get_validated_object(); *r_ret = nullptr == b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const Object *b = right->operator Object *(); + const Object *b = right->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr == b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -619,14 +619,14 @@ public: class OperatorEvaluatorNotEqualObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const ObjectID &a = VariantInternal::get_object_id(&p_left); - const ObjectID &b = VariantInternal::get_object_id(&p_right); + Object *a = p_left.get_validated_object(); + Object *b = p_right.get_validated_object(); *r_ret = a != b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const ObjectID &a = VariantInternal::get_object_id(left); - const ObjectID &b = VariantInternal::get_object_id(right); + Object *a = left->get_validated_object(); + Object *b = right->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -638,12 +638,12 @@ public: class OperatorEvaluatorNotEqualObjectNil { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - Object *a = p_left.operator Object *(); + Object *a = p_left.get_validated_object(); *r_ret = a != nullptr; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - Object *a = left->operator Object *(); + Object *a = left->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != nullptr; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -655,12 +655,12 @@ public: class OperatorEvaluatorNotEqualNilObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - Object *b = p_right.operator Object *(); + Object *b = p_right.get_validated_object(); *r_ret = nullptr != b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - Object *b = right->operator Object *(); + Object *b = right->get_validated_object(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr != b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { |
