summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/config/project_settings.cpp7
-rw-r--r--core/core_bind.cpp91
-rw-r--r--core/core_bind.h19
-rw-r--r--core/crypto/crypto.cpp18
-rw-r--r--core/crypto/crypto.h20
-rw-r--r--core/debugger/remote_debugger.cpp3
-rw-r--r--core/extension/gdextension.cpp2
-rw-r--r--core/extension/gdextension_interface.cpp26
-rw-r--r--core/extension/gdextension_interface.h67
-rw-r--r--core/input/input.cpp21
-rw-r--r--core/input/input.h6
-rw-r--r--core/input/input_map.cpp4
-rw-r--r--core/io/dir_access.cpp2
-rw-r--r--core/io/image.cpp104
-rw-r--r--core/io/image.h24
-rw-r--r--core/io/logger.cpp2
-rw-r--r--core/io/marshalls.cpp18
-rw-r--r--core/io/packet_peer_udp.cpp2
-rw-r--r--core/io/resource.cpp12
-rw-r--r--core/io/resource_format_binary.cpp68
-rw-r--r--core/io/resource_importer.cpp15
-rw-r--r--core/io/resource_loader.cpp235
-rw-r--r--core/io/resource_loader.h8
-rw-r--r--core/math/a_star_grid_2d.cpp4
-rw-r--r--core/math/aabb.h2
-rw-r--r--core/math/basis.h2
-rw-r--r--core/math/color.h2
-rw-r--r--core/math/face3.h2
-rw-r--r--core/math/plane.h2
-rw-r--r--core/math/projection.h2
-rw-r--r--core/math/quaternion.h2
-rw-r--r--core/math/rect2.h2
-rw-r--r--core/math/rect2i.h2
-rw-r--r--core/math/transform_2d.h2
-rw-r--r--core/math/transform_3d.h2
-rw-r--r--core/math/transform_interpolator.cpp34
-rw-r--r--core/math/vector2.h2
-rw-r--r--core/math/vector2i.h2
-rw-r--r--core/math/vector3.h2
-rw-r--r--core/math/vector3i.h2
-rw-r--r--core/math/vector4.h2
-rw-r--r--core/math/vector4i.h2
-rw-r--r--core/object/class_db.cpp78
-rw-r--r--core/object/class_db.h2
-rw-r--r--core/object/message_queue.cpp6
-rw-r--r--core/object/object.cpp44
-rw-r--r--core/object/script_language.cpp8
-rw-r--r--core/object/script_language_extension.cpp1
-rw-r--r--core/object/script_language_extension.h2
-rw-r--r--core/object/undo_redo.cpp14
-rw-r--r--core/object/worker_thread_pool.cpp104
-rw-r--r--core/object/worker_thread_pool.h24
-rw-r--r--core/os/midi_driver.cpp199
-rw-r--r--core/os/midi_driver.h68
-rw-r--r--core/os/os.cpp9
-rw-r--r--core/os/os.h2
-rw-r--r--core/register_core_types.cpp2
-rw-r--r--core/string/translation.cpp8
-rw-r--r--core/string/translation.h2
-rw-r--r--core/string/translation_po.cpp90
-rw-r--r--core/string/translation_po.h14
-rw-r--r--core/templates/command_queue_mt.h12
-rw-r--r--core/typedefs.h7
-rw-r--r--core/variant/array.cpp8
-rw-r--r--core/variant/typed_array.h22
-rw-r--r--core/variant/variant.cpp2
-rw-r--r--core/variant/variant.h2
-rw-r--r--core/variant/variant_call.cpp1
-rw-r--r--core/variant/variant_op.h32
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) {