summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/SCsub25
-rw-r--r--core/config/engine.cpp39
-rw-r--r--core/config/engine.h10
-rw-r--r--core/config/project_settings.cpp49
-rw-r--r--core/config/project_settings.h1
-rw-r--r--core/core_bind.cpp137
-rw-r--r--core/core_bind.h41
-rw-r--r--core/core_builders.py1
-rw-r--r--core/core_constants.cpp2
-rw-r--r--core/crypto/SCsub19
-rw-r--r--core/crypto/crypto.cpp2
-rw-r--r--core/crypto/crypto.h2
-rw-r--r--core/debugger/local_debugger.cpp5
-rw-r--r--core/debugger/remote_debugger_peer.cpp4
-rw-r--r--core/doc_data.cpp3
-rw-r--r--core/doc_data.h326
-rw-r--r--core/extension/extension_api_dump.cpp357
-rw-r--r--core/extension/extension_api_dump.h1
-rw-r--r--core/extension/gdextension.cpp167
-rw-r--r--core/extension/gdextension.h29
-rw-r--r--core/extension/gdextension_interface.cpp597
-rw-r--r--core/extension/gdextension_interface.h1957
-rw-r--r--core/extension/gdextension_manager.cpp23
-rw-r--r--core/extension/gdextension_manager.h4
-rw-r--r--core/extension/make_interface_dumper.py35
-rw-r--r--core/input/godotcontrollerdb.txt2
-rw-r--r--core/input/input.cpp94
-rw-r--r--core/input/input.h15
-rw-r--r--core/input/input_event.cpp142
-rw-r--r--core/input/input_event.h26
-rw-r--r--core/input/input_map.cpp4
-rw-r--r--core/io/compression.cpp218
-rw-r--r--core/io/compression.h3
-rw-r--r--core/io/dir_access.h2
-rw-r--r--core/io/file_access.cpp6
-rw-r--r--core/io/file_access.h5
-rw-r--r--core/io/file_access_compressed.cpp8
-rw-r--r--core/io/file_access_memory.cpp4
-rw-r--r--core/io/file_access_network.cpp498
-rw-r--r--core/io/file_access_network.h167
-rw-r--r--core/io/file_access_pack.cpp9
-rw-r--r--core/io/file_access_pack.h5
-rw-r--r--core/io/http_client.cpp5
-rw-r--r--core/io/http_client_tcp.cpp1
-rw-r--r--core/io/image.cpp17
-rw-r--r--core/io/ip.cpp2
-rw-r--r--core/io/ip.h1
-rw-r--r--core/io/json.cpp8
-rw-r--r--core/io/packed_data_container.cpp11
-rw-r--r--core/io/packed_data_container.h1
-rw-r--r--core/io/pck_packer.cpp4
-rw-r--r--core/io/remote_filesystem_client.cpp329
-rw-r--r--core/io/remote_filesystem_client.h65
-rw-r--r--core/io/resource_format_binary.cpp56
-rw-r--r--core/io/resource_format_binary.h2
-rw-r--r--core/io/resource_loader.cpp625
-rw-r--r--core/io/resource_loader.h72
-rw-r--r--core/io/xml_parser.cpp2
-rw-r--r--core/io/xml_parser.h2
-rw-r--r--core/io/zip_io.cpp50
-rw-r--r--core/io/zip_io.h7
-rw-r--r--core/math/a_star_grid_2d.cpp74
-rw-r--r--core/math/a_star_grid_2d.h15
-rw-r--r--core/math/basis.cpp13
-rw-r--r--core/math/basis.h2
-rw-r--r--core/math/bvh_debug.inc6
-rw-r--r--core/math/color.cpp13
-rw-r--r--core/math/convex_hull.cpp24
-rw-r--r--core/math/delaunay_2d.h125
-rw-r--r--core/math/geometry_3d.cpp1
-rw-r--r--core/math/math_funcs.h10
-rw-r--r--core/math/plane.cpp4
-rw-r--r--core/math/quaternion.cpp3
-rw-r--r--core/math/static_raycaster.h16
-rw-r--r--core/math/transform_2d.cpp10
-rw-r--r--core/math/transform_2d.h2
-rw-r--r--core/math/transform_3d.cpp8
-rw-r--r--core/math/transform_3d.h4
-rw-r--r--core/object/callable_method_pointer.h20
-rw-r--r--core/object/class_db.cpp145
-rw-r--r--core/object/class_db.h75
-rw-r--r--core/object/make_virtuals.py2
-rw-r--r--core/object/message_queue.cpp594
-rw-r--r--core/object/message_queue.h81
-rw-r--r--core/object/method_bind.h49
-rw-r--r--core/object/object.cpp133
-rw-r--r--core/object/object.h86
-rw-r--r--core/object/ref_counted.h39
-rw-r--r--core/object/script_language.cpp47
-rw-r--r--core/object/script_language.h14
-rw-r--r--core/object/script_language_extension.h2
-rw-r--r--core/object/undo_redo.cpp28
-rw-r--r--core/object/undo_redo.h5
-rw-r--r--core/object/worker_thread_pool.cpp247
-rw-r--r--core/object/worker_thread_pool.h15
-rw-r--r--core/os/keyboard.cpp39
-rw-r--r--core/os/keyboard.h2
-rw-r--r--core/os/mutex.h19
-rw-r--r--core/os/os.cpp98
-rw-r--r--core/os/os.h29
-rw-r--r--core/os/semaphore.h55
-rw-r--r--core/os/spin_lock.h6
-rw-r--r--core/os/thread.cpp46
-rw-r--r--core/os/thread.h28
-rw-r--r--core/os/thread_safe.cpp46
-rw-r--r--core/os/thread_safe.h3
-rw-r--r--core/os/time.cpp11
-rw-r--r--core/os/time.h3
-rw-r--r--core/register_core_types.cpp50
-rw-r--r--core/string/print_string.cpp6
-rw-r--r--core/string/print_string.h10
-rw-r--r--core/string/string_name.cpp8
-rw-r--r--core/string/string_name.h11
-rw-r--r--core/string/translation.cpp17
-rw-r--r--core/string/ustring.cpp315
-rw-r--r--core/string/ustring.h7
-rw-r--r--core/templates/hash_map.h10
-rw-r--r--core/templates/hash_set.h4
-rw-r--r--core/templates/hashfuncs.h6
-rw-r--r--core/templates/local_vector.h19
-rw-r--r--core/templates/paged_allocator.h45
-rw-r--r--core/templates/rid_owner.h4
-rw-r--r--core/templates/safe_refcount.h16
-rw-r--r--core/templates/self_list.h11
-rw-r--r--core/templates/vector.h5
-rw-r--r--core/variant/binder_common.h128
-rw-r--r--core/variant/callable.cpp11
-rw-r--r--core/variant/callable.h3
-rw-r--r--core/variant/callable_bind.cpp8
-rw-r--r--core/variant/callable_bind.h6
-rw-r--r--core/variant/dictionary.cpp11
-rw-r--r--core/variant/method_ptrcall.h10
-rw-r--r--core/variant/typed_array.h14
-rw-r--r--core/variant/variant.cpp4
-rw-r--r--core/variant/variant.h2
-rw-r--r--core/variant/variant_call.cpp17
-rw-r--r--core/variant/variant_construct.cpp11
-rw-r--r--core/variant/variant_internal.h52
-rw-r--r--core/variant/variant_op.cpp33
-rw-r--r--core/variant/variant_op.h11
-rw-r--r--core/variant/variant_utility.cpp3
-rw-r--r--core/version.h12
142 files changed, 6665 insertions, 2816 deletions
diff --git a/core/SCsub b/core/SCsub
index 43deff3ad5..a0176f6c33 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -64,6 +64,31 @@ thirdparty_misc_sources = [
thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_misc_sources)
+# Brotli
+if env["brotli"]:
+ thirdparty_brotli_dir = "#thirdparty/brotli/"
+ thirdparty_brotli_sources = [
+ "common/constants.c",
+ "common/context.c",
+ "common/dictionary.c",
+ "common/platform.c",
+ "common/shared_dictionary.c",
+ "common/transform.c",
+ "dec/bit_reader.c",
+ "dec/decode.c",
+ "dec/huffman.c",
+ "dec/state.c",
+ ]
+ thirdparty_brotli_sources = [thirdparty_brotli_dir + file for file in thirdparty_brotli_sources]
+
+ env_thirdparty.Prepend(CPPPATH=[thirdparty_brotli_dir + "include"])
+ env.Prepend(CPPPATH=[thirdparty_brotli_dir + "include"])
+
+ if env.get("use_ubsan") or env.get("use_asan") or env.get("use_tsan") or env.get("use_lsan") or env.get("use_msan"):
+ env_thirdparty.Append(CPPDEFINES=["BROTLI_BUILD_PORTABLE"])
+
+ env_thirdparty.add_source_files(thirdparty_obj, thirdparty_brotli_sources)
+
# Zlib library, can be unbundled
if env["builtin_zlib"]:
thirdparty_zlib_dir = "#thirdparty/zlib/"
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index 814ad3d076..7fdea7d1aa 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -33,9 +33,7 @@
#include "core/authors.gen.h"
#include "core/config/project_settings.h"
#include "core/donors.gen.h"
-#include "core/io/json.h"
#include "core/license.gen.h"
-#include "core/os/os.h"
#include "core/variant/typed_array.h"
#include "core/version.h"
@@ -319,43 +317,6 @@ Engine::Engine() {
singleton = this;
}
-void Engine::startup_begin() {
- startup_benchmark_total_from = OS::get_singleton()->get_ticks_usec();
-}
-
-void Engine::startup_benchmark_begin_measure(const String &p_what) {
- startup_benchmark_section = p_what;
- startup_benchmark_from = OS::get_singleton()->get_ticks_usec();
-}
-void Engine::startup_benchmark_end_measure() {
- uint64_t total = OS::get_singleton()->get_ticks_usec() - startup_benchmark_from;
- double total_f = double(total) / double(1000000);
-
- startup_benchmark_json[startup_benchmark_section] = total_f;
-}
-
-void Engine::startup_dump(const String &p_to_file) {
- uint64_t total = OS::get_singleton()->get_ticks_usec() - startup_benchmark_total_from;
- double total_f = double(total) / double(1000000);
- startup_benchmark_json["total_time"] = total_f;
-
- if (!p_to_file.is_empty()) {
- Ref<FileAccess> f = FileAccess::open(p_to_file, FileAccess::WRITE);
- if (f.is_valid()) {
- Ref<JSON> json;
- json.instantiate();
- f->store_string(json->stringify(startup_benchmark_json, "\t", false, true));
- }
- } else {
- List<Variant> keys;
- startup_benchmark_json.get_key_list(&keys);
- print_line("STARTUP BENCHMARK:");
- for (const Variant &K : keys) {
- print_line("\t-", K, ": ", startup_benchmark_json[K], +" sec.");
- }
- }
-}
-
Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr, const StringName &p_class_name) :
name(p_name),
ptr(p_ptr),
diff --git a/core/config/engine.h b/core/config/engine.h
index 52408f4be1..5ea653ba6c 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -83,11 +83,6 @@ private:
String write_movie_path;
String shader_cache_path;
- Dictionary startup_benchmark_json;
- String startup_benchmark_section;
- uint64_t startup_benchmark_from = 0;
- uint64_t startup_benchmark_total_from = 0;
-
public:
static Engine *get_singleton();
@@ -163,11 +158,6 @@ public:
bool is_validation_layers_enabled() const;
int32_t get_gpu_index() const;
- void startup_begin();
- void startup_benchmark_begin_measure(const String &p_what);
- void startup_benchmark_end_measure();
- void startup_dump(const String &p_to_file);
-
Engine();
virtual ~Engine() {}
};
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index ac5499d709..79fab50882 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -36,7 +36,6 @@
#include "core/io/config_file.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
-#include "core/io/file_access_network.h"
#include "core/io/file_access_pack.h"
#include "core/io/marshalls.h"
#include "core/os/keyboard.h"
@@ -68,14 +67,6 @@ String ProjectSettings::get_resource_path() const {
return resource_path;
}
-String ProjectSettings::get_safe_project_name() const {
- String safe_name = OS::get_singleton()->get_safe_dir_name(get("application/config/name"));
- if (safe_name.is_empty()) {
- safe_name = "UnnamedProject";
- }
- return safe_name;
-}
-
String ProjectSettings::get_imported_files_path() const {
return get_project_data_path().path_join("imported");
}
@@ -502,17 +493,6 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
}
}
- // If looking for files in a network client, use it directly
-
- if (FileAccessNetworkClient::get_singleton()) {
- Error err = _load_settings_text_or_binary("res://project.godot", "res://project.binary");
- if (err == OK && !p_ignore_override) {
- // Optional, we don't mind if it fails
- _load_settings_text("res://override.cfg");
- }
- return err;
- }
-
// Attempt with a user-defined main pack first
if (!p_main_pack.is_empty()) {
@@ -640,7 +620,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b
Error ProjectSettings::setup(const String &p_path, const String &p_main_pack, bool p_upwards, bool p_ignore_override) {
Error err = _setup(p_path, p_main_pack, p_upwards, p_ignore_override);
- if (err == OK) {
+ if (err == OK && !p_ignore_override) {
String custom_settings = GLOBAL_GET("application/config/project_settings_override");
if (!custom_settings.is_empty()) {
_load_settings_text(custom_settings);
@@ -942,10 +922,26 @@ Error ProjectSettings::_save_settings_text(const String &p_file, const RBMap<Str
}
Error ProjectSettings::_save_custom_bnd(const String &p_file) { // add other params as dictionary and array?
-
return save_custom(p_file);
}
+#ifdef TOOLS_ENABLED
+bool _csproj_exists(String p_root_dir) {
+ Ref<DirAccess> dir = DirAccess::open(p_root_dir);
+
+ dir->list_dir_begin();
+ String file_name = dir->_get_next();
+ while (file_name != "") {
+ if (!dir->current_is_dir() && file_name.get_extension() == "csproj") {
+ return true;
+ }
+ file_name = dir->_get_next();
+ }
+
+ return false;
+}
+#endif // TOOLS_ENABLED
+
Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_custom, const Vector<String> &p_custom_features, bool p_merge_with_current) {
ERR_FAIL_COND_V_MSG(p_path.is_empty(), ERR_INVALID_PARAMETER, "Project settings save path cannot be empty.");
@@ -964,7 +960,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
}
}
// Check for the existence of a csproj file.
- if (FileAccess::exists(get_resource_path().path_join(get_safe_project_name() + ".csproj"))) {
+ if (_csproj_exists(get_resource_path())) {
// If there is a csproj file, add the C# feature if it doesn't already exist.
if (!project_features.has("C#")) {
project_features.append("C#");
@@ -1218,6 +1214,8 @@ void ProjectSettings::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_order", "name", "position"), &ProjectSettings::set_order);
ClassDB::bind_method(D_METHOD("get_order", "name"), &ProjectSettings::get_order);
ClassDB::bind_method(D_METHOD("set_initial_value", "name", "value"), &ProjectSettings::set_initial_value);
+ ClassDB::bind_method(D_METHOD("set_as_basic", "name", "basic"), &ProjectSettings::set_as_basic);
+ ClassDB::bind_method(D_METHOD("set_as_internal", "name", "internal"), &ProjectSettings::set_as_internal);
ClassDB::bind_method(D_METHOD("add_property_info", "hint"), &ProjectSettings::_add_property_info_bind);
ClassDB::bind_method(D_METHOD("set_restart_if_changed", "name", "restart"), &ProjectSettings::set_restart_if_changed);
ClassDB::bind_method(D_METHOD("clear", "name"), &ProjectSettings::clear);
@@ -1262,6 +1260,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_BASIC("application/config/name", "");
GLOBAL_DEF_BASIC(PropertyInfo(Variant::DICTIONARY, "application/config/name_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary());
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/description", PROPERTY_HINT_MULTILINE_TEXT), "");
+ GLOBAL_DEF_INTERNAL(PropertyInfo(Variant::STRING, "application/config/tags"), PackedStringArray());
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/run/main_scene", PROPERTY_HINT_FILE, "*.tscn,*.scn,*.res"), "");
GLOBAL_DEF("application/run/disable_stdout", false);
GLOBAL_DEF("application/run/disable_stderr", false);
@@ -1284,7 +1283,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/mode", PROPERTY_HINT_ENUM, "Windowed,Minimized,Maximized,Fullscreen,Exclusive Fullscreen"), 0);
// Keep the enum values in sync with the `DisplayServer::SCREEN_` enum.
- GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/initial_position_type", PROPERTY_HINT_ENUM, "Absolute,Primary Screen Center,Other Screen Center"), 1);
+ GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/initial_position_type", PROPERTY_HINT_ENUM, "Absolute,Center of Primary Screen,Center of Other Screen,Center of Screen With Mouse Pointer,Center of Screen With Keyboard Focus"), 1);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::VECTOR2I, "display/window/size/initial_position"), Vector2i());
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/initial_screen", PROPERTY_HINT_RANGE, "0,64,1,or_greater"), 0);
@@ -1302,6 +1301,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "audio/buses/default_bus_layout", PROPERTY_HINT_FILE, "*.tres"), "res://default_bus_layout.tres");
+ 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);
@@ -1354,6 +1354,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("rendering/rendering_device/staging_buffer/block_size_kb", 256);
GLOBAL_DEF("rendering/rendering_device/staging_buffer/max_size_mb", 128);
GLOBAL_DEF("rendering/rendering_device/staging_buffer/texture_upload_region_size_px", 64);
+ GLOBAL_DEF("rendering/rendering_device/pipeline_cache/save_chunk_size_mb", 3.0);
GLOBAL_DEF("rendering/rendering_device/vulkan/max_descriptors_per_pool", 64);
GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), 1);
diff --git a/core/config/project_settings.h b/core/config/project_settings.h
index a0249ef267..b89e6694b0 100644
--- a/core/config/project_settings.h
+++ b/core/config/project_settings.h
@@ -166,7 +166,6 @@ public:
String get_project_data_dir_name() const;
String get_project_data_path() const;
String get_resource_path() const;
- String get_safe_project_name() const;
String get_imported_files_path() const;
static ProjectSettings *get_singleton();
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index f2eb7823e2..2d0d24406c 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -39,6 +39,7 @@
#include "core/math/geometry_2d.h"
#include "core/math/geometry_3d.h"
#include "core/os/keyboard.h"
+#include "core/os/thread_safe.h"
#include "core/variant/typed_array.h"
namespace core_bind {
@@ -224,6 +225,14 @@ int OS::get_low_processor_usage_mode_sleep_usec() const {
return ::OS::get_singleton()->get_low_processor_usage_mode_sleep_usec();
}
+void OS::set_delta_smoothing(bool p_enabled) {
+ ::OS::get_singleton()->set_delta_smoothing(p_enabled);
+}
+
+bool OS::is_delta_smoothing_enabled() const {
+ return ::OS::get_singleton()->is_delta_smoothing_enabled();
+}
+
void OS::alert(const String &p_alert, const String &p_title) {
::OS::get_singleton()->alert(p_alert, p_title);
}
@@ -257,6 +266,15 @@ Error OS::shell_open(String p_uri) {
return ::OS::get_singleton()->shell_open(p_uri);
}
+Error OS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
+ if (p_path.begins_with("res://")) {
+ WARN_PRINT("Attempting to explore file path with the \"res://\" protocol. Use `ProjectSettings.globalize_path()` to convert a Godot-specific path to a system path before opening it with `OS.shell_show_in_file_manager()`.");
+ } else if (p_path.begins_with("user://")) {
+ WARN_PRINT("Attempting to explore file path with the \"user://\" protocol. Use `ProjectSettings.globalize_path()` to convert a Godot-specific path to a system path before opening it with `OS.shell_show_in_file_manager()`.");
+ }
+ return ::OS::get_singleton()->shell_show_in_file_manager(p_path, p_open_folder);
+}
+
String OS::read_string_from_stdin() {
return ::OS::get_singleton()->get_stdin_string();
}
@@ -414,7 +432,14 @@ Error OS::set_thread_name(const String &p_name) {
};
bool OS::has_feature(const String &p_feature) const {
- return ::OS::get_singleton()->has_feature(p_feature);
+ const bool *value_ptr = feature_cache.getptr(p_feature);
+ if (value_ptr) {
+ return *value_ptr;
+ } else {
+ const bool has = ::OS::get_singleton()->has_feature(p_feature);
+ feature_cache[p_feature] = has;
+ return has;
+ }
}
uint64_t OS::get_static_memory_usage() const {
@@ -425,6 +450,10 @@ uint64_t OS::get_static_memory_peak_usage() const {
return ::OS::get_singleton()->get_static_memory_peak_usage();
}
+Dictionary OS::get_memory_info() const {
+ return ::OS::get_singleton()->get_memory_info();
+}
+
/** This method uses a signed argument for better error reporting as it's used from the scripting API. */
void OS::delay_usec(int p_usec) const {
ERR_FAIL_COND_MSG(
@@ -536,6 +565,9 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_low_processor_usage_mode_sleep_usec", "usec"), &OS::set_low_processor_usage_mode_sleep_usec);
ClassDB::bind_method(D_METHOD("get_low_processor_usage_mode_sleep_usec"), &OS::get_low_processor_usage_mode_sleep_usec);
+ ClassDB::bind_method(D_METHOD("set_delta_smoothing", "delta_smoothing_enabled"), &OS::set_delta_smoothing);
+ ClassDB::bind_method(D_METHOD("is_delta_smoothing_enabled"), &OS::is_delta_smoothing_enabled);
+
ClassDB::bind_method(D_METHOD("get_processor_count"), &OS::get_processor_count);
ClassDB::bind_method(D_METHOD("get_processor_name"), &OS::get_processor_name);
@@ -549,6 +581,7 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill);
ClassDB::bind_method(D_METHOD("shell_open", "uri"), &OS::shell_open);
+ ClassDB::bind_method(D_METHOD("shell_show_in_file_manager", "file_or_dir_path", "open_folder"), &OS::shell_show_in_file_manager, DEFVAL(true));
ClassDB::bind_method(D_METHOD("is_process_running", "pid"), &OS::is_process_running);
ClassDB::bind_method(D_METHOD("get_process_id"), &OS::get_process_id);
@@ -582,6 +615,7 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_static_memory_usage"), &OS::get_static_memory_usage);
ClassDB::bind_method(D_METHOD("get_static_memory_peak_usage"), &OS::get_static_memory_peak_usage);
+ ClassDB::bind_method(D_METHOD("get_memory_info"), &OS::get_memory_info);
ClassDB::bind_method(D_METHOD("move_to_trash", "path"), &OS::move_to_trash);
ClassDB::bind_method(D_METHOD("get_user_data_dir"), &OS::get_user_data_dir);
@@ -609,6 +643,7 @@ void OS::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "low_processor_usage_mode"), "set_low_processor_usage_mode", "is_in_low_processor_usage_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "low_processor_usage_mode_sleep_usec"), "set_low_processor_usage_mode_sleep_usec", "get_low_processor_usage_mode_sleep_usec");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "delta_smoothing"), "set_delta_smoothing", "is_delta_smoothing_enabled");
// Those default values need to be specified for the docs generator,
// to avoid using values from the documentation writer's own OS instance.
@@ -960,10 +995,11 @@ Vector<Vector3> Geometry3D::segment_intersects_cylinder(const Vector3 &p_from, c
return r;
}
-Vector<Vector3> Geometry3D::segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const Vector<Plane> &p_planes) {
+Vector<Vector3> Geometry3D::segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const TypedArray<Plane> &p_planes) {
Vector<Vector3> r;
Vector3 res, norm;
- if (!::Geometry3D::segment_intersects_convex(p_from, p_to, p_planes.ptr(), p_planes.size(), &res, &norm)) {
+ Vector<Plane> planes = Variant(p_planes);
+ if (!::Geometry3D::segment_intersects_convex(p_from, p_to, planes.ptr(), planes.size(), &res, &norm)) {
return r;
}
@@ -1152,17 +1188,37 @@ void Thread::_start_func(void *ud) {
ERR_FAIL_MSG(vformat("Could not call function '%s' on previously freed instance to start thread %s.", t->target_callable.get_method(), t->get_id()));
}
+ // Finding out a suitable name for the thread can involve querying a node, if the target is one.
+ // We know this is safe (unless the user is causing life cycle race conditions, which would be a bug on their part).
+ set_current_thread_safe_for_nodes(true);
String func_name = t->target_callable.is_custom() ? t->target_callable.get_custom()->get_as_text() : String(t->target_callable.get_method());
+ set_current_thread_safe_for_nodes(false);
::Thread::set_name(func_name);
+ // To avoid a circular reference between the thread and the script which can possibly contain a reference
+ // to the thread, we will do the call (keeping a reference up to that point) and then break chains with it.
+ // When the call returns, we will reference the thread again if possible.
+ ObjectID th_instance_id = t->get_instance_id();
+ Callable target_callable = t->target_callable;
+ t = Ref<Thread>();
+
Callable::CallError ce;
- t->target_callable.callp(nullptr, 0, t->ret, ce);
- if (ce.error != Callable::CallError::CALL_OK) {
+ Variant ret;
+ target_callable.callp(nullptr, 0, ret, ce);
+ // If script properly kept a reference to the thread, we should be able to re-reference it now
+ // (well, or if the call failed, since we had to break chains anyway because the outcome isn't known upfront).
+ t = Ref<Thread>(ObjectDB::get_instance(th_instance_id));
+ if (t.is_valid()) {
+ t->ret = ret;
t->running.clear();
- ERR_FAIL_MSG("Could not call function '" + func_name + "' to start thread " + t->get_id() + ": " + Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce) + ".");
+ } else {
+ // We could print a warning here, but the Thread object will be eventually destroyed
+ // noticing wait_to_finish() hasn't been called on it, and it will print a warning itself.
}
- t->running.clear();
+ if (ce.error != Callable::CallError::CALL_OK) {
+ ERR_FAIL_MSG("Could not call function '" + func_name + "' to start thread " + t->get_id() + ": " + Variant::get_callable_error_text(t->target_callable, nullptr, 0, ce) + ".");
+ }
}
Error Thread::start(const Callable &p_callable, Priority p_priority) {
@@ -1204,6 +1260,11 @@ Variant Thread::wait_to_finish() {
return r;
}
+void Thread::set_thread_safety_checks_enabled(bool p_enabled) {
+ ERR_FAIL_COND_MSG(::Thread::is_main_thread(), "This call is forbidden on the main thread.");
+ set_current_thread_safe_for_nodes(!p_enabled);
+}
+
void Thread::_bind_methods() {
ClassDB::bind_method(D_METHOD("start", "callable", "priority"), &Thread::start, DEFVAL(PRIORITY_NORMAL));
ClassDB::bind_method(D_METHOD("get_id"), &Thread::get_id);
@@ -1211,6 +1272,8 @@ void Thread::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_alive"), &Thread::is_alive);
ClassDB::bind_method(D_METHOD("wait_to_finish"), &Thread::wait_to_finish);
+ ClassDB::bind_static_method("Thread", D_METHOD("set_thread_safety_checks_enabled", "enabled"), &Thread::set_thread_safety_checks_enabled);
+
BIND_ENUM_CONSTANT(PRIORITY_LOW);
BIND_ENUM_CONSTANT(PRIORITY_NORMAL);
BIND_ENUM_CONSTANT(PRIORITY_HIGH);
@@ -1278,11 +1341,11 @@ Variant ClassDB::instantiate(const StringName &p_class) const {
}
}
-bool ClassDB::has_signal(StringName p_class, StringName p_signal) const {
+bool ClassDB::class_has_signal(StringName p_class, StringName p_signal) const {
return ::ClassDB::has_signal(p_class, p_signal);
}
-Dictionary ClassDB::get_signal(StringName p_class, StringName p_signal) const {
+Dictionary ClassDB::class_get_signal(StringName p_class, StringName p_signal) const {
MethodInfo signal;
if (::ClassDB::get_signal(p_class, p_signal, &signal)) {
return signal.operator Dictionary();
@@ -1291,7 +1354,7 @@ Dictionary ClassDB::get_signal(StringName p_class, StringName p_signal) const {
}
}
-TypedArray<Dictionary> ClassDB::get_signal_list(StringName p_class, bool p_no_inheritance) const {
+TypedArray<Dictionary> ClassDB::class_get_signal_list(StringName p_class, bool p_no_inheritance) const {
List<MethodInfo> signals;
::ClassDB::get_signal_list(p_class, &signals, p_no_inheritance);
TypedArray<Dictionary> ret;
@@ -1303,7 +1366,7 @@ TypedArray<Dictionary> ClassDB::get_signal_list(StringName p_class, bool p_no_in
return ret;
}
-TypedArray<Dictionary> ClassDB::get_property_list(StringName p_class, bool p_no_inheritance) const {
+TypedArray<Dictionary> ClassDB::class_get_property_list(StringName p_class, bool p_no_inheritance) const {
List<PropertyInfo> plist;
::ClassDB::get_property_list(p_class, &plist, p_no_inheritance);
TypedArray<Dictionary> ret;
@@ -1314,13 +1377,13 @@ TypedArray<Dictionary> ClassDB::get_property_list(StringName p_class, bool p_no_
return ret;
}
-Variant ClassDB::get_property(Object *p_object, const StringName &p_property) const {
+Variant ClassDB::class_get_property(Object *p_object, const StringName &p_property) const {
Variant ret;
::ClassDB::get_property(p_object, p_property, ret);
return ret;
}
-Error ClassDB::set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const {
+Error ClassDB::class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const {
Variant ret;
bool valid;
if (!::ClassDB::set_property(p_object, p_property, p_value, &valid)) {
@@ -1331,11 +1394,11 @@ Error ClassDB::set_property(Object *p_object, const StringName &p_property, cons
return OK;
}
-bool ClassDB::has_method(StringName p_class, StringName p_method, bool p_no_inheritance) const {
+bool ClassDB::class_has_method(StringName p_class, StringName p_method, bool p_no_inheritance) const {
return ::ClassDB::has_method(p_class, p_method, p_no_inheritance);
}
-TypedArray<Dictionary> ClassDB::get_method_list(StringName p_class, bool p_no_inheritance) const {
+TypedArray<Dictionary> ClassDB::class_get_method_list(StringName p_class, bool p_no_inheritance) const {
List<MethodInfo> methods;
::ClassDB::get_method_list(p_class, &methods, p_no_inheritance);
TypedArray<Dictionary> ret;
@@ -1353,7 +1416,7 @@ TypedArray<Dictionary> ClassDB::get_method_list(StringName p_class, bool p_no_in
return ret;
}
-PackedStringArray ClassDB::get_integer_constant_list(const StringName &p_class, bool p_no_inheritance) const {
+PackedStringArray ClassDB::class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance) const {
List<String> constants;
::ClassDB::get_integer_constant_list(p_class, &constants, p_no_inheritance);
@@ -1367,24 +1430,24 @@ PackedStringArray ClassDB::get_integer_constant_list(const StringName &p_class,
return ret;
}
-bool ClassDB::has_integer_constant(const StringName &p_class, const StringName &p_name) const {
+bool ClassDB::class_has_integer_constant(const StringName &p_class, const StringName &p_name) const {
bool success;
::ClassDB::get_integer_constant(p_class, p_name, &success);
return success;
}
-int64_t ClassDB::get_integer_constant(const StringName &p_class, const StringName &p_name) const {
+int64_t ClassDB::class_get_integer_constant(const StringName &p_class, const StringName &p_name) const {
bool found;
int64_t c = ::ClassDB::get_integer_constant(p_class, p_name, &found);
ERR_FAIL_COND_V(!found, 0);
return c;
}
-bool ClassDB::has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) const {
+bool ClassDB::class_has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) const {
return ::ClassDB::has_enum(p_class, p_name, p_no_inheritance);
}
-PackedStringArray ClassDB::get_enum_list(const StringName &p_class, bool p_no_inheritance) const {
+PackedStringArray ClassDB::class_get_enum_list(const StringName &p_class, bool p_no_inheritance) const {
List<StringName> enums;
::ClassDB::get_enum_list(p_class, &enums, p_no_inheritance);
@@ -1398,7 +1461,7 @@ PackedStringArray ClassDB::get_enum_list(const StringName &p_class, bool p_no_in
return ret;
}
-PackedStringArray ClassDB::get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance) const {
+PackedStringArray ClassDB::class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance) const {
List<StringName> constants;
::ClassDB::get_enum_constants(p_class, p_enum, &constants, p_no_inheritance);
@@ -1412,7 +1475,7 @@ PackedStringArray ClassDB::get_enum_constants(const StringName &p_class, const S
return ret;
}
-StringName ClassDB::get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) const {
+StringName ClassDB::class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance) const {
return ::ClassDB::get_integer_constant_enum(p_class, p_name, p_no_inheritance);
}
@@ -1429,27 +1492,27 @@ void ClassDB::_bind_methods() {
::ClassDB::bind_method(D_METHOD("can_instantiate", "class"), &ClassDB::can_instantiate);
::ClassDB::bind_method(D_METHOD("instantiate", "class"), &ClassDB::instantiate);
- ::ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &ClassDB::has_signal);
- ::ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &ClassDB::get_signal);
- ::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::get_signal_list, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &ClassDB::class_has_signal);
+ ::ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &ClassDB::class_get_signal);
+ ::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::class_get_signal_list, DEFVAL(false));
- ::ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &ClassDB::get_property_list, DEFVAL(false));
- ::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::get_property);
- ::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::set_property);
+ ::ClassDB::bind_method(D_METHOD("class_get_property_list", "class", "no_inheritance"), &ClassDB::class_get_property_list, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_get_property", "object", "property"), &ClassDB::class_get_property);
+ ::ClassDB::bind_method(D_METHOD("class_set_property", "object", "property", "value"), &ClassDB::class_set_property);
- ::ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &ClassDB::has_method, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_has_method", "class", "method", "no_inheritance"), &ClassDB::class_has_method, DEFVAL(false));
- ::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::get_method_list, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_get_method_list", "class", "no_inheritance"), &ClassDB::class_get_method_list, DEFVAL(false));
- ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::get_integer_constant_list, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_list", "class", "no_inheritance"), &ClassDB::class_get_integer_constant_list, DEFVAL(false));
- ::ClassDB::bind_method(D_METHOD("class_has_integer_constant", "class", "name"), &ClassDB::has_integer_constant);
- ::ClassDB::bind_method(D_METHOD("class_get_integer_constant", "class", "name"), &ClassDB::get_integer_constant);
+ ::ClassDB::bind_method(D_METHOD("class_has_integer_constant", "class", "name"), &ClassDB::class_has_integer_constant);
+ ::ClassDB::bind_method(D_METHOD("class_get_integer_constant", "class", "name"), &ClassDB::class_get_integer_constant);
- ::ClassDB::bind_method(D_METHOD("class_has_enum", "class", "name", "no_inheritance"), &ClassDB::has_enum, DEFVAL(false));
- ::ClassDB::bind_method(D_METHOD("class_get_enum_list", "class", "no_inheritance"), &ClassDB::get_enum_list, DEFVAL(false));
- ::ClassDB::bind_method(D_METHOD("class_get_enum_constants", "class", "enum", "no_inheritance"), &ClassDB::get_enum_constants, DEFVAL(false));
- ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_enum", "class", "name", "no_inheritance"), &ClassDB::get_integer_constant_enum, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_has_enum", "class", "name", "no_inheritance"), &ClassDB::class_has_enum, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_get_enum_list", "class", "no_inheritance"), &ClassDB::class_get_enum_list, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_get_enum_constants", "class", "enum", "no_inheritance"), &ClassDB::class_get_enum_constants, DEFVAL(false));
+ ::ClassDB::bind_method(D_METHOD("class_get_integer_constant_enum", "class", "name", "no_inheritance"), &ClassDB::class_get_integer_constant_enum, DEFVAL(false));
::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled);
}
diff --git a/core/core_bind.h b/core/core_bind.h
index 675da48591..6b25510b14 100644
--- a/core/core_bind.h
+++ b/core/core_bind.h
@@ -119,6 +119,8 @@ public:
class OS : public Object {
GDCLASS(OS, Object);
+ mutable HashMap<String, bool> feature_cache;
+
protected:
static void _bind_methods();
static OS *singleton;
@@ -139,6 +141,9 @@ public:
void set_low_processor_usage_mode_sleep_usec(int p_usec);
int get_low_processor_usage_mode_sleep_usec() const;
+ void set_delta_smoothing(bool p_enabled);
+ bool is_delta_smoothing_enabled() const;
+
void alert(const String &p_alert, const String &p_title = "ALERT!");
void crash(const String &p_message);
@@ -152,6 +157,7 @@ public:
int create_instance(const Vector<String> &p_arguments);
Error kill(int p_pid);
Error shell_open(String p_uri);
+ Error shell_show_in_file_manager(String p_path, bool p_open_folder = true);
bool is_process_running(int p_pid) const;
int get_process_id() const;
@@ -190,6 +196,7 @@ public:
uint64_t get_static_memory_usage() const;
uint64_t get_static_memory_peak_usage() const;
+ Dictionary get_memory_info() const;
void delay_usec(int p_usec) const;
void delay_msec(int p_msec) const;
@@ -322,7 +329,7 @@ public:
Vector<Vector3> segment_intersects_sphere(const Vector3 &p_from, const Vector3 &p_to, const Vector3 &p_sphere_pos, real_t p_sphere_radius);
Vector<Vector3> segment_intersects_cylinder(const Vector3 &p_from, const Vector3 &p_to, float p_height, float p_radius);
- Vector<Vector3> segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const Vector<Plane> &p_planes);
+ Vector<Vector3> segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const TypedArray<Plane> &p_planes);
Vector<Vector3> clip_polygon(const Vector<Vector3> &p_points, const Plane &p_plane);
@@ -401,6 +408,8 @@ public:
bool is_started() const;
bool is_alive() const;
Variant wait_to_finish();
+
+ static void set_thread_safety_checks_enabled(bool p_enabled);
};
namespace special {
@@ -420,26 +429,26 @@ public:
bool can_instantiate(const StringName &p_class) const;
Variant instantiate(const StringName &p_class) const;
- bool has_signal(StringName p_class, StringName p_signal) const;
- Dictionary get_signal(StringName p_class, StringName p_signal) const;
- TypedArray<Dictionary> get_signal_list(StringName p_class, bool p_no_inheritance = false) const;
+ bool class_has_signal(StringName p_class, StringName p_signal) const;
+ Dictionary class_get_signal(StringName p_class, StringName p_signal) const;
+ TypedArray<Dictionary> class_get_signal_list(StringName p_class, bool p_no_inheritance = false) const;
- TypedArray<Dictionary> get_property_list(StringName p_class, bool p_no_inheritance = false) const;
- Variant get_property(Object *p_object, const StringName &p_property) const;
- Error set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const;
+ TypedArray<Dictionary> class_get_property_list(StringName p_class, bool p_no_inheritance = false) const;
+ Variant class_get_property(Object *p_object, const StringName &p_property) const;
+ Error class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const;
- bool has_method(StringName p_class, StringName p_method, bool p_no_inheritance = false) const;
+ bool class_has_method(StringName p_class, StringName p_method, bool p_no_inheritance = false) const;
- TypedArray<Dictionary> get_method_list(StringName p_class, bool p_no_inheritance = false) const;
+ TypedArray<Dictionary> class_get_method_list(StringName p_class, bool p_no_inheritance = false) const;
- PackedStringArray get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const;
- bool has_integer_constant(const StringName &p_class, const StringName &p_name) const;
- int64_t get_integer_constant(const StringName &p_class, const StringName &p_name) const;
+ PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const;
+ bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const;
+ int64_t class_get_integer_constant(const StringName &p_class, const StringName &p_name) const;
- bool has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const;
- PackedStringArray get_enum_list(const StringName &p_class, bool p_no_inheritance = false) const;
- PackedStringArray get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const;
- StringName get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const;
+ bool class_has_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const;
+ PackedStringArray class_get_enum_list(const StringName &p_class, bool p_no_inheritance = false) const;
+ PackedStringArray class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const;
+ StringName class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const;
bool is_class_enabled(StringName p_class) const;
diff --git a/core/core_builders.py b/core/core_builders.py
index b0a3b85d58..e40ebbb14d 100644
--- a/core/core_builders.py
+++ b/core/core_builders.py
@@ -239,7 +239,6 @@ def make_license_header(target, source, env):
data_list += part["Copyright"]
with open(dst, "w", encoding="utf-8") as f:
-
f.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
f.write("#ifndef LICENSE_GEN_H\n")
f.write("#define LICENSE_GEN_H\n")
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index d88dda6609..2332bc235b 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -645,6 +645,7 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_RENDER);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_PHYSICS);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_NAVIGATION);
+ BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_AVOIDANCE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_FILE);
BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DIR);
@@ -704,6 +705,7 @@ void register_global_constants() {
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT);
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_EDITOR_BASIC_SETTING);
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_READ_ONLY);
+ BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_SECRET);
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_DEFAULT);
BIND_CORE_BITFIELD_FLAG(PROPERTY_USAGE_NO_EDITOR);
diff --git a/core/crypto/SCsub b/core/crypto/SCsub
index 9b7953fdc5..ac79e10d19 100644
--- a/core/crypto/SCsub
+++ b/core/crypto/SCsub
@@ -20,12 +20,13 @@ if is_builtin or not has_module:
# Only if the module is not enabled, we must compile here the required sources
# to make a "light" build with only the necessary mbedtls files.
if not has_module:
- env_thirdparty = env_crypto.Clone()
- env_thirdparty.disable_warnings()
- # Custom config file
- env_thirdparty.Append(
+ # Minimal mbedTLS config file
+ env_crypto.Append(
CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_core_mbedtls_config.h\\"')]
)
+ # Build minimal mbedTLS library (MD5/SHA/Base64/AES).
+ env_thirdparty = env_crypto.Clone()
+ env_thirdparty.disable_warnings()
thirdparty_mbedtls_dir = "#thirdparty/mbedtls/library/"
thirdparty_mbedtls_sources = [
"aes.c",
@@ -40,8 +41,16 @@ if not has_module:
]
thirdparty_mbedtls_sources = [thirdparty_mbedtls_dir + file for file in thirdparty_mbedtls_sources]
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_mbedtls_sources)
+ # Needed to force rebuilding the library when the configuration file is updated.
+ env_thirdparty.Depends(thirdparty_obj, "#thirdparty/mbedtls/include/godot_core_mbedtls_config.h")
env.core_sources += thirdparty_obj
-
+elif is_builtin:
+ # Module mbedTLS config file
+ env_crypto.Append(
+ CPPDEFINES=[("MBEDTLS_CONFIG_FILE", '\\"thirdparty/mbedtls/include/godot_module_mbedtls_config.h\\"')]
+ )
+ # Needed to force rebuilding the core files when the configuration file is updated.
+ thirdparty_obj = ["#thirdparty/mbedtls/include/godot_module_mbedtls_config.h"]
# Godot source files
diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp
index 939c1c298f..6b1c2a9cb2 100644
--- a/core/crypto/crypto.cpp
+++ b/core/crypto/crypto.cpp
@@ -63,6 +63,8 @@ X509Certificate *X509Certificate::create() {
void X509Certificate::_bind_methods() {
ClassDB::bind_method(D_METHOD("save", "path"), &X509Certificate::save);
ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load);
+ ClassDB::bind_method(D_METHOD("save_to_string"), &X509Certificate::save_to_string);
+ ClassDB::bind_method(D_METHOD("load_from_string", "string"), &X509Certificate::load_from_string);
}
/// TLSOptions
diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h
index 999fe076d6..4b5bf8305f 100644
--- a/core/crypto/crypto.h
+++ b/core/crypto/crypto.h
@@ -65,6 +65,8 @@ public:
virtual Error load(String p_path) = 0;
virtual Error load_from_memory(const uint8_t *p_buffer, int p_len) = 0;
virtual Error save(String p_path) = 0;
+ virtual String save_to_string() = 0;
+ virtual Error load_from_string(const String &string) = 0;
};
class TLSOptions : public RefCounted {
diff --git a/core/debugger/local_debugger.cpp b/core/debugger/local_debugger.cpp
index 623b1eb0ce..dc46ffc307 100644
--- a/core/debugger/local_debugger.cpp
+++ b/core/debugger/local_debugger.cpp
@@ -138,7 +138,7 @@ void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
// Cache options
String variable_prefix = options["variable_prefix"];
- if (line.is_empty()) {
+ if (line.is_empty() && !feof(stdin)) {
print_line("\nDebugger Break, Reason: '" + script_lang->debug_get_error() + "'");
print_line("*Frame " + itos(current_frame) + " - " + script_lang->debug_get_stack_level_source(current_frame) + ":" + itos(script_lang->debug_get_stack_level_line(current_frame)) + " in function '" + script_lang->debug_get_stack_level_function(current_frame) + "'");
print_line("Enter \"help\" for assistance.");
@@ -267,7 +267,8 @@ void LocalDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
print_line("Added breakpoint at " + source + ":" + itos(linenr));
}
- } else if (line == "q" || line == "quit") {
+ } else if (line == "q" || line == "quit" ||
+ (line.is_empty() && feof(stdin))) {
// Do not stop again on quit
script_debugger->clear_breakpoints();
script_debugger->set_depth(-1);
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index f82600a9a2..81ee09f515 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -66,7 +66,9 @@ int RemoteDebuggerPeerTCP::get_max_message_size() const {
void RemoteDebuggerPeerTCP::close() {
running = false;
- thread.wait_to_finish();
+ if (thread.is_started()) {
+ thread.wait_to_finish();
+ }
tcp_client->disconnect_from_host();
out_buf.clear();
in_buf.clear();
diff --git a/core/doc_data.cpp b/core/doc_data.cpp
index 5e09e560d5..7549ba884e 100644
--- a/core/doc_data.cpp
+++ b/core/doc_data.cpp
@@ -51,6 +51,7 @@ void DocData::return_doc_from_retinfo(DocData::MethodDoc &p_method, const Proper
if (p_method.return_enum.begins_with("_")) { //proxy class
p_method.return_enum = p_method.return_enum.substr(1, p_method.return_enum.length());
}
+ p_method.return_is_bitfield = p_retinfo.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD;
p_method.return_type = "int";
} else if (p_retinfo.class_name != StringName()) {
p_method.return_type = p_retinfo.class_name;
@@ -82,6 +83,7 @@ void DocData::argument_doc_from_arginfo(DocData::ArgumentDoc &p_argument, const
if (p_argument.enumeration.begins_with("_")) { //proxy class
p_argument.enumeration = p_argument.enumeration.substr(1, p_argument.enumeration.length());
}
+ p_argument.is_bitfield = p_arginfo.usage & PROPERTY_USAGE_CLASS_IS_BITFIELD;
p_argument.type = "int";
} else if (p_arginfo.class_name != StringName()) {
p_argument.type = p_arginfo.class_name;
@@ -165,6 +167,7 @@ void DocData::method_doc_from_methodinfo(DocData::MethodDoc &p_method, const Met
void DocData::constant_doc_from_variant(DocData::ConstantDoc &p_const, const StringName &p_name, const Variant &p_value, const String &p_desc) {
p_const.name = p_name;
p_const.value = p_value;
+ p_const.is_value_valid = (p_value.get_type() != Variant::OBJECT);
p_const.description = p_desc;
}
diff --git a/core/doc_data.h b/core/doc_data.h
index c503a4e0d6..0fe7414b98 100644
--- a/core/doc_data.h
+++ b/core/doc_data.h
@@ -50,6 +50,7 @@ public:
String name;
String type;
String enumeration;
+ bool is_bitfield = false;
String default_value;
bool operator<(const ArgumentDoc &p_arg) const {
if (name == p_arg.name) {
@@ -70,6 +71,9 @@ public:
if (p_dict.has("enumeration")) {
doc.enumeration = p_dict["enumeration"];
+ if (p_dict.has("is_bitfield")) {
+ doc.is_bitfield = p_dict["is_bitfield"];
+ }
}
if (p_dict.has("default_value")) {
@@ -78,12 +82,35 @@ public:
return doc;
}
+ static Dictionary to_dict(const ArgumentDoc &p_doc) {
+ Dictionary dict;
+
+ if (!p_doc.name.is_empty()) {
+ dict["name"] = p_doc.name;
+ }
+
+ if (!p_doc.type.is_empty()) {
+ dict["type"] = p_doc.type;
+ }
+
+ if (!p_doc.enumeration.is_empty()) {
+ dict["enumeration"] = p_doc.enumeration;
+ dict["is_bitfield"] = p_doc.is_bitfield;
+ }
+
+ if (!p_doc.default_value.is_empty()) {
+ dict["default_value"] = p_doc.default_value;
+ }
+
+ return dict;
+ }
};
struct MethodDoc {
String name;
String return_type;
String return_enum;
+ bool return_is_bitfield = false;
String qualifiers;
String description;
bool is_deprecated = false;
@@ -116,7 +143,7 @@ public:
return arguments[0] < p_method.arguments[0];
}
}
- return name < p_method.name;
+ return name.naturalcasecmp_to(p_method.name) < 0;
}
static MethodDoc from_dict(const Dictionary &p_dict) {
MethodDoc doc;
@@ -131,6 +158,9 @@ public:
if (p_dict.has("return_enum")) {
doc.return_enum = p_dict["return_enum"];
+ if (p_dict.has("return_is_bitfield")) {
+ doc.return_is_bitfield = p_dict["return_is_bitfield"];
+ }
}
if (p_dict.has("qualifiers")) {
@@ -167,6 +197,52 @@ public:
return doc;
}
+ static Dictionary to_dict(const MethodDoc &p_doc) {
+ Dictionary dict;
+
+ if (!p_doc.name.is_empty()) {
+ dict["name"] = p_doc.name;
+ }
+
+ if (!p_doc.return_type.is_empty()) {
+ dict["return_type"] = p_doc.return_type;
+ }
+
+ if (!p_doc.return_enum.is_empty()) {
+ dict["return_enum"] = p_doc.return_enum;
+ dict["return_is_bitfield"] = p_doc.return_is_bitfield;
+ }
+
+ if (!p_doc.qualifiers.is_empty()) {
+ dict["qualifiers"] = p_doc.qualifiers;
+ }
+
+ if (!p_doc.description.is_empty()) {
+ dict["description"] = p_doc.description;
+ }
+
+ dict["is_deprecated"] = p_doc.is_deprecated;
+
+ dict["is_experimental"] = p_doc.is_experimental;
+
+ if (!p_doc.arguments.is_empty()) {
+ Array arguments;
+ for (int i = 0; i < p_doc.arguments.size(); i++) {
+ arguments.push_back(ArgumentDoc::to_dict(p_doc.arguments[i]));
+ }
+ dict["arguments"] = arguments;
+ }
+
+ if (!p_doc.errors_returned.is_empty()) {
+ Array errors_returned;
+ for (int i = 0; i < p_doc.errors_returned.size(); i++) {
+ errors_returned.push_back(p_doc.errors_returned[i]);
+ }
+ dict["errors_returned"] = errors_returned;
+ }
+
+ return dict;
+ }
};
struct ConstantDoc {
@@ -198,10 +274,9 @@ public:
if (p_dict.has("enumeration")) {
doc.enumeration = p_dict["enumeration"];
- }
-
- if (p_dict.has("is_bitfield")) {
- doc.is_bitfield = p_dict["is_bitfield"];
+ if (p_dict.has("is_bitfield")) {
+ doc.is_bitfield = p_dict["is_bitfield"];
+ }
}
if (p_dict.has("description")) {
@@ -218,37 +293,33 @@ public:
return doc;
}
- };
+ static Dictionary to_dict(const ConstantDoc &p_doc) {
+ Dictionary dict;
- struct EnumDoc {
- String name = "@unnamed_enum";
- bool is_bitfield = false;
- String description;
- Vector<DocData::ConstantDoc> values;
- static EnumDoc from_dict(const Dictionary &p_dict) {
- EnumDoc doc;
-
- if (p_dict.has("name")) {
- doc.name = p_dict["name"];
+ if (!p_doc.name.is_empty()) {
+ dict["name"] = p_doc.name;
}
- if (p_dict.has("is_bitfield")) {
- doc.is_bitfield = p_dict["is_bitfield"];
+ if (!p_doc.value.is_empty()) {
+ dict["value"] = p_doc.value;
}
- if (p_dict.has("description")) {
- doc.description = p_dict["description"];
- }
+ dict["is_value_valid"] = p_doc.is_value_valid;
- Array values;
- if (p_dict.has("values")) {
- values = p_dict["values"];
+ if (!p_doc.enumeration.is_empty()) {
+ dict["enumeration"] = p_doc.enumeration;
+ dict["is_bitfield"] = p_doc.is_bitfield;
}
- for (int i = 0; i < values.size(); i++) {
- doc.values.push_back(ConstantDoc::from_dict(values[i]));
+
+ if (!p_doc.description.is_empty()) {
+ dict["description"] = p_doc.description;
}
- return doc;
+ dict["is_deprecated"] = p_doc.is_deprecated;
+
+ dict["is_experimental"] = p_doc.is_experimental;
+
+ return dict;
}
};
@@ -256,6 +327,7 @@ public:
String name;
String type;
String enumeration;
+ bool is_bitfield = false;
String description;
String setter, getter;
String default_value;
@@ -264,7 +336,7 @@ public:
bool is_deprecated = false;
bool is_experimental = false;
bool operator<(const PropertyDoc &p_prop) const {
- return name < p_prop.name;
+ return name.naturalcasecmp_to(p_prop.name) < 0;
}
static PropertyDoc from_dict(const Dictionary &p_dict) {
PropertyDoc doc;
@@ -279,6 +351,9 @@ public:
if (p_dict.has("enumeration")) {
doc.enumeration = p_dict["enumeration"];
+ if (p_dict.has("is_bitfield")) {
+ doc.is_bitfield = p_dict["is_bitfield"];
+ }
}
if (p_dict.has("description")) {
@@ -315,6 +390,50 @@ public:
return doc;
}
+ static Dictionary to_dict(const PropertyDoc &p_doc) {
+ Dictionary dict;
+
+ if (!p_doc.name.is_empty()) {
+ dict["name"] = p_doc.name;
+ }
+
+ if (!p_doc.type.is_empty()) {
+ dict["type"] = p_doc.type;
+ }
+
+ if (!p_doc.enumeration.is_empty()) {
+ dict["enumeration"] = p_doc.enumeration;
+ dict["is_bitfield"] = p_doc.is_bitfield;
+ }
+
+ if (!p_doc.description.is_empty()) {
+ dict["description"] = p_doc.description;
+ }
+
+ if (!p_doc.setter.is_empty()) {
+ dict["setter"] = p_doc.setter;
+ }
+
+ if (!p_doc.getter.is_empty()) {
+ dict["getter"] = p_doc.getter;
+ }
+
+ if (!p_doc.default_value.is_empty()) {
+ dict["default_value"] = p_doc.default_value;
+ }
+
+ dict["overridden"] = p_doc.overridden;
+
+ if (!p_doc.overrides.is_empty()) {
+ dict["overrides"] = p_doc.overrides;
+ }
+
+ dict["is_deprecated"] = p_doc.is_deprecated;
+
+ dict["is_experimental"] = p_doc.is_experimental;
+
+ return dict;
+ }
};
struct ThemeItemDoc {
@@ -326,7 +445,7 @@ public:
bool operator<(const ThemeItemDoc &p_theme_item) const {
// First sort by the data type, then by name.
if (data_type == p_theme_item.data_type) {
- return name < p_theme_item.name;
+ return name.naturalcasecmp_to(p_theme_item.name) < 0;
}
return data_type < p_theme_item.data_type;
}
@@ -355,6 +474,31 @@ public:
return doc;
}
+ static Dictionary to_dict(const ThemeItemDoc &p_doc) {
+ Dictionary dict;
+
+ if (!p_doc.name.is_empty()) {
+ dict["name"] = p_doc.name;
+ }
+
+ if (!p_doc.type.is_empty()) {
+ dict["type"] = p_doc.type;
+ }
+
+ if (!p_doc.data_type.is_empty()) {
+ dict["data_type"] = p_doc.data_type;
+ }
+
+ if (!p_doc.description.is_empty()) {
+ dict["description"] = p_doc.description;
+ }
+
+ if (!p_doc.default_value.is_empty()) {
+ dict["default_value"] = p_doc.default_value;
+ }
+
+ return dict;
+ }
};
struct TutorialDoc {
@@ -373,6 +517,19 @@ public:
return doc;
}
+ static Dictionary to_dict(const TutorialDoc &p_doc) {
+ Dictionary dict;
+
+ if (!p_doc.link.is_empty()) {
+ dict["link"] = p_doc.link;
+ }
+
+ if (!p_doc.title.is_empty()) {
+ dict["title"] = p_doc.title;
+ }
+
+ return dict;
+ }
};
struct ClassDoc {
@@ -514,6 +671,117 @@ public:
return doc;
}
+ static Dictionary to_dict(const ClassDoc &p_doc) {
+ Dictionary dict;
+
+ if (!p_doc.name.is_empty()) {
+ dict["name"] = p_doc.name;
+ }
+
+ if (!p_doc.inherits.is_empty()) {
+ dict["inherits"] = p_doc.inherits;
+ }
+
+ if (!p_doc.brief_description.is_empty()) {
+ dict["brief_description"] = p_doc.brief_description;
+ }
+
+ if (!p_doc.description.is_empty()) {
+ dict["description"] = p_doc.description;
+ }
+
+ if (!p_doc.tutorials.is_empty()) {
+ Array tutorials;
+ for (int i = 0; i < p_doc.tutorials.size(); i++) {
+ tutorials.push_back(TutorialDoc::to_dict(p_doc.tutorials[i]));
+ }
+ dict["tutorials"] = tutorials;
+ }
+
+ if (!p_doc.constructors.is_empty()) {
+ Array constructors;
+ for (int i = 0; i < p_doc.constructors.size(); i++) {
+ constructors.push_back(MethodDoc::to_dict(p_doc.constructors[i]));
+ }
+ dict["constructors"] = constructors;
+ }
+
+ if (!p_doc.methods.is_empty()) {
+ Array methods;
+ for (int i = 0; i < p_doc.methods.size(); i++) {
+ methods.push_back(MethodDoc::to_dict(p_doc.methods[i]));
+ }
+ dict["methods"] = methods;
+ }
+
+ if (!p_doc.operators.is_empty()) {
+ Array operators;
+ for (int i = 0; i < p_doc.operators.size(); i++) {
+ operators.push_back(MethodDoc::to_dict(p_doc.operators[i]));
+ }
+ dict["operators"] = operators;
+ }
+
+ if (!p_doc.signals.is_empty()) {
+ Array signals;
+ for (int i = 0; i < p_doc.signals.size(); i++) {
+ signals.push_back(MethodDoc::to_dict(p_doc.signals[i]));
+ }
+ dict["signals"] = signals;
+ }
+
+ if (!p_doc.constants.is_empty()) {
+ Array constants;
+ for (int i = 0; i < p_doc.constants.size(); i++) {
+ constants.push_back(ConstantDoc::to_dict(p_doc.constants[i]));
+ }
+ dict["constants"] = constants;
+ }
+
+ if (!p_doc.enums.is_empty()) {
+ Dictionary enums;
+ for (const KeyValue<String, String> &E : p_doc.enums) {
+ enums[E.key] = E.value;
+ }
+ dict["enums"] = enums;
+ }
+
+ if (!p_doc.properties.is_empty()) {
+ Array properties;
+ for (int i = 0; i < p_doc.properties.size(); i++) {
+ properties.push_back(PropertyDoc::to_dict(p_doc.properties[i]));
+ }
+ dict["properties"] = properties;
+ }
+
+ if (!p_doc.annotations.is_empty()) {
+ Array annotations;
+ for (int i = 0; i < p_doc.annotations.size(); i++) {
+ annotations.push_back(MethodDoc::to_dict(p_doc.annotations[i]));
+ }
+ dict["annotations"] = annotations;
+ }
+
+ if (!p_doc.theme_properties.is_empty()) {
+ Array theme_properties;
+ for (int i = 0; i < p_doc.theme_properties.size(); i++) {
+ theme_properties.push_back(ThemeItemDoc::to_dict(p_doc.theme_properties[i]));
+ }
+ dict["theme_properties"] = theme_properties;
+ }
+
+ dict["is_deprecated"] = p_doc.is_deprecated;
+
+ dict["is_experimental"] = p_doc.is_experimental;
+
+ dict["is_script_doc"] = p_doc.is_script_doc;
+
+ if (!p_doc.script_path.is_empty()) {
+ dict["script_path"] = p_doc.script_path;
+ }
+
+ return dict;
+ }
};
static String get_default_value_string(const Variant &p_value);
diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp
index 79b0ebc641..c67867f65d 100644
--- a/core/extension/extension_api_dump.cpp
+++ b/core/extension/extension_api_dump.cpp
@@ -880,6 +880,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() {
d2["is_virtual"] = false;
d2["hash"] = method->get_hash();
+ Vector<uint32_t> compat_hashes = ClassDB::get_method_compatibility_hashes(class_name, method_name);
+ if (compat_hashes.size()) {
+ Array compatibility;
+ for (int i = 0; i < compat_hashes.size(); i++) {
+ compatibility.push_back(compat_hashes[i]);
+ }
+ d2["hash_compatibility"] = compatibility;
+ }
+
Vector<Variant> default_args = method->get_default_arguments();
Array arguments;
@@ -1056,4 +1065,352 @@ void GDExtensionAPIDump::generate_extension_json_file(const String &p_path) {
fa->store_string(text);
}
+static bool compare_value(const String &p_path, const String &p_field, const Variant &p_old_value, const Variant &p_new_value, bool p_allow_name_change) {
+ bool failed = false;
+ String path = p_path + "/" + p_field;
+ if (p_old_value.get_type() == Variant::ARRAY && p_new_value.get_type() == Variant::ARRAY) {
+ Array old_array = p_old_value;
+ Array new_array = p_new_value;
+ if (!compare_value(path, "size", old_array.size(), new_array.size(), p_allow_name_change)) {
+ failed = true;
+ }
+ for (int i = 0; i < old_array.size() && i < new_array.size(); i++) {
+ if (!compare_value(path, itos(i), old_array[i], new_array[i], p_allow_name_change)) {
+ failed = true;
+ }
+ }
+ } else if (p_old_value.get_type() == Variant::DICTIONARY && p_new_value.get_type() == Variant::DICTIONARY) {
+ Dictionary old_dict = p_old_value;
+ Dictionary new_dict = p_new_value;
+ Array old_keys = old_dict.keys();
+ for (int i = 0; i < old_keys.size(); i++) {
+ Variant key = old_keys[i];
+ if (!new_dict.has(key)) {
+ failed = true;
+ print_error(vformat("Validate extension JSON: Error: Field '%s': %s was removed.", p_path, key));
+ continue;
+ }
+ if (p_allow_name_change && key == "name") {
+ continue;
+ }
+ if (!compare_value(path, key, old_dict[key], new_dict[key], p_allow_name_change)) {
+ failed = true;
+ }
+ }
+ Array new_keys = old_dict.keys();
+ for (int i = 0; i < new_keys.size(); i++) {
+ Variant key = new_keys[i];
+ if (!old_dict.has(key)) {
+ failed = true;
+ print_error(vformat("Validate extension JSON: Error: Field '%s': %s was added with value %s.", p_path, key, new_dict[key]));
+ }
+ }
+ } else {
+ bool equal = Variant::evaluate(Variant::OP_EQUAL, p_old_value, p_new_value);
+ if (!equal) {
+ print_error(vformat("Validate extension JSON: Error: Field '%s': %s changed value in new API, from %s to %s.", p_path, p_field, p_old_value.get_construct_string(), p_new_value.get_construct_string()));
+ return false;
+ }
+ }
+ return !failed;
+}
+
+static bool compare_dict_array(const Dictionary &p_old_api, const Dictionary &p_new_api, const String &p_base_array, const String &p_name_field, const Vector<String> &p_fields_to_compare, bool p_compare_hashes, const String &p_outer_class = String(), bool p_compare_operators = false, bool p_compare_enum_value = false) {
+ String base_array = p_outer_class + p_base_array;
+ if (!p_old_api.has(p_base_array)) {
+ return true; // May just not have this array and its still good. Probably added recently.
+ }
+ bool failed = false;
+ ERR_FAIL_COND_V_MSG(!p_new_api.has(p_base_array), false, "New API lacks base array: " + p_base_array);
+ Array new_api = p_new_api[p_base_array];
+ HashMap<String, Dictionary> new_api_assoc;
+
+ for (int i = 0; i < new_api.size(); i++) {
+ Dictionary elem = new_api[i];
+ ERR_FAIL_COND_V_MSG(!elem.has(p_name_field), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", base_array, p_name_field));
+ String name = elem[p_name_field];
+ if (p_compare_operators && elem.has("right_type")) {
+ name += " " + String(elem["right_type"]);
+ }
+ new_api_assoc.insert(name, elem);
+ }
+
+ Array old_api = p_old_api[p_base_array];
+ for (int i = 0; i < old_api.size(); i++) {
+ Dictionary old_elem = old_api[i];
+ if (!old_elem.has(p_name_field)) {
+ failed = true;
+ print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: '%s'.", base_array, p_name_field));
+ continue;
+ }
+ String name = old_elem[p_name_field];
+ if (p_compare_operators && old_elem.has("right_type")) {
+ name += " " + String(old_elem["right_type"]);
+ }
+ if (!new_api_assoc.has(name)) {
+ failed = true;
+ print_error(vformat("Validate extension JSON: API was removed: %s/%s", base_array, name));
+ continue;
+ }
+
+ Dictionary new_elem = new_api_assoc[name];
+
+ for (int j = 0; j < p_fields_to_compare.size(); j++) {
+ String field = p_fields_to_compare[j];
+ bool optional = field.begins_with("*");
+ if (optional) {
+ // This is an optional field, but if exists it has to exist in both.
+ field = field.substr(1, field.length());
+ }
+
+ bool added = field.begins_with("+");
+ if (added) {
+ // Meaning this field must either exist or contents may not exist.
+ field = field.substr(1, field.length());
+ }
+
+ bool enum_values = field.begins_with("$");
+ if (enum_values) {
+ // Meaning this field is a list of enum values.
+ field = field.substr(1, field.length());
+ }
+
+ bool allow_name_change = field.begins_with("@");
+ if (allow_name_change) {
+ // Meaning that when structurally comparing the old and new value, the dictionary entry 'name' may change.
+ field = field.substr(1, field.length());
+ }
+
+ Variant old_value;
+
+ if (!old_elem.has(field)) {
+ if (optional) {
+ if (new_elem.has(field)) {
+ failed = true;
+ print_error(vformat("Validate extension JSON: JSON file: Field was added in a way that breaks compatibility '%s/%s': %s", base_array, name, field));
+ }
+ } else if (added && new_elem.has(field)) {
+ // Should be ok, field now exists, should not be verified in prior versions where it does not.
+ } else {
+ failed = true;
+ print_error(vformat("Validate extension JSON: JSON file: Missing field in '%s/%s': %s", base_array, name, field));
+ }
+ continue;
+ } else {
+ old_value = old_elem[field];
+ }
+
+ if (!new_elem.has(field)) {
+ failed = true;
+ ERR_PRINT(vformat("Validate extension JSON: Missing field in current API '%s/%s': %s. This is a bug.", base_array, name, field));
+ continue;
+ }
+
+ Variant new_value = new_elem[field];
+
+ if (p_compare_enum_value && name.ends_with("_MAX")) {
+ if (static_cast<int64_t>(new_value) > static_cast<int64_t>(old_value)) {
+ // Ignore the _MAX value of an enum increasing.
+ continue;
+ }
+ }
+ if (enum_values) {
+ if (!compare_dict_array(old_elem, new_elem, field, "name", { "value" }, false, base_array + "/" + name + "/", false, true)) {
+ failed = true;
+ }
+ } else if (!compare_value(base_array + "/" + name, field, old_value, new_value, allow_name_change)) {
+ failed = true;
+ }
+ }
+
+ if (p_compare_hashes) {
+ if (!old_elem.has("hash")) {
+ if (old_elem.has("is_virtual") && bool(old_elem["is_virtual"]) && !new_elem.has("hash")) {
+ continue; // No hash for virtual methods, go on.
+ }
+
+ failed = true;
+ print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: 'hash'.", base_array));
+ continue;
+ }
+
+ uint64_t old_hash = old_elem["hash"];
+
+ if (!new_elem.has("hash")) {
+ failed = true;
+ print_error(vformat("Validate extension JSON: Error: Field '%s' is missing the field: 'hash'.", base_array));
+ continue;
+ }
+
+ uint64_t new_hash = new_elem["hash"];
+ bool hash_found = false;
+ if (old_hash == new_hash) {
+ hash_found = true;
+ } else if (new_elem.has("hash_compatibility")) {
+ Array compatibility = new_elem["hash_compatibility"];
+ for (int j = 0; j < compatibility.size(); j++) {
+ new_hash = compatibility[j];
+ if (new_hash == old_hash) {
+ hash_found = true;
+ break;
+ }
+ }
+ }
+
+ if (!hash_found) {
+ failed = true;
+ print_error(vformat("Validate extension JSON: Error: Hash changed for '%s/%s', from %08X to %08X. This means that the function has changed and no compatibility function was provided.", base_array, name, old_hash, new_hash));
+ continue;
+ }
+ }
+ }
+
+ return !failed;
+}
+
+static bool compare_sub_dict_array(HashSet<String> &r_removed_classes_registered, const String &p_outer, const String &p_outer_name, const Dictionary &p_old_api, const Dictionary &p_new_api, const String &p_base_array, const String &p_name_field, const Vector<String> &p_fields_to_compare, bool p_compare_hashes, bool p_compare_operators = false) {
+ if (!p_old_api.has(p_outer)) {
+ return true; // May just not have this array and its still good. Probably added recently or optional.
+ }
+ bool failed = false;
+ ERR_FAIL_COND_V_MSG(!p_new_api.has(p_outer), false, "New API lacks base array: " + p_outer);
+ Array new_api = p_new_api[p_outer];
+ HashMap<String, Dictionary> new_api_assoc;
+
+ for (int i = 0; i < new_api.size(); i++) {
+ Dictionary elem = new_api[i];
+ ERR_FAIL_COND_V_MSG(!elem.has(p_outer_name), false, vformat("Validate extension JSON: Element of base_array '%s' is missing field '%s'. This is a bug.", p_outer, p_outer_name));
+ new_api_assoc.insert(elem[p_outer_name], elem);
+ }
+
+ Array old_api = p_old_api[p_outer];
+
+ for (int i = 0; i < old_api.size(); i++) {
+ Dictionary old_elem = old_api[i];
+ if (!old_elem.has(p_outer_name)) {
+ failed = true;
+ print_error(vformat("Validate extension JSON: JSON file: element of base array '%s' is missing the field: '%s'.", p_outer, p_outer_name));
+ continue;
+ }
+ String name = old_elem[p_outer_name];
+ if (!new_api_assoc.has(name)) {
+ failed = true;
+ if (!r_removed_classes_registered.has(name)) {
+ print_error(vformat("Validate extension JSON: API was removed: %s/%s", p_outer, name));
+ r_removed_classes_registered.insert(name);
+ }
+ continue;
+ }
+
+ Dictionary new_elem = new_api_assoc[name];
+
+ if (!compare_dict_array(old_elem, new_elem, p_base_array, p_name_field, p_fields_to_compare, p_compare_hashes, p_outer + "/" + name + "/", p_compare_operators)) {
+ failed = true;
+ }
+ }
+
+ return !failed;
+}
+
+Error GDExtensionAPIDump::validate_extension_json_file(const String &p_path) {
+ Error error;
+ String text = FileAccess::get_file_as_string(p_path, &error);
+ if (error != OK) {
+ ERR_PRINT(vformat("Validate extension JSON: Could not open file '%s'.", p_path));
+ return error;
+ }
+
+ Ref<JSON> json;
+ json.instantiate();
+ error = json->parse(text);
+ if (error != OK) {
+ ERR_PRINT(vformat("Validate extension JSON: Error parsing '%s' at line %d: %s", p_path, json->get_error_line(), json->get_error_message()));
+ return error;
+ }
+
+ Dictionary old_api = json->get_data();
+ Dictionary new_api = generate_extension_api();
+
+ { // Validate header:
+ Dictionary header = old_api["header"];
+ ERR_FAIL_COND_V(!header.has("version_major"), ERR_INVALID_DATA);
+ ERR_FAIL_COND_V(!header.has("version_minor"), ERR_INVALID_DATA);
+ int major = header["version_major"];
+ int minor = header["version_minor"];
+
+ ERR_FAIL_COND_V_MSG(major != VERSION_MAJOR, ERR_INVALID_DATA, vformat("JSON API dump is for a different engine version (%d) than this one (%d)", major, VERSION_MAJOR));
+ ERR_FAIL_COND_V_MSG(minor > VERSION_MINOR, ERR_INVALID_DATA, vformat("JSON API dump is for a newer version of the engine: %d.%d", major, minor));
+ }
+
+ bool failed = false;
+
+ HashSet<String> removed_classes_registered;
+
+ if (!compare_dict_array(old_api, new_api, "global_constants", "name", Vector<String>({ "value", "is_bitfield" }), false)) {
+ failed = true;
+ }
+
+ if (!compare_dict_array(old_api, new_api, "global_enums", "name", Vector<String>({ "$values", "is_bitfield" }), false)) {
+ failed = true;
+ }
+
+ if (!compare_dict_array(old_api, new_api, "utility_functions", "name", Vector<String>({ "category", "is_vararg", "*return_type", "*@arguments" }), true)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "members", "name", { "type" }, false)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "constants", "name", { "type", "value" }, false)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "operators", "name", { "return_type" }, false, true)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "methods", "name", { "is_vararg", "is_static", "is_const", "*return_type", "*@arguments" }, true)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "builtin_classes", "name", old_api, new_api, "constructors", "index", { "*@arguments" }, false)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "constants", "name", { "value" }, false)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "enums", "name", { "is_bitfield", "$values" }, false)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "methods", "name", { "is_virtual", "is_vararg", "is_static", "is_const", "*return_value", "*@arguments" }, true)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "signals", "name", { "*@arguments" }, false)) {
+ failed = true;
+ }
+
+ if (!compare_sub_dict_array(removed_classes_registered, "classes", "name", old_api, new_api, "properties", "name", { "type", "*setter", "*getter", "*index" }, false)) {
+ failed = true;
+ }
+
+ if (!compare_dict_array(old_api, new_api, "singletons", "name", Vector<String>({ "type" }), false)) {
+ failed = true;
+ }
+
+ if (!compare_dict_array(old_api, new_api, "native_structures", "name", Vector<String>({ "format" }), false)) {
+ failed = true;
+ }
+
+ if (failed) {
+ return ERR_INVALID_DATA;
+ } else {
+ return OK;
+ }
+}
+
#endif // TOOLS_ENABLED
diff --git a/core/extension/extension_api_dump.h b/core/extension/extension_api_dump.h
index 7e588c9446..11ea2cf923 100644
--- a/core/extension/extension_api_dump.h
+++ b/core/extension/extension_api_dump.h
@@ -39,6 +39,7 @@ class GDExtensionAPIDump {
public:
static Dictionary generate_extension_api();
static void generate_extension_json_file(const String &p_path);
+ static Error validate_extension_json_file(const String &p_path);
};
#endif
diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp
index 829e1d8e5b..73526fae3e 100644
--- a/core/extension/gdextension.cpp
+++ b/core/extension/gdextension.cpp
@@ -34,6 +34,12 @@
#include "core/object/class_db.h"
#include "core/object/method_bind.h"
#include "core/os/os.h"
+#include "core/version.h"
+
+extern void gdextension_setup_interface();
+extern GDExtensionInterfaceFunctionPtr gdextension_get_proc_address(const char *p_name);
+
+typedef GDExtensionBool (*GDExtensionLegacyInitializationFunction)(void *p_interface, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization);
String GDExtension::get_extension_list_config_file() {
return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg");
@@ -146,9 +152,11 @@ String GDExtension::find_extension_library(const String &p_path, Ref<ConfigFile>
class GDExtensionMethodBind : public MethodBind {
GDExtensionClassMethodCall call_func;
+ GDExtensionClassMethodValidatedCall validated_call_func;
GDExtensionClassMethodPtrCall ptrcall_func;
void *method_userdata;
bool vararg;
+ uint32_t argument_count;
PropertyInfo return_value_info;
GodotTypeInfo::Metadata return_value_metadata;
List<PropertyInfo> arguments_info;
@@ -191,6 +199,40 @@ public:
r_error.expected = ce.expected;
return ret;
}
+ virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
+ ERR_FAIL_COND_MSG(vararg, "Validated methods don't have ptrcall support. This is most likely an engine bug.");
+ GDExtensionClassInstancePtr extension_instance = is_static() ? nullptr : p_object->_get_extension_instance();
+
+ if (validated_call_func) {
+ // This is added here, but it's unlikely to be provided by most extensions.
+ validated_call_func(method_userdata, extension_instance, reinterpret_cast<GDExtensionConstVariantPtr *>(p_args), (GDExtensionVariantPtr)r_ret);
+ } else {
+#if 1
+ // Slow code-path, but works for the time being.
+ Callable::CallError ce;
+ call(p_object, p_args, argument_count, ce);
+#else
+ // This is broken, because it needs more information to do the calling properly
+
+ // If not provided, go via ptrcall, which is faster than resorting to regular call.
+ const void **argptrs = (const void **)alloca(argument_count * sizeof(void *));
+ for (uint32_t i = 0; i < argument_count; i++) {
+ argptrs[i] = VariantInternal::get_opaque_pointer(p_args[i]);
+ }
+
+ bool returns = true;
+ void *ret_opaque;
+ if (returns) {
+ ret_opaque = VariantInternal::get_opaque_pointer(r_ret);
+ } else {
+ ret_opaque = nullptr; // May be unnecessary as this is ignored, but just in case.
+ }
+
+ ptrcall(p_object, argptrs, ret_opaque);
+#endif
+ }
+ }
+
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
ERR_FAIL_COND_MSG(vararg, "Vararg methods don't have ptrcall support. This is most likely an engine bug.");
GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance();
@@ -204,6 +246,7 @@ public:
explicit GDExtensionMethodBind(const GDExtensionClassMethodInfo *p_method_info) {
method_userdata = p_method_info->method_userdata;
call_func = p_method_info->call_func;
+ validated_call_func = nullptr;
ptrcall_func = p_method_info->ptrcall_func;
set_name(*reinterpret_cast<StringName *>(p_method_info->name));
@@ -218,7 +261,7 @@ public:
}
set_hint_flags(p_method_info->method_flags);
-
+ argument_count = p_method_info->argument_count;
vararg = p_method_info->method_flags & GDEXTENSION_METHOD_FLAG_VARARG;
_set_returns(p_method_info->has_return_value);
_set_const(p_method_info->method_flags & GDEXTENSION_METHOD_FLAG_CONST);
@@ -238,8 +281,6 @@ public:
}
};
-static GDExtensionInterface gdextension_interface;
-
void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs) {
GDExtension *self = reinterpret_cast<GDExtension *>(p_library);
@@ -272,6 +313,7 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library
parent_extension->gdextension.children.push_back(&extension->gdextension);
}
+ extension->gdextension.library = self;
extension->gdextension.parent_class_name = parent_class_name;
extension->gdextension.class_name = class_name;
extension->gdextension.editor_class = self->level_initialized == INITIALIZATION_LEVEL_EDITOR;
@@ -388,10 +430,23 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra
self->extension_classes.erase(class_name);
}
-void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionStringPtr r_path) {
+void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringPtr r_path) {
GDExtension *self = reinterpret_cast<GDExtension *>(p_library);
- *(String *)r_path = self->library_path;
+ memnew_placement(r_path, String(self->library_path));
+}
+
+HashMap<StringName, GDExtensionInterfaceFunctionPtr> gdextension_interface_functions;
+
+void GDExtension::register_interface_function(StringName p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) {
+ ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), "Attempt to register interface function '" + p_function_name + "', which appears to be already registered.");
+ gdextension_interface_functions.insert(p_function_name, p_function_pointer);
+}
+
+GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(StringName p_function_name) {
+ GDExtensionInterfaceFunctionPtr *function = gdextension_interface_functions.getptr(p_function_name);
+ ERR_FAIL_COND_V_MSG(function == nullptr, nullptr, "Attempt to get non-existent interface function: " + p_function_name);
+ return *function;
}
Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol) {
@@ -412,8 +467,9 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb
}
GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr;
+ GDExtensionBool ret = initialization_function(&gdextension_get_proc_address, this, &initialization);
- if (initialization_function(&gdextension_interface, this, &initialization)) {
+ if (ret) {
level_initialized = -1;
return OK;
} else {
@@ -479,20 +535,18 @@ GDExtension::~GDExtension() {
}
}
-extern void gdextension_setup_interface(GDExtensionInterface *p_interface);
-
void GDExtension::initialize_gdextensions() {
- gdextension_setup_interface(&gdextension_interface);
-
- gdextension_interface.classdb_register_extension_class = _register_extension_class;
- gdextension_interface.classdb_register_extension_class_method = _register_extension_class_method;
- gdextension_interface.classdb_register_extension_class_integer_constant = _register_extension_class_integer_constant;
- gdextension_interface.classdb_register_extension_class_property = _register_extension_class_property;
- gdextension_interface.classdb_register_extension_class_property_group = _register_extension_class_property_group;
- gdextension_interface.classdb_register_extension_class_property_subgroup = _register_extension_class_property_subgroup;
- gdextension_interface.classdb_register_extension_class_signal = _register_extension_class_signal;
- gdextension_interface.classdb_unregister_extension_class = _unregister_extension_class;
- gdextension_interface.get_library_path = _get_library_path;
+ gdextension_setup_interface();
+
+ register_interface_function("classdb_register_extension_class", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class);
+ register_interface_function("classdb_register_extension_class_method", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_method);
+ register_interface_function("classdb_register_extension_class_integer_constant", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_integer_constant);
+ register_interface_function("classdb_register_extension_class_property", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property);
+ register_interface_function("classdb_register_extension_class_property_group", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property_group);
+ register_interface_function("classdb_register_extension_class_property_subgroup", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property_subgroup);
+ register_interface_function("classdb_register_extension_class_signal", (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_signal);
+ register_interface_function("classdb_unregister_extension_class", (GDExtensionInterfaceFunctionPtr)&GDExtension::_unregister_extension_class);
+ register_interface_function("get_library_path", (GDExtensionInterfaceFunctionPtr)&GDExtension::_get_library_path);
}
Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
@@ -520,6 +574,50 @@ Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String
String entry_symbol = config->get_value("configuration", "entry_symbol");
+ uint32_t compatibility_minimum[3] = { 0, 0, 0 };
+ if (config->has_section_key("configuration", "compatibility_minimum")) {
+ String compat_string = config->get_value("configuration", "compatibility_minimum");
+ Vector<int> parts = compat_string.split_ints(".");
+ for (int i = 0; i < parts.size(); i++) {
+ if (i >= 3) {
+ break;
+ }
+ if (parts[i] >= 0) {
+ compatibility_minimum[i] = parts[i];
+ }
+ }
+ } else {
+ if (r_error) {
+ *r_error = ERR_INVALID_DATA;
+ }
+ ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path);
+ return Ref<Resource>();
+ }
+
+ if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) {
+ if (r_error) {
+ *r_error = ERR_INVALID_DATA;
+ }
+ ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path));
+ return Ref<Resource>();
+ }
+
+ bool compatible = true;
+ if (VERSION_MAJOR < compatibility_minimum[0]) {
+ compatible = false;
+ } else if (VERSION_MINOR < compatibility_minimum[1]) {
+ compatible = false;
+ } else if (VERSION_PATCH < compatibility_minimum[2]) {
+ compatible = false;
+ }
+ if (!compatible) {
+ if (r_error) {
+ *r_error = ERR_INVALID_DATA;
+ }
+ ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path));
+ return Ref<Resource>();
+ }
+
String library_path = GDExtension::find_extension_library(p_path, config, [](String p_feature) { return OS::get_singleton()->has_feature(p_feature); });
if (library_path.is_empty()) {
@@ -549,6 +647,15 @@ Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String
return Ref<Resource>();
}
+ // Handle icons if any are specified.
+ if (config->has_section("icons")) {
+ List<String> keys;
+ config->get_section_keys("icons", &keys);
+ for (const String &key : keys) {
+ lib->class_icon_paths[key] = config->get_value("icons", key);
+ }
+ }
+
return lib;
}
@@ -567,3 +674,25 @@ String GDExtensionResourceLoader::get_resource_type(const String &p_path) const
}
return "";
}
+
+#ifdef TOOLS_ENABLED
+Vector<StringName> GDExtensionEditorPlugins::extension_classes;
+GDExtensionEditorPlugins::EditorPluginRegisterFunc GDExtensionEditorPlugins::editor_node_add_plugin = nullptr;
+GDExtensionEditorPlugins::EditorPluginRegisterFunc GDExtensionEditorPlugins::editor_node_remove_plugin = nullptr;
+
+void GDExtensionEditorPlugins::add_extension_class(const StringName &p_class_name) {
+ if (editor_node_add_plugin) {
+ editor_node_add_plugin(p_class_name);
+ } else {
+ extension_classes.push_back(p_class_name);
+ }
+}
+
+void GDExtensionEditorPlugins::remove_extension_class(const StringName &p_class_name) {
+ if (editor_node_remove_plugin) {
+ editor_node_remove_plugin(p_class_name);
+ } else {
+ extension_classes.erase(p_class_name);
+ }
+}
+#endif // TOOLS_ENABLED
diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h
index 39523a142f..77ec458d30 100644
--- a/core/extension/gdextension.h
+++ b/core/extension/gdextension.h
@@ -67,6 +67,8 @@ protected:
static void _bind_methods();
public:
+ HashMap<String, String> class_icon_paths;
+
static String get_extension_list_config_file();
static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr);
@@ -86,7 +88,10 @@ public:
void initialize_library(InitializationLevel p_level);
void deinitialize_library(InitializationLevel p_level);
+ static void register_interface_function(StringName p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer);
+ static GDExtensionInterfaceFunctionPtr get_interface_function(StringName p_function_name);
static void initialize_gdextensions();
+
GDExtension();
~GDExtension();
};
@@ -101,4 +106,28 @@ public:
virtual String get_resource_type(const String &p_path) const;
};
+#ifdef TOOLS_ENABLED
+class GDExtensionEditorPlugins {
+private:
+ static Vector<StringName> extension_classes;
+
+protected:
+ friend class EditorNode;
+
+ // Since this in core, we can't directly reference EditorNode, so it will
+ // set these function pointers in its constructor.
+ typedef void (*EditorPluginRegisterFunc)(const StringName &p_class_name);
+ static EditorPluginRegisterFunc editor_node_add_plugin;
+ static EditorPluginRegisterFunc editor_node_remove_plugin;
+
+public:
+ static void add_extension_class(const StringName &p_class_name);
+ static void remove_extension_class(const StringName &p_class_name);
+
+ static const Vector<StringName> &get_extension_classes() {
+ return extension_classes;
+ }
+};
+#endif // TOOLS_ENABLED
+
#endif // GDEXTENSION_H
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 2bedb623e4..7fbf2d00a1 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -31,6 +31,7 @@
#include "gdextension_interface.h"
#include "core/config/engine.h"
+#include "core/extension/gdextension.h"
#include "core/io/file_access.h"
#include "core/io/xml_parser.h"
#include "core/object/class_db.h"
@@ -40,16 +41,28 @@
#include "core/variant/variant.h"
#include "core/version.h"
+// Core interface functions.
+GDExtensionInterfaceFunctionPtr gdextension_get_proc_address(const char *p_name) {
+ return GDExtension::get_interface_function(p_name);
+}
+
+static void gdextension_get_godot_version(GDExtensionGodotVersion *r_godot_version) {
+ r_godot_version->major = VERSION_MAJOR;
+ r_godot_version->minor = VERSION_MINOR;
+ r_godot_version->patch = VERSION_PATCH;
+ r_godot_version->string = VERSION_FULL_NAME;
+}
+
// Memory Functions
-static void *gdextension_alloc(size_t p_size) {
+static void *gdextension_mem_alloc(size_t p_size) {
return memalloc(p_size);
}
-static void *gdextension_realloc(void *p_mem, size_t p_size) {
+static void *gdextension_mem_realloc(void *p_mem, size_t p_size) {
return memrealloc(p_mem, p_size);
}
-static void gdextension_free(void *p_mem) {
+static void gdextension_mem_free(void *p_mem) {
memfree(p_mem);
}
@@ -80,10 +93,10 @@ uint64_t gdextension_get_native_struct_size(GDExtensionConstStringNamePtr p_name
// Variant functions
-static void gdextension_variant_new_copy(GDExtensionVariantPtr r_dest, GDExtensionConstVariantPtr p_src) {
+static void gdextension_variant_new_copy(GDExtensionUninitializedVariantPtr r_dest, GDExtensionConstVariantPtr p_src) {
memnew_placement(reinterpret_cast<Variant *>(r_dest), Variant(*reinterpret_cast<const Variant *>(p_src)));
}
-static void gdextension_variant_new_nil(GDExtensionVariantPtr r_dest) {
+static void gdextension_variant_new_nil(GDExtensionUninitializedVariantPtr r_dest) {
memnew_placement(reinterpret_cast<Variant *>(r_dest), Variant);
}
static void gdextension_variant_destroy(GDExtensionVariantPtr p_self) {
@@ -92,14 +105,14 @@ static void gdextension_variant_destroy(GDExtensionVariantPtr p_self) {
// variant type
-static void gdextension_variant_call(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argcount, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) {
+static void gdextension_variant_call(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argcount, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) {
Variant *self = (Variant *)p_self;
const StringName method = *reinterpret_cast<const StringName *>(p_method);
const Variant **args = (const Variant **)p_args;
- Variant ret;
Callable::CallError error;
- self->callp(method, args, p_argcount, ret, error);
- memnew_placement(r_return, Variant(ret));
+ memnew_placement(r_return, Variant);
+ Variant *ret = reinterpret_cast<Variant *>(r_return);
+ self->callp(method, args, p_argcount, *ret, error);
if (r_error) {
r_error->error = (GDExtensionCallErrorType)(error.error);
@@ -108,14 +121,14 @@ static void gdextension_variant_call(GDExtensionVariantPtr p_self, GDExtensionCo
}
}
-static void gdextension_variant_call_static(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argcount, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) {
+static void gdextension_variant_call_static(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argcount, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) {
Variant::Type type = (Variant::Type)p_type;
const StringName method = *reinterpret_cast<const StringName *>(p_method);
const Variant **args = (const Variant **)p_args;
- Variant ret;
Callable::CallError error;
- Variant::call_static(type, method, args, p_argcount, ret, error);
- memnew_placement(r_return, Variant(ret));
+ memnew_placement(r_return, Variant);
+ Variant *ret = reinterpret_cast<Variant *>(r_return);
+ Variant::call_static(type, method, args, p_argcount, *ret, error);
if (r_error) {
r_error->error = (GDExtensionCallErrorType)error.error;
@@ -124,12 +137,13 @@ static void gdextension_variant_call_static(GDExtensionVariantType p_type, GDExt
}
}
-static void gdextension_variant_evaluate(GDExtensionVariantOperator p_op, GDExtensionConstVariantPtr p_a, GDExtensionConstVariantPtr p_b, GDExtensionVariantPtr r_return, GDExtensionBool *r_valid) {
+static void gdextension_variant_evaluate(GDExtensionVariantOperator p_op, GDExtensionConstVariantPtr p_a, GDExtensionConstVariantPtr p_b, GDExtensionUninitializedVariantPtr r_return, GDExtensionBool *r_valid) {
Variant::Operator op = (Variant::Operator)p_op;
const Variant *a = (const Variant *)p_a;
const Variant *b = (const Variant *)p_b;
- Variant *ret = (Variant *)r_return;
bool valid;
+ memnew_placement(r_return, Variant);
+ Variant *ret = reinterpret_cast<Variant *>(r_return);
Variant::evaluate(op, *a, *b, *ret, valid);
*r_valid = valid;
}
@@ -175,7 +189,7 @@ static void gdextension_variant_set_indexed(GDExtensionVariantPtr p_self, GDExte
*r_oob = oob;
}
-static void gdextension_variant_get(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid) {
+static void gdextension_variant_get(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid) {
const Variant *self = (const Variant *)p_self;
const Variant *key = (const Variant *)p_key;
@@ -184,7 +198,7 @@ static void gdextension_variant_get(GDExtensionConstVariantPtr p_self, GDExtensi
*r_valid = valid;
}
-static void gdextension_variant_get_named(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid) {
+static void gdextension_variant_get_named(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid) {
const Variant *self = (const Variant *)p_self;
const StringName *key = (const StringName *)p_key;
@@ -193,7 +207,7 @@ static void gdextension_variant_get_named(GDExtensionConstVariantPtr p_self, GDE
*r_valid = valid;
}
-static void gdextension_variant_get_keyed(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid) {
+static void gdextension_variant_get_keyed(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid) {
const Variant *self = (const Variant *)p_self;
const Variant *key = (const Variant *)p_key;
@@ -202,7 +216,7 @@ static void gdextension_variant_get_keyed(GDExtensionConstVariantPtr p_self, GDE
*r_valid = valid;
}
-static void gdextension_variant_get_indexed(GDExtensionConstVariantPtr p_self, GDExtensionInt p_index, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid, GDExtensionBool *r_oob) {
+static void gdextension_variant_get_indexed(GDExtensionConstVariantPtr p_self, GDExtensionInt p_index, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid, GDExtensionBool *r_oob) {
const Variant *self = (const Variant *)p_self;
bool valid;
@@ -213,9 +227,10 @@ static void gdextension_variant_get_indexed(GDExtensionConstVariantPtr p_self, G
}
/// Iteration.
-static GDExtensionBool gdextension_variant_iter_init(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionBool *r_valid) {
+static GDExtensionBool gdextension_variant_iter_init(GDExtensionConstVariantPtr p_self, GDExtensionUninitializedVariantPtr r_iter, GDExtensionBool *r_valid) {
const Variant *self = (const Variant *)p_self;
- Variant *iter = (Variant *)r_iter;
+ memnew_placement(r_iter, Variant);
+ Variant *iter = reinterpret_cast<Variant *>(r_iter);
bool valid;
bool ret = self->iter_init(*iter, valid);
@@ -233,7 +248,7 @@ static GDExtensionBool gdextension_variant_iter_next(GDExtensionConstVariantPtr
return ret;
}
-static void gdextension_variant_iter_get(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid) {
+static void gdextension_variant_iter_get(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid) {
const Variant *self = (const Variant *)p_self;
Variant *iter = (Variant *)r_iter;
@@ -264,12 +279,12 @@ static GDExtensionBool gdextension_variant_booleanize(GDExtensionConstVariantPtr
return self->booleanize();
}
-static void gdextension_variant_duplicate(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_ret, GDExtensionBool p_deep) {
+static void gdextension_variant_duplicate(GDExtensionConstVariantPtr p_self, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool p_deep) {
const Variant *self = (const Variant *)p_self;
memnew_placement(r_ret, Variant(self->duplicate(p_deep)));
}
-static void gdextension_variant_stringify(GDExtensionConstVariantPtr p_self, GDExtensionStringPtr r_ret) {
+static void gdextension_variant_stringify(GDExtensionConstVariantPtr p_self, GDExtensionUninitializedVariantPtr r_ret) {
const Variant *self = (const Variant *)p_self;
memnew_placement(r_ret, String(*self));
}
@@ -298,7 +313,7 @@ static GDExtensionBool gdextension_variant_has_key(GDExtensionConstVariantPtr p_
return ret;
}
-static void gdextension_variant_get_type_name(GDExtensionVariantType p_type, GDExtensionStringPtr r_ret) {
+static void gdextension_variant_get_type_name(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_ret) {
String name = Variant::get_type_name((Variant::Type)p_type);
memnew_placement(r_ret, String(name));
}
@@ -395,7 +410,7 @@ static GDExtensionVariantFromTypeConstructorFunc gdextension_get_variant_from_ty
ERR_FAIL_V_MSG(nullptr, "Getting Variant conversion function with invalid type");
}
-static GDExtensionTypeFromVariantConstructorFunc gdextension_get_type_from_variant_constructor(GDExtensionVariantType p_type) {
+static GDExtensionTypeFromVariantConstructorFunc gdextension_get_variant_to_type_constructor(GDExtensionVariantType p_type) {
switch (p_type) {
case GDEXTENSION_VARIANT_TYPE_BOOL:
return VariantTypeConstructor<bool>::type_from_variant;
@@ -498,11 +513,12 @@ static GDExtensionPtrConstructor gdextension_variant_get_ptr_constructor(GDExten
static GDExtensionPtrDestructor gdextension_variant_get_ptr_destructor(GDExtensionVariantType p_type) {
return (GDExtensionPtrDestructor)Variant::get_ptr_destructor(Variant::Type(p_type));
}
-static void gdextension_variant_construct(GDExtensionVariantType p_type, GDExtensionVariantPtr p_base, const GDExtensionConstVariantPtr *p_args, int32_t p_argument_count, GDExtensionCallError *r_error) {
- memnew_placement(p_base, Variant);
+static void gdextension_variant_construct(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_base, const GDExtensionConstVariantPtr *p_args, int32_t p_argument_count, GDExtensionCallError *r_error) {
+ memnew_placement(r_base, Variant);
+ Variant *base = reinterpret_cast<Variant *>(r_base);
Callable::CallError error;
- Variant::construct(Variant::Type(p_type), *(Variant *)p_base, (const Variant **)p_args, p_argument_count, error);
+ Variant::construct(Variant::Type(p_type), *base, (const Variant **)p_args, p_argument_count, error);
if (r_error) {
r_error->error = (GDExtensionCallErrorType)(error.error);
@@ -533,7 +549,7 @@ static GDExtensionPtrKeyedGetter gdextension_variant_get_ptr_keyed_getter(GDExte
static GDExtensionPtrKeyedChecker gdextension_variant_get_ptr_keyed_checker(GDExtensionVariantType p_type) {
return (GDExtensionPtrKeyedChecker)Variant::get_member_ptr_keyed_checker(Variant::Type(p_type));
}
-static void gdextension_variant_get_constant_value(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_constant, GDExtensionVariantPtr r_ret) {
+static void gdextension_variant_get_constant_value(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_constant, GDExtensionUninitializedVariantPtr r_ret) {
StringName constant = *reinterpret_cast<const StringName *>(p_constant);
memnew_placement(r_ret, Variant(Variant::get_constant_value(Variant::Type(p_type), constant)));
}
@@ -549,77 +565,67 @@ static GDExtensionPtrUtilityFunction gdextension_variant_get_ptr_utility_functio
//string helpers
-static void gdextension_string_new_with_latin1_chars(GDExtensionStringPtr r_dest, const char *p_contents) {
- String *dest = (String *)r_dest;
- memnew_placement(dest, String);
- *dest = String(p_contents);
+static void gdextension_string_new_with_latin1_chars(GDExtensionUninitializedStringPtr r_dest, const char *p_contents) {
+ memnew_placement(r_dest, String(p_contents));
}
-static void gdextension_string_new_with_utf8_chars(GDExtensionStringPtr r_dest, const char *p_contents) {
- String *dest = (String *)r_dest;
- memnew_placement(dest, String);
+static void gdextension_string_new_with_utf8_chars(GDExtensionUninitializedStringPtr r_dest, const char *p_contents) {
+ memnew_placement(r_dest, String);
+ String *dest = reinterpret_cast<String *>(r_dest);
dest->parse_utf8(p_contents);
}
-static void gdextension_string_new_with_utf16_chars(GDExtensionStringPtr r_dest, const char16_t *p_contents) {
- String *dest = (String *)r_dest;
- memnew_placement(dest, String);
+static void gdextension_string_new_with_utf16_chars(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents) {
+ memnew_placement(r_dest, String);
+ String *dest = reinterpret_cast<String *>(r_dest);
dest->parse_utf16(p_contents);
}
-static void gdextension_string_new_with_utf32_chars(GDExtensionStringPtr r_dest, const char32_t *p_contents) {
- String *dest = (String *)r_dest;
- memnew_placement(dest, String);
- *dest = String((const char32_t *)p_contents);
+static void gdextension_string_new_with_utf32_chars(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents) {
+ memnew_placement(r_dest, String((const char32_t *)p_contents));
}
-static void gdextension_string_new_with_wide_chars(GDExtensionStringPtr r_dest, const wchar_t *p_contents) {
- String *dest = (String *)r_dest;
+static void gdextension_string_new_with_wide_chars(GDExtensionUninitializedStringPtr r_dest, const wchar_t *p_contents) {
if constexpr (sizeof(wchar_t) == 2) {
// wchar_t is 16 bit, parse.
- memnew_placement(dest, String);
+ memnew_placement(r_dest, String);
+ String *dest = reinterpret_cast<String *>(r_dest);
dest->parse_utf16((const char16_t *)p_contents);
} else {
// wchar_t is 32 bit, copy.
- memnew_placement(dest, String);
- *dest = String((const char32_t *)p_contents);
+ memnew_placement(r_dest, String((const char32_t *)p_contents));
}
}
-static void gdextension_string_new_with_latin1_chars_and_len(GDExtensionStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) {
- String *dest = (String *)r_dest;
- memnew_placement(dest, String);
- *dest = String(p_contents, p_size);
+static void gdextension_string_new_with_latin1_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) {
+ memnew_placement(r_dest, String(p_contents, p_size));
}
-static void gdextension_string_new_with_utf8_chars_and_len(GDExtensionStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) {
- String *dest = (String *)r_dest;
- memnew_placement(dest, String);
+static void gdextension_string_new_with_utf8_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size) {
+ memnew_placement(r_dest, String);
+ String *dest = reinterpret_cast<String *>(r_dest);
dest->parse_utf8(p_contents, p_size);
}
-static void gdextension_string_new_with_utf16_chars_and_len(GDExtensionStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_size) {
- String *dest = (String *)r_dest;
- memnew_placement(dest, String);
+static void gdextension_string_new_with_utf16_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_size) {
+ memnew_placement(r_dest, String);
+ String *dest = reinterpret_cast<String *>(r_dest);
dest->parse_utf16(p_contents, p_size);
}
-static void gdextension_string_new_with_utf32_chars_and_len(GDExtensionStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_size) {
- String *dest = (String *)r_dest;
- memnew_placement(dest, String);
- *dest = String((const char32_t *)p_contents, p_size);
+static void gdextension_string_new_with_utf32_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_size) {
+ memnew_placement(r_dest, String((const char32_t *)p_contents, p_size));
}
-static void gdextension_string_new_with_wide_chars_and_len(GDExtensionStringPtr r_dest, const wchar_t *p_contents, GDExtensionInt p_size) {
- String *dest = (String *)r_dest;
+static void gdextension_string_new_with_wide_chars_and_len(GDExtensionUninitializedStringPtr r_dest, const wchar_t *p_contents, GDExtensionInt p_size) {
if constexpr (sizeof(wchar_t) == 2) {
// wchar_t is 16 bit, parse.
- memnew_placement(dest, String);
+ memnew_placement(r_dest, String);
+ String *dest = reinterpret_cast<String *>(r_dest);
dest->parse_utf16((const char16_t *)p_contents, p_size);
} else {
// wchar_t is 32 bit, copy.
- memnew_placement(dest, String);
- *dest = String((const char32_t *)p_contents, p_size);
+ memnew_placement(r_dest, String((const char32_t *)p_contents, p_size));
}
}
@@ -680,13 +686,17 @@ static GDExtensionInt gdextension_string_to_wide_chars(GDExtensionConstStringPtr
static char32_t *gdextension_string_operator_index(GDExtensionStringPtr p_self, GDExtensionInt p_index) {
String *self = (String *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->length() + 1, nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->length() + 1)) {
+ return nullptr;
+ }
return &self->ptrw()[p_index];
}
static const char32_t *gdextension_string_operator_index_const(GDExtensionConstStringPtr p_self, GDExtensionInt p_index) {
const String *self = (const String *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->length() + 1, nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->length() + 1)) {
+ return nullptr;
+ }
return &self->ptr()[p_index];
}
@@ -747,121 +757,161 @@ static int64_t gdextension_worker_thread_pool_add_native_task(GDExtensionObjectP
static uint8_t *gdextension_packed_byte_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
PackedByteArray *self = (PackedByteArray *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptrw()[p_index];
}
static const uint8_t *gdextension_packed_byte_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const PackedByteArray *self = (const PackedByteArray *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptr()[p_index];
}
static GDExtensionTypePtr gdextension_packed_color_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
PackedColorArray *self = (PackedColorArray *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionTypePtr)&self->ptrw()[p_index];
}
static GDExtensionTypePtr gdextension_packed_color_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const PackedColorArray *self = (const PackedColorArray *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionTypePtr)&self->ptr()[p_index];
}
static float *gdextension_packed_float32_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
PackedFloat32Array *self = (PackedFloat32Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptrw()[p_index];
}
static const float *gdextension_packed_float32_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const PackedFloat32Array *self = (const PackedFloat32Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptr()[p_index];
}
static double *gdextension_packed_float64_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
PackedFloat64Array *self = (PackedFloat64Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptrw()[p_index];
}
static const double *gdextension_packed_float64_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const PackedFloat64Array *self = (const PackedFloat64Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptr()[p_index];
}
static int32_t *gdextension_packed_int32_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
PackedInt32Array *self = (PackedInt32Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptrw()[p_index];
}
static const int32_t *gdextension_packed_int32_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const PackedInt32Array *self = (const PackedInt32Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptr()[p_index];
}
static int64_t *gdextension_packed_int64_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
PackedInt64Array *self = (PackedInt64Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptrw()[p_index];
}
static const int64_t *gdextension_packed_int64_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const PackedInt64Array *self = (const PackedInt64Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return &self->ptr()[p_index];
}
static GDExtensionStringPtr gdextension_packed_string_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
PackedStringArray *self = (PackedStringArray *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionStringPtr)&self->ptrw()[p_index];
}
static GDExtensionStringPtr gdextension_packed_string_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const PackedStringArray *self = (const PackedStringArray *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionStringPtr)&self->ptr()[p_index];
}
static GDExtensionTypePtr gdextension_packed_vector2_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
PackedVector2Array *self = (PackedVector2Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionTypePtr)&self->ptrw()[p_index];
}
static GDExtensionTypePtr gdextension_packed_vector2_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const PackedVector2Array *self = (const PackedVector2Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionTypePtr)&self->ptr()[p_index];
}
static GDExtensionTypePtr gdextension_packed_vector3_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
PackedVector3Array *self = (PackedVector3Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionTypePtr)&self->ptrw()[p_index];
}
static GDExtensionTypePtr gdextension_packed_vector3_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const PackedVector3Array *self = (const PackedVector3Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionTypePtr)&self->ptr()[p_index];
}
static GDExtensionVariantPtr gdextension_array_operator_index(GDExtensionTypePtr p_self, GDExtensionInt p_index) {
Array *self = (Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionVariantPtr)&self->operator[](p_index);
}
static GDExtensionVariantPtr gdextension_array_operator_index_const(GDExtensionConstTypePtr p_self, GDExtensionInt p_index) {
const Array *self = (const Array *)p_self;
- ERR_FAIL_INDEX_V(p_index, self->size(), nullptr);
+ if (unlikely(p_index < 0 || p_index >= self->size())) {
+ return nullptr;
+ }
return (GDExtensionVariantPtr)&self->operator[](p_index);
}
@@ -892,14 +942,13 @@ static GDExtensionVariantPtr gdextension_dictionary_operator_index_const(GDExten
/* OBJECT API */
-static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error) {
+static void gdextension_object_method_bind_call(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error) {
const MethodBind *mb = reinterpret_cast<const MethodBind *>(p_method_bind);
Object *o = (Object *)p_instance;
const Variant **args = (const Variant **)p_args;
Callable::CallError error;
- Variant ret = mb->call(o, args, p_arg_count, error);
- memnew_placement(r_return, Variant(ret));
+ memnew_placement(r_return, Variant(mb->call(o, args, p_arg_count, error)));
if (r_error) {
r_error->error = (GDExtensionCallErrorType)(error.error);
@@ -943,6 +992,19 @@ static GDExtensionObjectPtr gdextension_object_get_instance_from_id(GDObjectInst
return (GDExtensionObjectPtr)ObjectDB::get_instance(ObjectID(p_instance_id));
}
+static GDExtensionBool gdextension_object_get_class_name(GDExtensionConstObjectPtr p_object, GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringNamePtr r_class_name) {
+ if (!p_object) {
+ return false;
+ }
+ const Object *o = (const Object *)p_object;
+
+ memnew_placement(r_class_name, StringName);
+ StringName *class_name = reinterpret_cast<StringName *>(r_class_name);
+ *class_name = o->get_class_name_for_extension((GDExtension *)p_library);
+
+ return true;
+}
+
static GDExtensionObjectPtr gdextension_object_cast_to(GDExtensionConstObjectPtr p_object, void *p_class_tag) {
if (!p_object) {
return nullptr;
@@ -984,7 +1046,12 @@ static GDExtensionScriptInstancePtr gdextension_script_instance_create(const GDE
static GDExtensionMethodBindPtr gdextension_classdb_get_method_bind(GDExtensionConstStringNamePtr p_classname, GDExtensionConstStringNamePtr p_methodname, GDExtensionInt p_hash) {
const StringName classname = *reinterpret_cast<const StringName *>(p_classname);
const StringName methodname = *reinterpret_cast<const StringName *>(p_methodname);
- MethodBind *mb = ClassDB::get_method(classname, methodname);
+ bool exists = false;
+ MethodBind *mb = ClassDB::get_method_with_compatibility(classname, methodname, p_hash, &exists);
+ if (!mb && exists) {
+ ERR_PRINT("Method '" + classname + "." + methodname + "' has changed and no compatibility fallback has been provided. Please open an issue.");
+ return nullptr;
+ }
ERR_FAIL_COND_V(!mb, nullptr);
if (mb->get_hash() != p_hash) {
ERR_PRINT("Hash mismatch for method '" + classname + "." + methodname + "'.");
@@ -1004,204 +1071,150 @@ static void *gdextension_classdb_get_class_tag(GDExtensionConstStringNamePtr p_c
return class_info ? class_info->class_ptr : nullptr;
}
-void gdextension_setup_interface(GDExtensionInterface *p_interface) {
- GDExtensionInterface &gde_interface = *p_interface;
-
- gde_interface.version_major = VERSION_MAJOR;
- gde_interface.version_minor = VERSION_MINOR;
-#if VERSION_PATCH
- gde_interface.version_patch = VERSION_PATCH;
-#else
- gde_interface.version_patch = 0;
+static void gdextension_editor_add_plugin(GDExtensionConstStringNamePtr p_classname) {
+#ifdef TOOLS_ENABLED
+ const StringName classname = *reinterpret_cast<const StringName *>(p_classname);
+ GDExtensionEditorPlugins::add_extension_class(classname);
#endif
- gde_interface.version_string = VERSION_FULL_NAME;
-
- /* GODOT CORE */
-
- gde_interface.mem_alloc = gdextension_alloc;
- gde_interface.mem_realloc = gdextension_realloc;
- gde_interface.mem_free = gdextension_free;
-
- gde_interface.print_error = gdextension_print_error;
- gde_interface.print_error_with_message = gdextension_print_error_with_message;
- gde_interface.print_warning = gdextension_print_warning;
- gde_interface.print_warning_with_message = gdextension_print_warning_with_message;
- gde_interface.print_script_error = gdextension_print_script_error;
- gde_interface.print_script_error_with_message = gdextension_print_script_error_with_message;
-
- gde_interface.get_native_struct_size = gdextension_get_native_struct_size;
-
- /* GODOT VARIANT */
-
- // variant general
- gde_interface.variant_new_copy = gdextension_variant_new_copy;
- gde_interface.variant_new_nil = gdextension_variant_new_nil;
- gde_interface.variant_destroy = gdextension_variant_destroy;
-
- gde_interface.variant_call = gdextension_variant_call;
- gde_interface.variant_call_static = gdextension_variant_call_static;
- gde_interface.variant_evaluate = gdextension_variant_evaluate;
- gde_interface.variant_set = gdextension_variant_set;
- gde_interface.variant_set_named = gdextension_variant_set_named;
- gde_interface.variant_set_keyed = gdextension_variant_set_keyed;
- gde_interface.variant_set_indexed = gdextension_variant_set_indexed;
- gde_interface.variant_get = gdextension_variant_get;
- gde_interface.variant_get_named = gdextension_variant_get_named;
- gde_interface.variant_get_keyed = gdextension_variant_get_keyed;
- gde_interface.variant_get_indexed = gdextension_variant_get_indexed;
- gde_interface.variant_iter_init = gdextension_variant_iter_init;
- gde_interface.variant_iter_next = gdextension_variant_iter_next;
- gde_interface.variant_iter_get = gdextension_variant_iter_get;
- gde_interface.variant_hash = gdextension_variant_hash;
- gde_interface.variant_recursive_hash = gdextension_variant_recursive_hash;
- gde_interface.variant_hash_compare = gdextension_variant_hash_compare;
- gde_interface.variant_booleanize = gdextension_variant_booleanize;
- gde_interface.variant_duplicate = gdextension_variant_duplicate;
- gde_interface.variant_stringify = gdextension_variant_stringify;
-
- gde_interface.variant_get_type = gdextension_variant_get_type;
- gde_interface.variant_has_method = gdextension_variant_has_method;
- gde_interface.variant_has_member = gdextension_variant_has_member;
- gde_interface.variant_has_key = gdextension_variant_has_key;
- gde_interface.variant_get_type_name = gdextension_variant_get_type_name;
- gde_interface.variant_can_convert = gdextension_variant_can_convert;
- gde_interface.variant_can_convert_strict = gdextension_variant_can_convert_strict;
-
- gde_interface.get_variant_from_type_constructor = gdextension_get_variant_from_type_constructor;
- gde_interface.get_variant_to_type_constructor = gdextension_get_type_from_variant_constructor;
-
- // ptrcalls.
-
- gde_interface.variant_get_ptr_operator_evaluator = gdextension_variant_get_ptr_operator_evaluator;
- gde_interface.variant_get_ptr_builtin_method = gdextension_variant_get_ptr_builtin_method;
- gde_interface.variant_get_ptr_constructor = gdextension_variant_get_ptr_constructor;
- gde_interface.variant_get_ptr_destructor = gdextension_variant_get_ptr_destructor;
- gde_interface.variant_construct = gdextension_variant_construct;
- gde_interface.variant_get_ptr_setter = gdextension_variant_get_ptr_setter;
- gde_interface.variant_get_ptr_getter = gdextension_variant_get_ptr_getter;
- gde_interface.variant_get_ptr_indexed_setter = gdextension_variant_get_ptr_indexed_setter;
- gde_interface.variant_get_ptr_indexed_getter = gdextension_variant_get_ptr_indexed_getter;
- gde_interface.variant_get_ptr_keyed_setter = gdextension_variant_get_ptr_keyed_setter;
- gde_interface.variant_get_ptr_keyed_getter = gdextension_variant_get_ptr_keyed_getter;
- gde_interface.variant_get_ptr_keyed_checker = gdextension_variant_get_ptr_keyed_checker;
- gde_interface.variant_get_constant_value = gdextension_variant_get_constant_value;
- gde_interface.variant_get_ptr_utility_function = gdextension_variant_get_ptr_utility_function;
-
- // extra utilities
-
- gde_interface.string_new_with_latin1_chars = gdextension_string_new_with_latin1_chars;
- gde_interface.string_new_with_utf8_chars = gdextension_string_new_with_utf8_chars;
- gde_interface.string_new_with_utf16_chars = gdextension_string_new_with_utf16_chars;
- gde_interface.string_new_with_utf32_chars = gdextension_string_new_with_utf32_chars;
- gde_interface.string_new_with_wide_chars = gdextension_string_new_with_wide_chars;
- gde_interface.string_new_with_latin1_chars_and_len = gdextension_string_new_with_latin1_chars_and_len;
- gde_interface.string_new_with_utf8_chars_and_len = gdextension_string_new_with_utf8_chars_and_len;
- gde_interface.string_new_with_utf16_chars_and_len = gdextension_string_new_with_utf16_chars_and_len;
- gde_interface.string_new_with_utf32_chars_and_len = gdextension_string_new_with_utf32_chars_and_len;
- gde_interface.string_new_with_wide_chars_and_len = gdextension_string_new_with_wide_chars_and_len;
- gde_interface.string_to_latin1_chars = gdextension_string_to_latin1_chars;
- gde_interface.string_to_utf8_chars = gdextension_string_to_utf8_chars;
- gde_interface.string_to_utf16_chars = gdextension_string_to_utf16_chars;
- gde_interface.string_to_utf32_chars = gdextension_string_to_utf32_chars;
- gde_interface.string_to_wide_chars = gdextension_string_to_wide_chars;
- gde_interface.string_operator_index = gdextension_string_operator_index;
- gde_interface.string_operator_index_const = gdextension_string_operator_index_const;
- gde_interface.string_operator_plus_eq_string = gdextension_string_operator_plus_eq_string;
- gde_interface.string_operator_plus_eq_char = gdextension_string_operator_plus_eq_char;
- gde_interface.string_operator_plus_eq_cstr = gdextension_string_operator_plus_eq_cstr;
- gde_interface.string_operator_plus_eq_wcstr = gdextension_string_operator_plus_eq_wcstr;
- gde_interface.string_operator_plus_eq_c32str = gdextension_string_operator_plus_eq_c32str;
-
- /* XMLParser extra utilities */
-
- gde_interface.xml_parser_open_buffer = gdextension_xml_parser_open_buffer;
-
- /* FileAccess extra utilities */
-
- gde_interface.file_access_store_buffer = gdextension_file_access_store_buffer;
- gde_interface.file_access_get_buffer = gdextension_file_access_get_buffer;
-
- /* WorkerThreadPool extra utilities */
-
- gde_interface.worker_thread_pool_add_native_group_task = gdextension_worker_thread_pool_add_native_group_task;
- gde_interface.worker_thread_pool_add_native_task = gdextension_worker_thread_pool_add_native_task;
-
- /* Packed array functions */
-
- gde_interface.packed_byte_array_operator_index = gdextension_packed_byte_array_operator_index;
- gde_interface.packed_byte_array_operator_index_const = gdextension_packed_byte_array_operator_index_const;
-
- gde_interface.packed_color_array_operator_index = gdextension_packed_color_array_operator_index;
- gde_interface.packed_color_array_operator_index_const = gdextension_packed_color_array_operator_index_const;
-
- gde_interface.packed_float32_array_operator_index = gdextension_packed_float32_array_operator_index;
- gde_interface.packed_float32_array_operator_index_const = gdextension_packed_float32_array_operator_index_const;
- gde_interface.packed_float64_array_operator_index = gdextension_packed_float64_array_operator_index;
- gde_interface.packed_float64_array_operator_index_const = gdextension_packed_float64_array_operator_index_const;
-
- gde_interface.packed_int32_array_operator_index = gdextension_packed_int32_array_operator_index;
- gde_interface.packed_int32_array_operator_index_const = gdextension_packed_int32_array_operator_index_const;
- gde_interface.packed_int64_array_operator_index = gdextension_packed_int64_array_operator_index;
- gde_interface.packed_int64_array_operator_index_const = gdextension_packed_int64_array_operator_index_const;
-
- gde_interface.packed_string_array_operator_index = gdextension_packed_string_array_operator_index;
- gde_interface.packed_string_array_operator_index_const = gdextension_packed_string_array_operator_index_const;
-
- gde_interface.packed_vector2_array_operator_index = gdextension_packed_vector2_array_operator_index;
- gde_interface.packed_vector2_array_operator_index_const = gdextension_packed_vector2_array_operator_index_const;
- gde_interface.packed_vector3_array_operator_index = gdextension_packed_vector3_array_operator_index;
- gde_interface.packed_vector3_array_operator_index_const = gdextension_packed_vector3_array_operator_index_const;
-
- gde_interface.array_operator_index = gdextension_array_operator_index;
- gde_interface.array_operator_index_const = gdextension_array_operator_index_const;
- gde_interface.array_ref = gdextension_array_ref;
- gde_interface.array_set_typed = gdextension_array_set_typed;
-
- /* Dictionary functions */
-
- gde_interface.dictionary_operator_index = gdextension_dictionary_operator_index;
- gde_interface.dictionary_operator_index_const = gdextension_dictionary_operator_index_const;
-
- /* OBJECT */
-
- gde_interface.object_method_bind_call = gdextension_object_method_bind_call;
- gde_interface.object_method_bind_ptrcall = gdextension_object_method_bind_ptrcall;
- gde_interface.object_destroy = gdextension_object_destroy;
- gde_interface.global_get_singleton = gdextension_global_get_singleton;
- gde_interface.object_get_instance_binding = gdextension_object_get_instance_binding;
- gde_interface.object_set_instance_binding = gdextension_object_set_instance_binding;
- gde_interface.object_set_instance = gdextension_object_set_instance;
-
- gde_interface.object_cast_to = gdextension_object_cast_to;
- gde_interface.object_get_instance_from_id = gdextension_object_get_instance_from_id;
- gde_interface.object_get_instance_id = gdextension_object_get_instance_id;
-
- /* REFERENCE */
-
- gde_interface.ref_get_object = gdextension_ref_get_object;
- gde_interface.ref_set_object = gdextension_ref_set_object;
-
- /* SCRIPT INSTANCE */
-
- gde_interface.script_instance_create = gdextension_script_instance_create;
-
- /* CLASSDB */
-
- gde_interface.classdb_construct_object = gdextension_classdb_construct_object;
- gde_interface.classdb_get_method_bind = gdextension_classdb_get_method_bind;
- gde_interface.classdb_get_class_tag = gdextension_classdb_get_class_tag;
-
- /* CLASSDB EXTENSION */
-
- //these are filled by implementation, since it will want to keep track of registered classes
- gde_interface.classdb_register_extension_class = nullptr;
- gde_interface.classdb_register_extension_class_method = nullptr;
- gde_interface.classdb_register_extension_class_integer_constant = nullptr;
- gde_interface.classdb_register_extension_class_property = nullptr;
- gde_interface.classdb_register_extension_class_property_group = nullptr;
- gde_interface.classdb_register_extension_class_property_subgroup = nullptr;
- gde_interface.classdb_register_extension_class_signal = nullptr;
- gde_interface.classdb_unregister_extension_class = nullptr;
+}
- gde_interface.get_library_path = nullptr;
+static void gdextension_editor_remove_plugin(GDExtensionConstStringNamePtr p_classname) {
+#ifdef TOOLS_ENABLED
+ const StringName classname = *reinterpret_cast<const StringName *>(p_classname);
+ GDExtensionEditorPlugins::remove_extension_class(classname);
+#endif
}
+
+#define REGISTER_INTERFACE_FUNC(m_name) GDExtension::register_interface_function(#m_name, (GDExtensionInterfaceFunctionPtr)&gdextension_##m_name)
+
+void gdextension_setup_interface() {
+ REGISTER_INTERFACE_FUNC(get_godot_version);
+ REGISTER_INTERFACE_FUNC(mem_alloc);
+ REGISTER_INTERFACE_FUNC(mem_realloc);
+ REGISTER_INTERFACE_FUNC(mem_free);
+ REGISTER_INTERFACE_FUNC(print_error);
+ REGISTER_INTERFACE_FUNC(print_error_with_message);
+ REGISTER_INTERFACE_FUNC(print_warning);
+ REGISTER_INTERFACE_FUNC(print_warning_with_message);
+ REGISTER_INTERFACE_FUNC(print_script_error);
+ REGISTER_INTERFACE_FUNC(print_script_error_with_message);
+ REGISTER_INTERFACE_FUNC(get_native_struct_size);
+ REGISTER_INTERFACE_FUNC(variant_new_copy);
+ REGISTER_INTERFACE_FUNC(variant_new_nil);
+ REGISTER_INTERFACE_FUNC(variant_destroy);
+ REGISTER_INTERFACE_FUNC(variant_call);
+ REGISTER_INTERFACE_FUNC(variant_call_static);
+ REGISTER_INTERFACE_FUNC(variant_evaluate);
+ REGISTER_INTERFACE_FUNC(variant_set);
+ REGISTER_INTERFACE_FUNC(variant_set_named);
+ REGISTER_INTERFACE_FUNC(variant_set_keyed);
+ REGISTER_INTERFACE_FUNC(variant_set_indexed);
+ REGISTER_INTERFACE_FUNC(variant_get);
+ REGISTER_INTERFACE_FUNC(variant_get_named);
+ REGISTER_INTERFACE_FUNC(variant_get_keyed);
+ REGISTER_INTERFACE_FUNC(variant_get_indexed);
+ REGISTER_INTERFACE_FUNC(variant_iter_init);
+ REGISTER_INTERFACE_FUNC(variant_iter_next);
+ REGISTER_INTERFACE_FUNC(variant_iter_get);
+ REGISTER_INTERFACE_FUNC(variant_hash);
+ REGISTER_INTERFACE_FUNC(variant_recursive_hash);
+ REGISTER_INTERFACE_FUNC(variant_hash_compare);
+ REGISTER_INTERFACE_FUNC(variant_booleanize);
+ REGISTER_INTERFACE_FUNC(variant_duplicate);
+ REGISTER_INTERFACE_FUNC(variant_stringify);
+ REGISTER_INTERFACE_FUNC(variant_get_type);
+ REGISTER_INTERFACE_FUNC(variant_has_method);
+ REGISTER_INTERFACE_FUNC(variant_has_member);
+ REGISTER_INTERFACE_FUNC(variant_has_key);
+ REGISTER_INTERFACE_FUNC(variant_get_type_name);
+ REGISTER_INTERFACE_FUNC(variant_can_convert);
+ REGISTER_INTERFACE_FUNC(variant_can_convert_strict);
+ REGISTER_INTERFACE_FUNC(get_variant_from_type_constructor);
+ REGISTER_INTERFACE_FUNC(get_variant_to_type_constructor);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_operator_evaluator);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_builtin_method);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_constructor);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_destructor);
+ REGISTER_INTERFACE_FUNC(variant_construct);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_setter);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_getter);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_indexed_setter);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_indexed_getter);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_keyed_setter);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_keyed_getter);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_keyed_checker);
+ REGISTER_INTERFACE_FUNC(variant_get_constant_value);
+ REGISTER_INTERFACE_FUNC(variant_get_ptr_utility_function);
+ REGISTER_INTERFACE_FUNC(string_new_with_latin1_chars);
+ REGISTER_INTERFACE_FUNC(string_new_with_utf8_chars);
+ REGISTER_INTERFACE_FUNC(string_new_with_utf16_chars);
+ REGISTER_INTERFACE_FUNC(string_new_with_utf32_chars);
+ 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_utf16_chars_and_len);
+ 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);
+ REGISTER_INTERFACE_FUNC(string_to_utf8_chars);
+ REGISTER_INTERFACE_FUNC(string_to_utf16_chars);
+ REGISTER_INTERFACE_FUNC(string_to_utf32_chars);
+ REGISTER_INTERFACE_FUNC(string_to_wide_chars);
+ REGISTER_INTERFACE_FUNC(string_operator_index);
+ REGISTER_INTERFACE_FUNC(string_operator_index_const);
+ REGISTER_INTERFACE_FUNC(string_operator_plus_eq_string);
+ REGISTER_INTERFACE_FUNC(string_operator_plus_eq_char);
+ REGISTER_INTERFACE_FUNC(string_operator_plus_eq_cstr);
+ REGISTER_INTERFACE_FUNC(string_operator_plus_eq_wcstr);
+ REGISTER_INTERFACE_FUNC(string_operator_plus_eq_c32str);
+ REGISTER_INTERFACE_FUNC(xml_parser_open_buffer);
+ REGISTER_INTERFACE_FUNC(file_access_store_buffer);
+ REGISTER_INTERFACE_FUNC(file_access_get_buffer);
+ REGISTER_INTERFACE_FUNC(worker_thread_pool_add_native_group_task);
+ REGISTER_INTERFACE_FUNC(worker_thread_pool_add_native_task);
+ REGISTER_INTERFACE_FUNC(packed_byte_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_byte_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(packed_color_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_color_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(packed_float32_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_float32_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(packed_float64_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_float64_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(packed_int32_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_int32_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(packed_int64_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_int64_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(packed_string_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_string_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(packed_vector2_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_vector2_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(packed_vector3_array_operator_index);
+ REGISTER_INTERFACE_FUNC(packed_vector3_array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(array_operator_index);
+ REGISTER_INTERFACE_FUNC(array_operator_index_const);
+ REGISTER_INTERFACE_FUNC(array_ref);
+ REGISTER_INTERFACE_FUNC(array_set_typed);
+ REGISTER_INTERFACE_FUNC(dictionary_operator_index);
+ REGISTER_INTERFACE_FUNC(dictionary_operator_index_const);
+ REGISTER_INTERFACE_FUNC(object_method_bind_call);
+ REGISTER_INTERFACE_FUNC(object_method_bind_ptrcall);
+ REGISTER_INTERFACE_FUNC(object_destroy);
+ REGISTER_INTERFACE_FUNC(global_get_singleton);
+ REGISTER_INTERFACE_FUNC(object_get_instance_binding);
+ REGISTER_INTERFACE_FUNC(object_set_instance_binding);
+ REGISTER_INTERFACE_FUNC(object_set_instance);
+ REGISTER_INTERFACE_FUNC(object_get_class_name);
+ REGISTER_INTERFACE_FUNC(object_cast_to);
+ REGISTER_INTERFACE_FUNC(object_get_instance_from_id);
+ REGISTER_INTERFACE_FUNC(object_get_instance_id);
+ REGISTER_INTERFACE_FUNC(ref_get_object);
+ REGISTER_INTERFACE_FUNC(ref_set_object);
+ REGISTER_INTERFACE_FUNC(script_instance_create);
+ REGISTER_INTERFACE_FUNC(classdb_construct_object);
+ REGISTER_INTERFACE_FUNC(classdb_get_method_bind);
+ REGISTER_INTERFACE_FUNC(classdb_get_class_tag);
+ REGISTER_INTERFACE_FUNC(editor_add_plugin);
+ REGISTER_INTERFACE_FUNC(editor_remove_plugin);
+}
+
+#undef REGISTER_INTERFACE_FUNCTION
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index f323b2aa53..2a328c9a34 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -139,16 +139,37 @@ typedef enum {
} GDExtensionVariantOperator;
+// In this API there are multiple functions which expect the caller to pass a pointer
+// on return value as parameter.
+// In order to make it clear if the caller should initialize the return value or not
+// we have two flavor of types:
+// - `GDExtensionXXXPtr` for pointer on an initialized value
+// - `GDExtensionUninitializedXXXPtr` for pointer on uninitialized value
+//
+// Notes:
+// - Not respecting those requirements can seems harmless, but will lead to unexpected
+// segfault or memory leak (for instance with a specific compiler/OS, or when two
+// native extensions start doing ptrcall on each other).
+// - Initialization must be done with the function pointer returned by `variant_get_ptr_constructor`,
+// zero-initializing the variable should not be considered a valid initialization method here !
+// - Some types have no destructor (see `extension_api.json`'s `has_destructor` field), for
+// them it is always safe to skip the constructor for the return value if you are in a hurry ;-)
+
typedef void *GDExtensionVariantPtr;
typedef const void *GDExtensionConstVariantPtr;
+typedef void *GDExtensionUninitializedVariantPtr;
typedef void *GDExtensionStringNamePtr;
typedef const void *GDExtensionConstStringNamePtr;
+typedef void *GDExtensionUninitializedStringNamePtr;
typedef void *GDExtensionStringPtr;
typedef const void *GDExtensionConstStringPtr;
+typedef void *GDExtensionUninitializedStringPtr;
typedef void *GDExtensionObjectPtr;
typedef const void *GDExtensionConstObjectPtr;
+typedef void *GDExtensionUninitializedObjectPtr;
typedef void *GDExtensionTypePtr;
typedef const void *GDExtensionConstTypePtr;
+typedef void *GDExtensionUninitializedTypePtr;
typedef const void *GDExtensionMethodBindPtr;
typedef int64_t GDExtensionInt;
typedef uint8_t GDExtensionBool;
@@ -174,11 +195,11 @@ typedef struct {
int32_t expected;
} GDExtensionCallError;
-typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionVariantPtr, GDExtensionTypePtr);
-typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionTypePtr, GDExtensionVariantPtr);
+typedef void (*GDExtensionVariantFromTypeConstructorFunc)(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr);
+typedef void (*GDExtensionTypeFromVariantConstructorFunc)(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr);
typedef void (*GDExtensionPtrOperatorEvaluator)(GDExtensionConstTypePtr p_left, GDExtensionConstTypePtr p_right, GDExtensionTypePtr r_result);
typedef void (*GDExtensionPtrBuiltInMethod)(GDExtensionTypePtr p_base, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_return, int p_argument_count);
-typedef void (*GDExtensionPtrConstructor)(GDExtensionTypePtr p_base, const GDExtensionConstTypePtr *p_args);
+typedef void (*GDExtensionPtrConstructor)(GDExtensionUninitializedTypePtr p_base, const GDExtensionConstTypePtr *p_args);
typedef void (*GDExtensionPtrDestructor)(GDExtensionTypePtr p_base);
typedef void (*GDExtensionPtrSetter)(GDExtensionTypePtr p_base, GDExtensionConstTypePtr p_value);
typedef void (*GDExtensionPtrGetter)(GDExtensionConstTypePtr p_base, GDExtensionTypePtr r_value);
@@ -295,6 +316,7 @@ typedef enum {
} GDExtensionClassMethodArgumentMetadata;
typedef void (*GDExtensionClassMethodCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
+typedef void (*GDExtensionClassMethodValidatedCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionVariantPtr r_return);
typedef void (*GDExtensionClassMethodPtrCall)(void *method_userdata, GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
typedef struct {
@@ -400,214 +422,6 @@ typedef struct {
} GDExtensionScriptInstanceInfo;
-/* INTERFACE */
-
-typedef struct {
- uint32_t version_major;
- uint32_t version_minor;
- uint32_t version_patch;
- const char *version_string;
-
- /* GODOT CORE */
-
- void *(*mem_alloc)(size_t p_bytes);
- void *(*mem_realloc)(void *p_ptr, size_t p_bytes);
- void (*mem_free)(void *p_ptr);
-
- void (*print_error)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
- void (*print_error_with_message)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
- void (*print_warning)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
- void (*print_warning_with_message)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
- void (*print_script_error)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
- void (*print_script_error_with_message)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
-
- uint64_t (*get_native_struct_size)(GDExtensionConstStringNamePtr p_name);
-
- /* GODOT VARIANT */
-
- /* variant general */
- void (*variant_new_copy)(GDExtensionVariantPtr r_dest, GDExtensionConstVariantPtr p_src);
- void (*variant_new_nil)(GDExtensionVariantPtr r_dest);
- void (*variant_destroy)(GDExtensionVariantPtr p_self);
-
- /* variant type */
- void (*variant_call)(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
- void (*variant_call_static)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionVariantPtr r_return, GDExtensionCallError *r_error);
- void (*variant_evaluate)(GDExtensionVariantOperator p_op, GDExtensionConstVariantPtr p_a, GDExtensionConstVariantPtr p_b, GDExtensionVariantPtr r_return, GDExtensionBool *r_valid);
- void (*variant_set)(GDExtensionVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid);
- void (*variant_set_named)(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid);
- void (*variant_set_keyed)(GDExtensionVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid);
- void (*variant_set_indexed)(GDExtensionVariantPtr p_self, GDExtensionInt p_index, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid, GDExtensionBool *r_oob);
- void (*variant_get)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid);
- void (*variant_get_named)(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid);
- void (*variant_get_keyed)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid);
- void (*variant_get_indexed)(GDExtensionConstVariantPtr p_self, GDExtensionInt p_index, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid, GDExtensionBool *r_oob);
- GDExtensionBool (*variant_iter_init)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionBool *r_valid);
- GDExtensionBool (*variant_iter_next)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionBool *r_valid);
- void (*variant_iter_get)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionVariantPtr r_ret, GDExtensionBool *r_valid);
- GDExtensionInt (*variant_hash)(GDExtensionConstVariantPtr p_self);
- GDExtensionInt (*variant_recursive_hash)(GDExtensionConstVariantPtr p_self, GDExtensionInt p_recursion_count);
- GDExtensionBool (*variant_hash_compare)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_other);
- GDExtensionBool (*variant_booleanize)(GDExtensionConstVariantPtr p_self);
- void (*variant_duplicate)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_ret, GDExtensionBool p_deep);
- void (*variant_stringify)(GDExtensionConstVariantPtr p_self, GDExtensionStringPtr r_ret);
-
- GDExtensionVariantType (*variant_get_type)(GDExtensionConstVariantPtr p_self);
- GDExtensionBool (*variant_has_method)(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_method);
- GDExtensionBool (*variant_has_member)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member);
- GDExtensionBool (*variant_has_key)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionBool *r_valid);
- void (*variant_get_type_name)(GDExtensionVariantType p_type, GDExtensionStringPtr r_name);
- GDExtensionBool (*variant_can_convert)(GDExtensionVariantType p_from, GDExtensionVariantType p_to);
- GDExtensionBool (*variant_can_convert_strict)(GDExtensionVariantType p_from, GDExtensionVariantType p_to);
-
- /* ptrcalls */
- GDExtensionVariantFromTypeConstructorFunc (*get_variant_from_type_constructor)(GDExtensionVariantType p_type);
- GDExtensionTypeFromVariantConstructorFunc (*get_variant_to_type_constructor)(GDExtensionVariantType p_type);
- GDExtensionPtrOperatorEvaluator (*variant_get_ptr_operator_evaluator)(GDExtensionVariantOperator p_operator, GDExtensionVariantType p_type_a, GDExtensionVariantType p_type_b);
- GDExtensionPtrBuiltInMethod (*variant_get_ptr_builtin_method)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, GDExtensionInt p_hash);
- GDExtensionPtrConstructor (*variant_get_ptr_constructor)(GDExtensionVariantType p_type, int32_t p_constructor);
- GDExtensionPtrDestructor (*variant_get_ptr_destructor)(GDExtensionVariantType p_type);
- void (*variant_construct)(GDExtensionVariantType p_type, GDExtensionVariantPtr p_base, const GDExtensionConstVariantPtr *p_args, int32_t p_argument_count, GDExtensionCallError *r_error);
- GDExtensionPtrSetter (*variant_get_ptr_setter)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member);
- GDExtensionPtrGetter (*variant_get_ptr_getter)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member);
- GDExtensionPtrIndexedSetter (*variant_get_ptr_indexed_setter)(GDExtensionVariantType p_type);
- GDExtensionPtrIndexedGetter (*variant_get_ptr_indexed_getter)(GDExtensionVariantType p_type);
- GDExtensionPtrKeyedSetter (*variant_get_ptr_keyed_setter)(GDExtensionVariantType p_type);
- GDExtensionPtrKeyedGetter (*variant_get_ptr_keyed_getter)(GDExtensionVariantType p_type);
- GDExtensionPtrKeyedChecker (*variant_get_ptr_keyed_checker)(GDExtensionVariantType p_type);
- void (*variant_get_constant_value)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_constant, GDExtensionVariantPtr r_ret);
- GDExtensionPtrUtilityFunction (*variant_get_ptr_utility_function)(GDExtensionConstStringNamePtr p_function, GDExtensionInt p_hash);
-
- /* extra utilities */
- void (*string_new_with_latin1_chars)(GDExtensionStringPtr r_dest, const char *p_contents);
- void (*string_new_with_utf8_chars)(GDExtensionStringPtr r_dest, const char *p_contents);
- void (*string_new_with_utf16_chars)(GDExtensionStringPtr r_dest, const char16_t *p_contents);
- void (*string_new_with_utf32_chars)(GDExtensionStringPtr r_dest, const char32_t *p_contents);
- void (*string_new_with_wide_chars)(GDExtensionStringPtr r_dest, const wchar_t *p_contents);
- void (*string_new_with_latin1_chars_and_len)(GDExtensionStringPtr r_dest, const char *p_contents, GDExtensionInt p_size);
- void (*string_new_with_utf8_chars_and_len)(GDExtensionStringPtr r_dest, const char *p_contents, GDExtensionInt p_size);
- void (*string_new_with_utf16_chars_and_len)(GDExtensionStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_size);
- void (*string_new_with_utf32_chars_and_len)(GDExtensionStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_size);
- void (*string_new_with_wide_chars_and_len)(GDExtensionStringPtr r_dest, const wchar_t *p_contents, GDExtensionInt p_size);
- /* Information about the following functions:
- * - The return value is the resulting encoded string length.
- * - The length returned is in characters, not in bytes. It also does not include a trailing zero.
- * - These functions also do not write trailing zero, If you need it, write it yourself at the position indicated by the length (and make sure to allocate it).
- * - Passing NULL in r_text means only the length is computed (again, without including trailing zero).
- * - p_max_write_length argument is in characters, not bytes. It will be ignored if r_text is NULL.
- * - p_max_write_length argument does not affect the return value, it's only to cap write length.
- */
- GDExtensionInt (*string_to_latin1_chars)(GDExtensionConstStringPtr p_self, char *r_text, GDExtensionInt p_max_write_length);
- GDExtensionInt (*string_to_utf8_chars)(GDExtensionConstStringPtr p_self, char *r_text, GDExtensionInt p_max_write_length);
- GDExtensionInt (*string_to_utf16_chars)(GDExtensionConstStringPtr p_self, char16_t *r_text, GDExtensionInt p_max_write_length);
- GDExtensionInt (*string_to_utf32_chars)(GDExtensionConstStringPtr p_self, char32_t *r_text, GDExtensionInt p_max_write_length);
- GDExtensionInt (*string_to_wide_chars)(GDExtensionConstStringPtr p_self, wchar_t *r_text, GDExtensionInt p_max_write_length);
- char32_t *(*string_operator_index)(GDExtensionStringPtr p_self, GDExtensionInt p_index);
- const char32_t *(*string_operator_index_const)(GDExtensionConstStringPtr p_self, GDExtensionInt p_index);
-
- void (*string_operator_plus_eq_string)(GDExtensionStringPtr p_self, GDExtensionConstStringPtr p_b);
- void (*string_operator_plus_eq_char)(GDExtensionStringPtr p_self, char32_t p_b);
- void (*string_operator_plus_eq_cstr)(GDExtensionStringPtr p_self, const char *p_b);
- void (*string_operator_plus_eq_wcstr)(GDExtensionStringPtr p_self, const wchar_t *p_b);
- void (*string_operator_plus_eq_c32str)(GDExtensionStringPtr p_self, const char32_t *p_b);
-
- /* XMLParser extra utilities */
-
- GDExtensionInt (*xml_parser_open_buffer)(GDExtensionObjectPtr p_instance, const uint8_t *p_buffer, size_t p_size);
-
- /* FileAccess extra utilities */
-
- void (*file_access_store_buffer)(GDExtensionObjectPtr p_instance, const uint8_t *p_src, uint64_t p_length);
- uint64_t (*file_access_get_buffer)(GDExtensionConstObjectPtr p_instance, uint8_t *p_dst, uint64_t p_length);
-
- /* WorkerThreadPool extra utilities */
-
- int64_t (*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);
- int64_t (*worker_thread_pool_add_native_task)(GDExtensionObjectPtr p_instance, void (*p_func)(void *), void *p_userdata, GDExtensionBool p_high_priority, GDExtensionConstStringPtr p_description);
-
- /* Packed array functions */
-
- uint8_t *(*packed_byte_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedByteArray
- const uint8_t *(*packed_byte_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedByteArray
-
- GDExtensionTypePtr (*packed_color_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedColorArray, returns Color ptr
- GDExtensionTypePtr (*packed_color_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedColorArray, returns Color ptr
-
- float *(*packed_float32_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedFloat32Array
- const float *(*packed_float32_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedFloat32Array
- double *(*packed_float64_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedFloat64Array
- const double *(*packed_float64_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedFloat64Array
-
- int32_t *(*packed_int32_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedInt32Array
- const int32_t *(*packed_int32_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedInt32Array
- int64_t *(*packed_int64_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedInt32Array
- const int64_t *(*packed_int64_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedInt32Array
-
- GDExtensionStringPtr (*packed_string_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedStringArray
- GDExtensionStringPtr (*packed_string_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedStringArray
-
- GDExtensionTypePtr (*packed_vector2_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedVector2Array, returns Vector2 ptr
- GDExtensionTypePtr (*packed_vector2_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedVector2Array, returns Vector2 ptr
- GDExtensionTypePtr (*packed_vector3_array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedVector3Array, returns Vector3 ptr
- GDExtensionTypePtr (*packed_vector3_array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be a PackedVector3Array, returns Vector3 ptr
-
- GDExtensionVariantPtr (*array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be an Array ptr
- GDExtensionVariantPtr (*array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be an Array ptr
- void (*array_ref)(GDExtensionTypePtr p_self, GDExtensionConstTypePtr p_from); // p_self should be an Array ptr
- void (*array_set_typed)(GDExtensionTypePtr p_self, GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script); // p_self should be an Array ptr
-
- /* Dictionary functions */
-
- GDExtensionVariantPtr (*dictionary_operator_index)(GDExtensionTypePtr p_self, GDExtensionConstVariantPtr p_key); // p_self should be an Dictionary ptr
- GDExtensionVariantPtr (*dictionary_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key); // p_self should be an Dictionary ptr
-
- /* OBJECT */
-
- void (*object_method_bind_call)(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionVariantPtr r_ret, GDExtensionCallError *r_error);
- void (*object_method_bind_ptrcall)(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
- void (*object_destroy)(GDExtensionObjectPtr p_o);
- GDExtensionObjectPtr (*global_get_singleton)(GDExtensionConstStringNamePtr p_name);
-
- void *(*object_get_instance_binding)(GDExtensionObjectPtr p_o, void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks);
- void (*object_set_instance_binding)(GDExtensionObjectPtr p_o, void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks);
-
- void (*object_set_instance)(GDExtensionObjectPtr p_o, GDExtensionConstStringNamePtr p_classname, GDExtensionClassInstancePtr p_instance); /* p_classname should be a registered extension class and should extend the p_o object's class. */
-
- GDExtensionObjectPtr (*object_cast_to)(GDExtensionConstObjectPtr p_object, void *p_class_tag);
- GDExtensionObjectPtr (*object_get_instance_from_id)(GDObjectInstanceID p_instance_id);
- GDObjectInstanceID (*object_get_instance_id)(GDExtensionConstObjectPtr p_object);
-
- /* REFERENCE */
-
- GDExtensionObjectPtr (*ref_get_object)(GDExtensionConstRefPtr p_ref);
- void (*ref_set_object)(GDExtensionRefPtr p_ref, GDExtensionObjectPtr p_object);
-
- /* SCRIPT INSTANCE */
-
- GDExtensionScriptInstancePtr (*script_instance_create)(const GDExtensionScriptInstanceInfo *p_info, GDExtensionScriptInstanceDataPtr p_instance_data);
-
- /* CLASSDB */
-
- GDExtensionObjectPtr (*classdb_construct_object)(GDExtensionConstStringNamePtr p_classname); /* The passed class must be a built-in godot class, or an already-registered extension class. In both case, object_set_instance should be called to fully initialize the object. */
- GDExtensionMethodBindPtr (*classdb_get_method_bind)(GDExtensionConstStringNamePtr p_classname, GDExtensionConstStringNamePtr p_methodname, GDExtensionInt p_hash);
- void *(*classdb_get_class_tag)(GDExtensionConstStringNamePtr p_classname);
-
- /* CLASSDB EXTENSION */
-
- /* Provided parameters for `classdb_register_extension_*` can be safely freed once the function returns. */
- void (*classdb_register_extension_class)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs);
- void (*classdb_register_extension_class_method)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info);
- void (*classdb_register_extension_class_integer_constant)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield);
- void (*classdb_register_extension_class_property)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter);
- void (*classdb_register_extension_class_property_group)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_group_name, GDExtensionConstStringPtr p_prefix);
- void (*classdb_register_extension_class_property_subgroup)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_subgroup_name, GDExtensionConstStringPtr p_prefix);
- void (*classdb_register_extension_class_signal)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_signal_name, const GDExtensionPropertyInfo *p_argument_info, GDExtensionInt p_argument_count);
- void (*classdb_unregister_extension_class)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name); /* Unregistering a parent class before a class that inherits it will result in failure. Inheritors must be unregistered first. */
-
- void (*get_library_path)(GDExtensionClassLibraryPtr p_library, GDExtensionStringPtr r_path);
-
-} GDExtensionInterface;
-
/* INITIALIZATION */
typedef enum {
@@ -629,12 +443,1723 @@ typedef struct {
void (*deinitialize)(void *userdata, GDExtensionInitializationLevel p_level);
} GDExtensionInitialization;
-/* Define a C function prototype that implements the function below and expose it to dlopen() (or similar).
- * This is the entry point of the GDExtension library and will be called on initialization.
- * It can be used to set up different init levels, which are called during various stages of initialization/shutdown.
- * The function name must be a unique one specified in the .gdextension config file.
+typedef void (*GDExtensionInterfaceFunctionPtr)();
+typedef GDExtensionInterfaceFunctionPtr (*GDExtensionInterfaceGetProcAddress)(const char *p_function_name);
+
+/*
+ * Each GDExtension should define a C function that matches the signature of GDExtensionInitializationFunction,
+ * and export it so that it can be loaded via dlopen() or equivalent for the given platform.
+ *
+ * For example:
+ *
+ * GDExtensionBool my_extension_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization);
+ *
+ * This function's name must be specified as the 'entry_symbol' in the .gdextension file.
+ *
+ * This makes it the entry point of the GDExtension and will be called on initialization.
+ *
+ * The GDExtension can then modify the r_initialization structure, setting the minimum initialization level,
+ * and providing pointers to functions that will be called at various stages of initialization/shutdown.
+ *
+ * The rest of the GDExtension's interface to Godot consists of function pointers that can be loaded
+ * by calling p_get_proc_address("...") with the name of the function.
+ *
+ * For example:
+ *
+ * GDExtensionInterfaceGetGodotVersion *get_godot_version = (GDExtensionInterfaceGetGodotVersion)p_get_proc_address("get_godot_version");
+ *
+ * You can then call it like a normal function:
+ *
+ * GDExtensionGodotVersion godot_version;
+ * get_godot_version(&godot_version);
+ * printf("Godot v%d.%d.%d\n", godot_version.major, godot_version.minor, godot_version.patch);
+ *
+ * All of these interface functions are described below, together with the name that's used to load it,
+ * and the function pointer typedef that shows its signature.
+ */
+typedef GDExtensionBool (*GDExtensionInitializationFunction)(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization);
+
+/* INTERFACE */
+
+typedef struct {
+ uint32_t major;
+ uint32_t minor;
+ uint32_t patch;
+ const char *string;
+} GDExtensionGodotVersion;
+
+/**
+ * @name get_godot_version
+ *
+ * Gets the Godot version that the GDExtension was loaded into.
+ *
+ * @param r_godot_version A pointer to the structure to write the version information into.
+ */
+typedef void (*GDExtensionInterfaceGetGodotVersion)(GDExtensionGodotVersion *r_godot_version);
+
+/* INTERFACE: Memory */
+
+/**
+ * @name mem_alloc
+ *
+ * Allocates memory.
+ *
+ * @param p_bytes The amount of memory to allocate in bytes.
+ *
+ * @return A pointer to the allocated memory, or NULL if unsuccessful.
+ */
+typedef void *(*GDExtensionInterfaceMemAlloc)(size_t p_bytes);
+
+/**
+ * @name mem_realloc
+ *
+ * Reallocates memory.
+ *
+ * @param p_ptr A pointer to the previously allocated memory.
+ * @param p_bytes The number of bytes to resize the memory block to.
+ *
+ * @return A pointer to the allocated memory, or NULL if unsuccessful.
+ */
+typedef void *(*GDExtensionInterfaceMemRealloc)(void *p_ptr, size_t p_bytes);
+
+/**
+ * @name mem_free
+ *
+ * Frees memory.
+ *
+ * @param p_ptr A pointer to the previously allocated memory.
+ */
+typedef void (*GDExtensionInterfaceMemFree)(void *p_ptr);
+
+/* INTERFACE: Godot Core */
+
+/**
+ * @name print_error
+ *
+ * Logs an error to Godot's built-in debugger and to the OS terminal.
+ *
+ * @param p_description The code trigging the error.
+ * @param p_function The function name where the error occurred.
+ * @param p_file The file where the error occurred.
+ * @param p_line The line where the error occurred.
+ * @param p_editor_notify Whether or not to notify the editor.
+ */
+typedef void (*GDExtensionInterfacePrintError)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
+
+/**
+ * @name print_error_with_message
+ *
+ * Logs an error with a message to Godot's built-in debugger and to the OS terminal.
+ *
+ * @param p_description The code trigging the error.
+ * @param p_message The message to show along with the error.
+ * @param p_function The function name where the error occurred.
+ * @param p_file The file where the error occurred.
+ * @param p_line The line where the error occurred.
+ * @param p_editor_notify Whether or not to notify the editor.
+ */
+typedef void (*GDExtensionInterfacePrintErrorWithMessage)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
+
+/**
+ * @name print_warning
+ *
+ * Logs a warning to Godot's built-in debugger and to the OS terminal.
+ *
+ * @param p_description The code trigging the warning.
+ * @param p_function The function name where the warning occurred.
+ * @param p_file The file where the warning occurred.
+ * @param p_line The line where the warning occurred.
+ * @param p_editor_notify Whether or not to notify the editor.
+ */
+typedef void (*GDExtensionInterfacePrintWarning)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
+
+/**
+ * @name print_warning_with_message
+ *
+ * Logs a warning with a message to Godot's built-in debugger and to the OS terminal.
+ *
+ * @param p_description The code trigging the warning.
+ * @param p_message The message to show along with the warning.
+ * @param p_function The function name where the warning occurred.
+ * @param p_file The file where the warning occurred.
+ * @param p_line The line where the warning occurred.
+ * @param p_editor_notify Whether or not to notify the editor.
+ */
+typedef void (*GDExtensionInterfacePrintWarningWithMessage)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
+
+/**
+ * @name print_script_error
+ *
+ * Logs a script error to Godot's built-in debugger and to the OS terminal.
+ *
+ * @param p_description The code trigging the error.
+ * @param p_function The function name where the error occurred.
+ * @param p_file The file where the error occurred.
+ * @param p_line The line where the error occurred.
+ * @param p_editor_notify Whether or not to notify the editor.
+ */
+typedef void (*GDExtensionInterfacePrintScriptError)(const char *p_description, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
+
+/**
+ * @name print_script_error_with_message
+ *
+ * Logs a script error with a message to Godot's built-in debugger and to the OS terminal.
+ *
+ * @param p_description The code trigging the error.
+ * @param p_message The message to show along with the error.
+ * @param p_function The function name where the error occurred.
+ * @param p_file The file where the error occurred.
+ * @param p_line The line where the error occurred.
+ * @param p_editor_notify Whether or not to notify the editor.
+ */
+typedef void (*GDExtensionInterfacePrintScriptErrorWithMessage)(const char *p_description, const char *p_message, const char *p_function, const char *p_file, int32_t p_line, GDExtensionBool p_editor_notify);
+
+/**
+ * @name get_native_struct_size
+ *
+ * Gets the size of a native struct (ex. ObjectID) in bytes.
+ *
+ * @param p_name A pointer to a StringName identifying the struct name.
+ *
+ * @return The size in bytes.
+ */
+typedef uint64_t (*GDExtensionInterfaceGetNativeStructSize)(GDExtensionConstStringNamePtr p_name);
+
+/* INTERFACE: Variant */
+
+/**
+ * @name variant_new_copy
+ *
+ * Copies one Variant into a another.
+ *
+ * @param r_dest A pointer to the destination Variant.
+ * @param p_src A pointer to the source Variant.
+ */
+typedef void (*GDExtensionInterfaceVariantNewCopy)(GDExtensionUninitializedVariantPtr r_dest, GDExtensionConstVariantPtr p_src);
+
+/**
+ * @name variant_new_nil
+ *
+ * Creates a new Variant containing nil.
+ *
+ * @param r_dest A pointer to the destination Variant.
+ */
+typedef void (*GDExtensionInterfaceVariantNewNil)(GDExtensionUninitializedVariantPtr r_dest);
+
+/**
+ * @name variant_destroy
+ *
+ * Destroys a Variant.
+ *
+ * @param p_self A pointer to the Variant to destroy.
+ */
+typedef void (*GDExtensionInterfaceVariantDestroy)(GDExtensionVariantPtr p_self);
+
+/**
+ * @name variant_call
+ *
+ * Calls a method on a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_method A pointer to a StringName identifying the method.
+ * @param p_args A pointer to a C array of Variant.
+ * @param p_argument_count The number of arguments.
+ * @param r_return A pointer a Variant which will be assigned the return value.
+ * @param r_error A pointer the structure which will hold error information.
+ *
+ * @see Variant::callp()
+ */
+typedef void (*GDExtensionInterfaceVariantCall)(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error);
+
+/**
+ * @name variant_call_static
+ *
+ * Calls a static method on a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_method A pointer to a StringName identifying the method.
+ * @param p_args A pointer to a C array of Variant.
+ * @param p_argument_count The number of arguments.
+ * @param r_return A pointer a Variant which will be assigned the return value.
+ * @param r_error A pointer the structure which will be updated with error information.
+ *
+ * @see Variant::call_static()
+ */
+typedef void (*GDExtensionInterfaceVariantCallStatic)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_argument_count, GDExtensionUninitializedVariantPtr r_return, GDExtensionCallError *r_error);
+
+/**
+ * @name variant_evaluate
+ *
+ * Evaluate an operator on two Variants.
+ *
+ * @param p_op The operator to evaluate.
+ * @param p_a The first Variant.
+ * @param p_b The second Variant.
+ * @param r_return A pointer a Variant which will be assigned the return value.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ *
+ * @see Variant::evaluate()
+ */
+typedef void (*GDExtensionInterfaceVariantEvaluate)(GDExtensionVariantOperator p_op, GDExtensionConstVariantPtr p_a, GDExtensionConstVariantPtr p_b, GDExtensionUninitializedVariantPtr r_return, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_set
+ *
+ * Sets a key on a Variant to a value.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_key A pointer to a Variant representing the key.
+ * @param p_value A pointer to a Variant representing the value.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ *
+ * @see Variant::set()
+ */
+typedef void (*GDExtensionInterfaceVariantSet)(GDExtensionVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_set_named
+ *
+ * Sets a named key on a Variant to a value.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_key A pointer to a StringName representing the key.
+ * @param p_value A pointer to a Variant representing the value.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ *
+ * @see Variant::set_named()
+ */
+typedef void (*GDExtensionInterfaceVariantSetNamed)(GDExtensionVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_set_keyed
+ *
+ * Sets a keyed property on a Variant to a value.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_key A pointer to a Variant representing the key.
+ * @param p_value A pointer to a Variant representing the value.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ *
+ * @see Variant::set_keyed()
+ */
+typedef void (*GDExtensionInterfaceVariantSetKeyed)(GDExtensionVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_set_indexed
+ *
+ * Sets an index on a Variant to a value.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_index The index.
+ * @param p_value A pointer to a Variant representing the value.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ * @param r_oob A pointer to a boolean which will be set to true if the index is out of bounds.
+ */
+typedef void (*GDExtensionInterfaceVariantSetIndexed)(GDExtensionVariantPtr p_self, GDExtensionInt p_index, GDExtensionConstVariantPtr p_value, GDExtensionBool *r_valid, GDExtensionBool *r_oob);
+
+/**
+ * @name variant_get
+ *
+ * Gets the value of a key from a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_key A pointer to a Variant representing the key.
+ * @param r_ret A pointer to a Variant which will be assigned the value.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ */
+typedef void (*GDExtensionInterfaceVariantGet)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_get_named
+ *
+ * Gets the value of a named key from a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_key A pointer to a StringName representing the key.
+ * @param r_ret A pointer to a Variant which will be assigned the value.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ */
+typedef void (*GDExtensionInterfaceVariantGetNamed)(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_get_keyed
+ *
+ * Gets the value of a keyed property from a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_key A pointer to a Variant representing the key.
+ * @param r_ret A pointer to a Variant which will be assigned the value.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ */
+typedef void (*GDExtensionInterfaceVariantGetKeyed)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_get_indexed
+ *
+ * Gets the value of an index from a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_index The index.
+ * @param r_ret A pointer to a Variant which will be assigned the value.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ * @param r_oob A pointer to a boolean which will be set to true if the index is out of bounds.
+ */
+typedef void (*GDExtensionInterfaceVariantGetIndexed)(GDExtensionConstVariantPtr p_self, GDExtensionInt p_index, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid, GDExtensionBool *r_oob);
+
+/**
+ * @name variant_iter_init
+ *
+ * Initializes an iterator over a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param r_iter A pointer to a Variant which will be assigned the iterator.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ *
+ * @return true if the operation is valid; otherwise false.
+ *
+ * @see Variant::iter_init()
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceVariantIterInit)(GDExtensionConstVariantPtr p_self, GDExtensionUninitializedVariantPtr r_iter, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_iter_next
+ *
+ * Gets the next value for an iterator over a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param r_iter A pointer to a Variant which will be assigned the iterator.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ *
+ * @return true if the operation is valid; otherwise false.
+ *
+ * @see Variant::iter_next()
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceVariantIterNext)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_iter_get
+ *
+ * Gets the next value for an iterator over a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param r_iter A pointer to a Variant which will be assigned the iterator.
+ * @param r_ret A pointer to a Variant which will be assigned false if the operation is invalid.
+ * @param r_valid A pointer to a boolean which will be set to false if the operation is invalid.
+ *
+ * @see Variant::iter_get()
+ */
+typedef void (*GDExtensionInterfaceVariantIterGet)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_iter, GDExtensionUninitializedVariantPtr r_ret, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_hash
+ *
+ * Gets the hash of a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ *
+ * @return The hash value.
+ *
+ * @see Variant::hash()
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceVariantHash)(GDExtensionConstVariantPtr p_self);
+
+/**
+ * @name variant_recursive_hash
+ *
+ * Gets the recursive hash of a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_recursion_count The number of recursive loops so far.
+ *
+ * @return The hash value.
+ *
+ * @see Variant::recursive_hash()
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceVariantRecursiveHash)(GDExtensionConstVariantPtr p_self, GDExtensionInt p_recursion_count);
+
+/**
+ * @name variant_hash_compare
+ *
+ * Compares two Variants by their hash.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_other A pointer to the other Variant to compare it to.
+ *
+ * @return The hash value.
+ *
+ * @see Variant::hash_compare()
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceVariantHashCompare)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_other);
+
+/**
+ * @name variant_booleanize
+ *
+ * Converts a Variant to a boolean.
+ *
+ * @param p_self A pointer to the Variant.
+ *
+ * @return The boolean value of the Variant.
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceVariantBooleanize)(GDExtensionConstVariantPtr p_self);
+
+/**
+ * @name variant_duplicate
+ *
+ * Duplicates a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param r_ret A pointer to a Variant to store the duplicated value.
+ * @param p_deep Whether or not to duplicate deeply (when supported by the Variant type).
+ */
+typedef void (*GDExtensionInterfaceVariantDuplicate)(GDExtensionConstVariantPtr p_self, GDExtensionVariantPtr r_ret, GDExtensionBool p_deep);
+
+/**
+ * @name variant_stringify
+ *
+ * Converts a Variant to a string.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param r_ret A pointer to a String to store the resulting value.
+ */
+typedef void (*GDExtensionInterfaceVariantStringify)(GDExtensionConstVariantPtr p_self, GDExtensionStringPtr r_ret);
+
+/**
+ * @name variant_get_type
+ *
+ * Gets the type of a Variant.
+ *
+ * @param p_self A pointer to the Variant.
+ *
+ * @return The variant type.
+ */
+typedef GDExtensionVariantType (*GDExtensionInterfaceVariantGetType)(GDExtensionConstVariantPtr p_self);
+
+/**
+ * @name variant_has_method
+ *
+ * Checks if a Variant has the given method.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_method A pointer to a StringName with the method name.
+ *
+ * @return
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceVariantHasMethod)(GDExtensionConstVariantPtr p_self, GDExtensionConstStringNamePtr p_method);
+
+/**
+ * @name variant_has_member
+ *
+ * Checks if a type of Variant has the given member.
+ *
+ * @param p_type The Variant type.
+ * @param p_member A pointer to a StringName with the member name.
+ *
+ * @return
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceVariantHasMember)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member);
+
+/**
+ * @name variant_has_key
+ *
+ * Checks if a Variant has a key.
+ *
+ * @param p_self A pointer to the Variant.
+ * @param p_key A pointer to a Variant representing the key.
+ * @param r_valid A pointer to a boolean which will be set to false if the key doesn't exist.
+ *
+ * @return true if the key exists; otherwise false.
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceVariantHasKey)(GDExtensionConstVariantPtr p_self, GDExtensionConstVariantPtr p_key, GDExtensionBool *r_valid);
+
+/**
+ * @name variant_get_type_name
+ *
+ * Gets the name of a Variant type.
+ *
+ * @param p_type The Variant type.
+ * @param r_name A pointer to a String to store the Variant type name.
+ */
+typedef void (*GDExtensionInterfaceVariantGetTypeName)(GDExtensionVariantType p_type, GDExtensionUninitializedStringPtr r_name);
+
+/**
+ * @name variant_can_convert
+ *
+ * Checks if Variants can be converted from one type to another.
+ *
+ * @param p_from The Variant type to convert from.
+ * @param p_to The Variant type to convert to.
+ *
+ * @return true if the conversion is possible; otherwise false.
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceVariantCanConvert)(GDExtensionVariantType p_from, GDExtensionVariantType p_to);
+
+/**
+ * @name variant_can_convert_strict
+ *
+ * Checks if Variant can be converted from one type to another using stricter rules.
+ *
+ * @param p_from The Variant type to convert from.
+ * @param p_to The Variant type to convert to.
+ *
+ * @return true if the conversion is possible; otherwise false.
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceVariantCanConvertStrict)(GDExtensionVariantType p_from, GDExtensionVariantType p_to);
+
+/**
+ * @name get_variant_from_type_constructor
+ *
+ * Gets a pointer to a function that can create a Variant of the given type from a raw value.
+ *
+ * @param p_type The Variant type.
+ *
+ * @return A pointer to a function that can create a Variant of the given type from a raw value.
+ */
+typedef GDExtensionVariantFromTypeConstructorFunc (*GDExtensionInterfaceGetVariantFromTypeConstructor)(GDExtensionVariantType p_type);
+
+/**
+ * @name get_variant_to_type_constructor
+ *
+ * Gets a pointer to a function that can get the raw value from a Variant of the given type.
+ *
+ * @param p_type The Variant type.
+ *
+ * @return A pointer to a function that can get the raw value from a Variant of the given type.
+ */
+typedef GDExtensionTypeFromVariantConstructorFunc (*GDExtensionInterfaceGetVariantToTypeConstructor)(GDExtensionVariantType p_type);
+
+/**
+ * @name variant_get_ptr_operator_evaluator
+ *
+ * Gets a pointer to a function that can evaluate the given Variant operator on the given Variant types.
+ *
+ * @param p_operator The variant operator.
+ * @param p_type_a The type of the first Variant.
+ * @param p_type_b The type of the second Variant.
+ *
+ * @return A pointer to a function that can evaluate the given Variant operator on the given Variant types.
+ */
+typedef GDExtensionPtrOperatorEvaluator (*GDExtensionInterfaceVariantGetPtrOperatorEvaluator)(GDExtensionVariantOperator p_operator, GDExtensionVariantType p_type_a, GDExtensionVariantType p_type_b);
+
+/**
+ * @name variant_get_ptr_builtin_method
+ *
+ * Gets a pointer to a function that can call a builtin method on a type of Variant.
+ *
+ * @param p_type The Variant type.
+ * @param p_method A pointer to a StringName with the method name.
+ * @param p_hash A hash representing the method signature.
+ *
+ * @return A pointer to a function that can call a builtin method on a type of Variant.
+ */
+typedef GDExtensionPtrBuiltInMethod (*GDExtensionInterfaceVariantGetPtrBuiltinMethod)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_method, GDExtensionInt p_hash);
+
+/**
+ * @name variant_get_ptr_constructor
+ *
+ * Gets a pointer to a function that can call one of the constructors for a type of Variant.
+ *
+ * @param p_type The Variant type.
+ * @param p_constructor The index of the constructor.
+ *
+ * @return A pointer to a function that can call one of the constructors for a type of Variant.
+ */
+typedef GDExtensionPtrConstructor (*GDExtensionInterfaceVariantGetPtrConstructor)(GDExtensionVariantType p_type, int32_t p_constructor);
+
+/**
+ * @name variant_get_ptr_destructor
+ *
+ * Gets a pointer to a function than can call the destructor for a type of Variant.
+ *
+ * @param p_type The Variant type.
+ *
+ * @return A pointer to a function than can call the destructor for a type of Variant.
+ */
+typedef GDExtensionPtrDestructor (*GDExtensionInterfaceVariantGetPtrDestructor)(GDExtensionVariantType p_type);
+
+/**
+ * @name variant_construct
+ *
+ * Constructs a Variant of the given type, using the first constructor that matches the given arguments.
+ *
+ * @param p_type The Variant type.
+ * @param p_base A pointer to a Variant to store the constructed value.
+ * @param p_args A pointer to a C array of Variant pointers representing the arguments for the constructor.
+ * @param p_argument_count The number of arguments to pass to the constructor.
+ * @param r_error A pointer the structure which will be updated with error information.
+ */
+typedef void (*GDExtensionInterfaceVariantConstruct)(GDExtensionVariantType p_type, GDExtensionUninitializedVariantPtr r_base, const GDExtensionConstVariantPtr *p_args, int32_t p_argument_count, GDExtensionCallError *r_error);
+
+/**
+ * @name variant_get_ptr_setter
+ *
+ * Gets a pointer to a function that can call a member's setter on the given Variant type.
+ *
+ * @param p_type The Variant type.
+ * @param p_member A pointer to a StringName with the member name.
+ *
+ * @return A pointer to a function that can call a member's setter on the given Variant type.
+ */
+typedef GDExtensionPtrSetter (*GDExtensionInterfaceVariantGetPtrSetter)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member);
+
+/**
+ * @name variant_get_ptr_getter
+ *
+ * Gets a pointer to a function that can call a member's getter on the given Variant type.
+ *
+ * @param p_type The Variant type.
+ * @param p_member A pointer to a StringName with the member name.
+ *
+ * @return A pointer to a function that can call a member's getter on the given Variant type.
+ */
+typedef GDExtensionPtrGetter (*GDExtensionInterfaceVariantGetPtrGetter)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_member);
+
+/**
+ * @name variant_get_ptr_indexed_setter
+ *
+ * Gets a pointer to a function that can set an index on the given Variant type.
+ *
+ * @param p_type The Variant type.
+ *
+ * @return A pointer to a function that can set an index on the given Variant type.
+ */
+typedef GDExtensionPtrIndexedSetter (*GDExtensionInterfaceVariantGetPtrIndexedSetter)(GDExtensionVariantType p_type);
+
+/**
+ * @name variant_get_ptr_indexed_getter
+ *
+ * Gets a pointer to a function that can get an index on the given Variant type.
+ *
+ * @param p_type The Variant type.
+ *
+ * @return A pointer to a function that can get an index on the given Variant type.
+ */
+typedef GDExtensionPtrIndexedGetter (*GDExtensionInterfaceVariantGetPtrIndexedGetter)(GDExtensionVariantType p_type);
+
+/**
+ * @name variant_get_ptr_keyed_setter
+ *
+ * Gets a pointer to a function that can set a key on the given Variant type.
+ *
+ * @param p_type The Variant type.
+ *
+ * @return A pointer to a function that can set a key on the given Variant type.
+ */
+typedef GDExtensionPtrKeyedSetter (*GDExtensionInterfaceVariantGetPtrKeyedSetter)(GDExtensionVariantType p_type);
+
+/**
+ * @name variant_get_ptr_keyed_getter
+ *
+ * Gets a pointer to a function that can get a key on the given Variant type.
+ *
+ * @param p_type The Variant type.
+ *
+ * @return A pointer to a function that can get a key on the given Variant type.
+ */
+typedef GDExtensionPtrKeyedGetter (*GDExtensionInterfaceVariantGetPtrKeyedGetter)(GDExtensionVariantType p_type);
+
+/**
+ * @name variant_get_ptr_keyed_checker
+ *
+ * Gets a pointer to a function that can check a key on the given Variant type.
+ *
+ * @param p_type The Variant type.
+ *
+ * @return A pointer to a function that can check a key on the given Variant type.
+ */
+typedef GDExtensionPtrKeyedChecker (*GDExtensionInterfaceVariantGetPtrKeyedChecker)(GDExtensionVariantType p_type);
+
+/**
+ * @name variant_get_constant_value
+ *
+ * Gets the value of a constant from the given Variant type.
+ *
+ * @param p_type The Variant type.
+ * @param p_constant A pointer to a StringName with the constant name.
+ * @param r_ret A pointer to a Variant to store the value.
+ */
+typedef void (*GDExtensionInterfaceVariantGetConstantValue)(GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_constant, GDExtensionUninitializedVariantPtr r_ret);
+
+/**
+ * @name variant_get_ptr_utility_function
+ *
+ * Gets a pointer to a function that can call a Variant utility function.
+ *
+ * @param p_function A pointer to a StringName with the function name.
+ * @param p_hash A hash representing the function signature.
+ *
+ * @return A pointer to a function that can call a Variant utility function.
+ */
+typedef GDExtensionPtrUtilityFunction (*GDExtensionInterfaceVariantGetPtrUtilityFunction)(GDExtensionConstStringNamePtr p_function, GDExtensionInt p_hash);
+
+/* INTERFACE: String Utilities */
+
+/**
+ * @name string_new_with_latin1_chars
+ *
+ * Creates a String from a Latin-1 encoded C string.
+ *
+ * @param r_dest A pointer to a Variant to hold the newly created String.
+ * @param p_contents A pointer to a Latin-1 encoded C string (null terminated).
+ */
+typedef void (*GDExtensionInterfaceStringNewWithLatin1Chars)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents);
+
+/**
+ * @name string_new_with_utf8_chars
+ *
+ * Creates a String from a UTF-8 encoded C string.
+ *
+ * @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 (null terminated).
+ */
+typedef void (*GDExtensionInterfaceStringNewWithUtf8Chars)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents);
+
+/**
+ * @name string_new_with_utf16_chars
+ *
+ * Creates a String from a UTF-16 encoded C string.
+ *
+ * @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 (null terminated).
+ */
+typedef void (*GDExtensionInterfaceStringNewWithUtf16Chars)(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents);
+
+/**
+ * @name string_new_with_utf32_chars
+ *
+ * Creates a String from a UTF-32 encoded C string.
+ *
+ * @param r_dest A pointer to a Variant to hold the newly created String.
+ * @param p_contents A pointer to a UTF-32 encoded C string (null terminated).
+ */
+typedef void (*GDExtensionInterfaceStringNewWithUtf32Chars)(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents);
+
+/**
+ * @name string_new_with_wide_chars
+ *
+ * Creates a String from a wide C string.
+ *
+ * @param r_dest A pointer to a Variant to hold the newly created String.
+ * @param p_contents A pointer to a wide C string (null terminated).
+ */
+typedef void (*GDExtensionInterfaceStringNewWithWideChars)(GDExtensionUninitializedStringPtr r_dest, const wchar_t *p_contents);
+
+/**
+ * @name string_new_with_latin1_chars_and_len
+ *
+ * Creates a String from a Latin-1 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 Latin-1 encoded C string.
+ * @param p_size The number of characters.
+ */
+typedef void (*GDExtensionInterfaceStringNewWithLatin1CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size);
+
+/**
+ * @name string_new_with_utf8_chars_and_len
+ *
+ * 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 characters.
+ */
+typedef void (*GDExtensionInterfaceStringNewWithUtf8CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char *p_contents, GDExtensionInt p_size);
+
+/**
+ * @name string_new_with_utf16_chars_and_len
+ *
+ * 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.
+ */
+typedef void (*GDExtensionInterfaceStringNewWithUtf16CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char16_t *p_contents, GDExtensionInt p_size);
+
+/**
+ * @name string_new_with_utf32_chars_and_len
+ *
+ * Creates a String from a UTF-32 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-32 encoded C string.
+ * @param p_size The number of characters.
+ */
+typedef void (*GDExtensionInterfaceStringNewWithUtf32CharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const char32_t *p_contents, GDExtensionInt p_size);
+
+/**
+ * @name string_new_with_wide_chars_and_len
+ *
+ * Creates a String from a wide 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 wide C string.
+ * @param p_size The number of characters.
+ */
+typedef void (*GDExtensionInterfaceStringNewWithWideCharsAndLen)(GDExtensionUninitializedStringPtr r_dest, const wchar_t *p_contents, GDExtensionInt p_size);
+
+/**
+ * @name string_to_latin1_chars
+ *
+ * Converts a String to a Latin-1 encoded C string.
+ *
+ * It doesn't write a null terminator.
+ *
+ * @param p_self A pointer to the String.
+ * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed.
+ * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value.
+ *
+ * @return The resulting encoded string length in characters (not bytes), not including a null terminator.
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceStringToLatin1Chars)(GDExtensionConstStringPtr p_self, char *r_text, GDExtensionInt p_max_write_length);
+
+/**
+ * @name string_to_utf8_chars
+ *
+ * Converts a String to a UTF-8 encoded C string.
+ *
+ * It doesn't write a null terminator.
+ *
+ * @param p_self A pointer to the String.
+ * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed.
+ * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value.
+ *
+ * @return The resulting encoded string length in characters (not bytes), not including a null terminator.
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceStringToUtf8Chars)(GDExtensionConstStringPtr p_self, char *r_text, GDExtensionInt p_max_write_length);
+
+/**
+ * @name string_to_utf16_chars
+ *
+ * Converts a String to a UTF-16 encoded C string.
+ *
+ * It doesn't write a null terminator.
+ *
+ * @param p_self A pointer to the String.
+ * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed.
+ * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value.
+ *
+ * @return The resulting encoded string length in characters (not bytes), not including a null terminator.
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceStringToUtf16Chars)(GDExtensionConstStringPtr p_self, char16_t *r_text, GDExtensionInt p_max_write_length);
+
+/**
+ * @name string_to_utf32_chars
+ *
+ * Converts a String to a UTF-32 encoded C string.
+ *
+ * It doesn't write a null terminator.
+ *
+ * @param p_self A pointer to the String.
+ * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed.
+ * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value.
+ *
+ * @return The resulting encoded string length in characters (not bytes), not including a null terminator.
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceStringToUtf32Chars)(GDExtensionConstStringPtr p_self, char32_t *r_text, GDExtensionInt p_max_write_length);
+
+/**
+ * @name string_to_wide_chars
+ *
+ * Converts a String to a wide C string.
+ *
+ * It doesn't write a null terminator.
+ *
+ * @param p_self A pointer to the String.
+ * @param r_text A pointer to the buffer to hold the resulting data. If NULL is passed in, only the length will be computed.
+ * @param p_max_write_length The maximum number of characters that can be written to r_text. It has no affect on the return value.
+ *
+ * @return The resulting encoded string length in characters (not bytes), not including a null terminator.
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceStringToWideChars)(GDExtensionConstStringPtr p_self, wchar_t *r_text, GDExtensionInt p_max_write_length);
+
+/**
+ * @name string_operator_index
+ *
+ * Gets a pointer to the character at the given index from a String.
+ *
+ * @param p_self A pointer to the String.
+ * @param p_index The index.
+ *
+ * @return A pointer to the requested character.
+ */
+typedef char32_t *(*GDExtensionInterfaceStringOperatorIndex)(GDExtensionStringPtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name string_operator_index_const
+ *
+ * Gets a const pointer to the character at the given index from a String.
+ *
+ * @param p_self A pointer to the String.
+ * @param p_index The index.
+ *
+ * @return A const pointer to the requested character.
+ */
+typedef const char32_t *(*GDExtensionInterfaceStringOperatorIndexConst)(GDExtensionConstStringPtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name string_operator_plus_eq_string
+ *
+ * Appends another String to a String.
+ *
+ * @param p_self A pointer to the String.
+ * @param p_b A pointer to the other String to append.
+ */
+typedef void (*GDExtensionInterfaceStringOperatorPlusEqString)(GDExtensionStringPtr p_self, GDExtensionConstStringPtr p_b);
+
+/**
+ * @name string_operator_plus_eq_char
+ *
+ * Appends a character to a String.
+ *
+ * @param p_self A pointer to the String.
+ * @param p_b A pointer to the character to append.
+ */
+typedef void (*GDExtensionInterfaceStringOperatorPlusEqChar)(GDExtensionStringPtr p_self, char32_t p_b);
+
+/**
+ * @name string_operator_plus_eq_cstr
+ *
+ * Appends a Latin-1 encoded C string to a String.
+ *
+ * @param p_self A pointer to the String.
+ * @param p_b A pointer to a Latin-1 encoded C string (null terminated).
+ */
+typedef void (*GDExtensionInterfaceStringOperatorPlusEqCstr)(GDExtensionStringPtr p_self, const char *p_b);
+
+/**
+ * @name string_operator_plus_eq_wcstr
+ *
+ * Appends a wide C string to a String.
+ *
+ * @param p_self A pointer to the String.
+ * @param p_b A pointer to a wide C string (null terminated).
+ */
+typedef void (*GDExtensionInterfaceStringOperatorPlusEqWcstr)(GDExtensionStringPtr p_self, const wchar_t *p_b);
+
+/**
+ * @name string_operator_plus_eq_c32str
+ *
+ * Appends a UTF-32 encoded C string to a String.
+ *
+ * @param p_self A pointer to the String.
+ * @param p_b A pointer to a UTF-32 encoded C string (null terminated).
+ */
+typedef void (*GDExtensionInterfaceStringOperatorPlusEqC32str)(GDExtensionStringPtr p_self, const char32_t *p_b);
+
+/* INTERFACE: XMLParser Utilities */
+
+/**
+ * @name xml_parser_open_buffer
+ *
+ * Opens a raw XML buffer on an XMLParser instance.
+ *
+ * @param p_instance A pointer to an XMLParser object.
+ * @param p_buffer A pointer to the buffer.
+ * @param p_size The size of the buffer.
+ *
+ * @return A Godot error code (ex. OK, ERR_INVALID_DATA, etc).
+ *
+ * @see XMLParser::open_buffer()
+ */
+typedef GDExtensionInt (*GDExtensionInterfaceXmlParserOpenBuffer)(GDExtensionObjectPtr p_instance, const uint8_t *p_buffer, size_t p_size);
+
+/* INTERFACE: FileAccess Utilities */
+
+/**
+ * @name file_access_store_buffer
+ *
+ * Stores the given buffer using an instance of FileAccess.
+ *
+ * @param p_instance A pointer to a FileAccess object.
+ * @param p_src A pointer to the buffer.
+ * @param p_length The size of the buffer.
+ *
+ * @see FileAccess::store_buffer()
+ */
+typedef void (*GDExtensionInterfaceFileAccessStoreBuffer)(GDExtensionObjectPtr p_instance, const uint8_t *p_src, uint64_t p_length);
+
+/**
+ * @name file_access_get_buffer
+ *
+ * Reads the next p_length bytes into the given buffer using an instance of FileAccess.
+ *
+ * @param p_instance A pointer to a FileAccess object.
+ * @param p_dst A pointer to the buffer to store the data.
+ * @param p_length The requested number of bytes to read.
+ *
+ * @return The actual number of bytes read (may be less than requested).
+ */
+typedef uint64_t (*GDExtensionInterfaceFileAccessGetBuffer)(GDExtensionConstObjectPtr p_instance, uint8_t *p_dst, uint64_t p_length);
+
+/* INTERFACE: WorkerThreadPool Utilities */
+
+/**
+ * @name worker_thread_pool_add_native_group_task
+ *
+ * Adds a group task to an instance of WorkerThreadPool.
+ *
+ * @param p_instance A pointer to a WorkerThreadPool object.
+ * @param p_func A pointer to a function to run in the thread pool.
+ * @param p_userdata A pointer to arbitrary data which will be passed to p_func.
+ * @param p_tasks The number of tasks needed in the group.
+ * @param p_high_priority Whether or not this is a high priority task.
+ * @param p_description A pointer to a String with the task description.
+ *
+ * @return The task group ID.
+ *
+ * @see WorkerThreadPool::add_group_task()
+ */
+typedef int64_t (*GDExtensionInterfaceWorkerThreadPoolAddNativeGroupTask)(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);
+
+/**
+ * @name worker_thread_pool_add_native_task
+ *
+ * Adds a task to an instance of WorkerThreadPool.
+ *
+ * @param p_instance A pointer to a WorkerThreadPool object.
+ * @param p_func A pointer to a function to run in the thread pool.
+ * @param p_userdata A pointer to arbitrary data which will be passed to p_func.
+ * @param p_high_priority Whether or not this is a high priority task.
+ * @param p_description A pointer to a String with the task description.
+ *
+ * @return The task ID.
+ */
+typedef int64_t (*GDExtensionInterfaceWorkerThreadPoolAddNativeTask)(GDExtensionObjectPtr p_instance, void (*p_func)(void *), void *p_userdata, GDExtensionBool p_high_priority, GDExtensionConstStringPtr p_description);
+
+/* INTERFACE: Packed Array */
+
+/**
+ * @name packed_byte_array_operator_index
+ *
+ * Gets a pointer to a byte in a PackedByteArray.
+ *
+ * @param p_self A pointer to a PackedByteArray object.
+ * @param p_index The index of the byte to get.
+ *
+ * @return A pointer to the requested byte.
+ */
+typedef uint8_t *(*GDExtensionInterfacePackedByteArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_byte_array_operator_index_const
+ *
+ * Gets a const pointer to a byte in a PackedByteArray.
+ *
+ * @param p_self A const pointer to a PackedByteArray object.
+ * @param p_index The index of the byte to get.
+ *
+ * @return A const pointer to the requested byte.
+ */
+typedef const uint8_t *(*GDExtensionInterfacePackedByteArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_color_array_operator_index
+ *
+ * Gets a pointer to a color in a PackedColorArray.
+ *
+ * @param p_self A pointer to a PackedColorArray object.
+ * @param p_index The index of the Color to get.
+ *
+ * @return A pointer to the requested Color.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedColorArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_color_array_operator_index_const
+ *
+ * Gets a const pointer to a color in a PackedColorArray.
+ *
+ * @param p_self A const pointer to a const PackedColorArray object.
+ * @param p_index The index of the Color to get.
+ *
+ * @return A const pointer to the requested Color.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedColorArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_float32_array_operator_index
+ *
+ * Gets a pointer to a 32-bit float in a PackedFloat32Array.
+ *
+ * @param p_self A pointer to a PackedFloat32Array object.
+ * @param p_index The index of the float to get.
+ *
+ * @return A pointer to the requested 32-bit float.
+ */
+typedef float *(*GDExtensionInterfacePackedFloat32ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_float32_array_operator_index_const
+ *
+ * Gets a const pointer to a 32-bit float in a PackedFloat32Array.
+ *
+ * @param p_self A const pointer to a PackedFloat32Array object.
+ * @param p_index The index of the float to get.
+ *
+ * @return A const pointer to the requested 32-bit float.
+ */
+typedef const float *(*GDExtensionInterfacePackedFloat32ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_float64_array_operator_index
+ *
+ * Gets a pointer to a 64-bit float in a PackedFloat64Array.
+ *
+ * @param p_self A pointer to a PackedFloat64Array object.
+ * @param p_index The index of the float to get.
+ *
+ * @return A pointer to the requested 64-bit float.
+ */
+typedef double *(*GDExtensionInterfacePackedFloat64ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_float64_array_operator_index_const
+ *
+ * Gets a const pointer to a 64-bit float in a PackedFloat64Array.
+ *
+ * @param p_self A const pointer to a PackedFloat64Array object.
+ * @param p_index The index of the float to get.
+ *
+ * @return A const pointer to the requested 64-bit float.
+ */
+typedef const double *(*GDExtensionInterfacePackedFloat64ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_int32_array_operator_index
+ *
+ * Gets a pointer to a 32-bit integer in a PackedInt32Array.
+ *
+ * @param p_self A pointer to a PackedInt32Array object.
+ * @param p_index The index of the integer to get.
+ *
+ * @return A pointer to the requested 32-bit integer.
+ */
+typedef int32_t *(*GDExtensionInterfacePackedInt32ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_int32_array_operator_index_const
+ *
+ * Gets a const pointer to a 32-bit integer in a PackedInt32Array.
+ *
+ * @param p_self A const pointer to a PackedInt32Array object.
+ * @param p_index The index of the integer to get.
+ *
+ * @return A const pointer to the requested 32-bit integer.
+ */
+typedef const int32_t *(*GDExtensionInterfacePackedInt32ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_int64_array_operator_index
+ *
+ * Gets a pointer to a 64-bit integer in a PackedInt64Array.
+ *
+ * @param p_self A pointer to a PackedInt64Array object.
+ * @param p_index The index of the integer to get.
+ *
+ * @return A pointer to the requested 64-bit integer.
+ */
+typedef int64_t *(*GDExtensionInterfacePackedInt64ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_int64_array_operator_index_const
+ *
+ * Gets a const pointer to a 64-bit integer in a PackedInt64Array.
+ *
+ * @param p_self A const pointer to a PackedInt64Array object.
+ * @param p_index The index of the integer to get.
+ *
+ * @return A const pointer to the requested 64-bit integer.
+ */
+typedef const int64_t *(*GDExtensionInterfacePackedInt64ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_string_array_operator_index
+ *
+ * Gets a pointer to a string in a PackedStringArray.
+ *
+ * @param p_self A pointer to a PackedStringArray object.
+ * @param p_index The index of the String to get.
+ *
+ * @return A pointer to the requested String.
+ */
+typedef GDExtensionStringPtr (*GDExtensionInterfacePackedStringArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_string_array_operator_index_const
+ *
+ * Gets a const pointer to a string in a PackedStringArray.
+ *
+ * @param p_self A const pointer to a PackedStringArray object.
+ * @param p_index The index of the String to get.
+ *
+ * @return A const pointer to the requested String.
+ */
+typedef GDExtensionStringPtr (*GDExtensionInterfacePackedStringArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_vector2_array_operator_index
+ *
+ * Gets a pointer to a Vector2 in a PackedVector2Array.
+ *
+ * @param p_self A pointer to a PackedVector2Array object.
+ * @param p_index The index of the Vector2 to get.
+ *
+ * @return A pointer to the requested Vector2.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector2ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_vector2_array_operator_index_const
+ *
+ * Gets a const pointer to a Vector2 in a PackedVector2Array.
+ *
+ * @param p_self A const pointer to a PackedVector2Array object.
+ * @param p_index The index of the Vector2 to get.
+ *
+ * @return A const pointer to the requested Vector2.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector2ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_vector3_array_operator_index
+ *
+ * Gets a pointer to a Vector3 in a PackedVector3Array.
+ *
+ * @param p_self A pointer to a PackedVector3Array object.
+ * @param p_index The index of the Vector3 to get.
+ *
+ * @return A pointer to the requested Vector3.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector3ArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name packed_vector3_array_operator_index_const
+ *
+ * Gets a const pointer to a Vector3 in a PackedVector3Array.
+ *
+ * @param p_self A const pointer to a PackedVector3Array object.
+ * @param p_index The index of the Vector3 to get.
+ *
+ * @return A const pointer to the requested Vector3.
+ */
+typedef GDExtensionTypePtr (*GDExtensionInterfacePackedVector3ArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name array_operator_index
+ *
+ * Gets a pointer to a Variant in an Array.
+ *
+ * @param p_self A pointer to an Array object.
+ * @param p_index The index of the Variant to get.
+ *
+ * @return A pointer to the requested Variant.
+ */
+typedef GDExtensionVariantPtr (*GDExtensionInterfaceArrayOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name array_operator_index_const
+ *
+ * Gets a const pointer to a Variant in an Array.
+ *
+ * @param p_self A const pointer to an Array object.
+ * @param p_index The index of the Variant to get.
+ *
+ * @return A const pointer to the requested Variant.
+ */
+typedef GDExtensionVariantPtr (*GDExtensionInterfaceArrayOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index);
+
+/**
+ * @name array_ref
+ *
+ * Sets an Array to be a reference to another Array object.
+ *
+ * @param p_self A pointer to the Array object to update.
+ * @param p_from A pointer to the Array object to reference.
+ */
+typedef void (*GDExtensionInterfaceArrayRef)(GDExtensionTypePtr p_self, GDExtensionConstTypePtr p_from);
+
+/**
+ * @name array_set_typed
+ *
+ * Makes an Array into a typed Array.
+ *
+ * @param p_self A pointer to the Array.
+ * @param p_type The type of Variant the Array will store.
+ * @param p_class_name A pointer to a StringName with the name of the object (if p_type is GDEXTENSION_VARIANT_TYPE_OBJECT).
+ * @param p_script A pointer to a Script object (if p_type is GDEXTENSION_VARIANT_TYPE_OBJECT and the base class is extended by a script).
+ */
+typedef void (*GDExtensionInterfaceArraySetTyped)(GDExtensionTypePtr p_self, GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script);
+
+/* INTERFACE: Dictionary */
+
+/**
+ * @name dictionary_operator_index
+ *
+ * Gets a pointer to a Variant in a Dictionary with the given key.
+ *
+ * @param p_self A pointer to a Dictionary object.
+ * @param p_key A pointer to a Variant representing the key.
+ *
+ * @return A pointer to a Variant representing the value at the given key.
+ */
+typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndex)(GDExtensionTypePtr p_self, GDExtensionConstVariantPtr p_key);
+
+/**
+ * @name dictionary_operator_index_const
+ *
+ * Gets a const pointer to a Variant in a Dictionary with the given key.
+ *
+ * @param p_self A const pointer to a Dictionary object.
+ * @param p_key A pointer to a Variant representing the key.
+ *
+ * @return A const pointer to a Variant representing the value at the given key.
+ */
+typedef GDExtensionVariantPtr (*GDExtensionInterfaceDictionaryOperatorIndexConst)(GDExtensionConstTypePtr p_self, GDExtensionConstVariantPtr p_key);
+
+/* INTERFACE: Object */
+
+/**
+ * @name object_method_bind_call
+ *
+ * Calls a method on an Object.
+ *
+ * @param p_method_bind A pointer to the MethodBind representing the method on the Object's class.
+ * @param p_instance A pointer to the Object.
+ * @param p_args A pointer to a C array of Variants representing the arguments.
+ * @param p_arg_count The number of arguments.
+ * @param r_ret A pointer to Variant which will receive the return value.
+ * @param r_error A pointer to a GDExtensionCallError struct that will receive error information.
+ */
+typedef void (*GDExtensionInterfaceObjectMethodBindCall)(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstVariantPtr *p_args, GDExtensionInt p_arg_count, GDExtensionUninitializedVariantPtr r_ret, GDExtensionCallError *r_error);
+
+/**
+ * @name object_method_bind_ptrcall
+ *
+ * Calls a method on an Object (using a "ptrcall").
+ *
+ * @param p_method_bind A pointer to the MethodBind representing the method on the Object's class.
+ * @param p_instance A pointer to the Object.
+ * @param p_args A pointer to a C array representing the arguments.
+ * @param r_ret A pointer to the Object that will receive the return value.
+ */
+typedef void (*GDExtensionInterfaceObjectMethodBindPtrcall)(GDExtensionMethodBindPtr p_method_bind, GDExtensionObjectPtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret);
+
+/**
+ * @name object_destroy
+ *
+ * Destroys an Object.
+ *
+ * @param p_o A pointer to the Object.
+ */
+typedef void (*GDExtensionInterfaceObjectDestroy)(GDExtensionObjectPtr p_o);
+
+/**
+ * @name global_get_singleton
+ *
+ * Gets a global singleton by name.
+ *
+ * @param p_name A pointer to a StringName with the singleton name.
+ *
+ * @return A pointer to the singleton Object.
+ */
+typedef GDExtensionObjectPtr (*GDExtensionInterfaceGlobalGetSingleton)(GDExtensionConstStringNamePtr p_name);
+
+/**
+ * @name object_get_instance_binding
+ *
+ * Gets a pointer representing an Object's instance binding.
+ *
+ * @param p_o A pointer to the Object.
+ * @param p_library A token the library received by the GDExtension's entry point function.
+ * @param p_callbacks A pointer to a GDExtensionInstanceBindingCallbacks struct.
+ *
+ * @return
+ */
+typedef void *(*GDExtensionInterfaceObjectGetInstanceBinding)(GDExtensionObjectPtr p_o, void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks);
+
+/**
+ * @name object_set_instance_binding
+ *
+ * Sets an Object's instance binding.
+ *
+ * @param p_o A pointer to the Object.
+ * @param p_library A token the library received by the GDExtension's entry point function.
+ * @param p_binding A pointer to the instance binding.
+ * @param p_callbacks A pointer to a GDExtensionInstanceBindingCallbacks struct.
+ */
+typedef void (*GDExtensionInterfaceObjectSetInstanceBinding)(GDExtensionObjectPtr p_o, void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks);
+
+/**
+ * @name object_set_instance
+ *
+ * Sets an extension class instance on a Object.
+ *
+ * @param p_o A pointer to the Object.
+ * @param p_classname A pointer to a StringName with the registered extension class's name.
+ * @param p_instance A pointer to the extension class instance.
+ */
+typedef void (*GDExtensionInterfaceObjectSetInstance)(GDExtensionObjectPtr p_o, GDExtensionConstStringNamePtr p_classname, GDExtensionClassInstancePtr p_instance); /* p_classname should be a registered extension class and should extend the p_o object's class. */
+
+/**
+ * @name object_get_class_name
+ *
+ * Gets the class name of an Object.
+ *
+ * @param p_object A pointer to the Object.
+ * @param p_library A pointer the library received by the GDExtension's entry point function.
+ * @param r_class_name A pointer to a String to receive the class name.
+ *
+ * @return true if successful in getting the class name; otherwise false.
+ */
+typedef GDExtensionBool (*GDExtensionInterfaceObjectGetClassName)(GDExtensionConstObjectPtr p_object, GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringNamePtr r_class_name);
+
+/**
+ * @name object_cast_to
+ *
+ * Casts an Object to a different type.
+ *
+ * @param p_object A pointer to the Object.
+ * @param p_class_tag A pointer uniquely identifying a built-in class in the ClassDB.
+ *
+ * @return Returns a pointer to the Object, or NULL if it can't be cast to the requested type.
+ */
+typedef GDExtensionObjectPtr (*GDExtensionInterfaceObjectCastTo)(GDExtensionConstObjectPtr p_object, void *p_class_tag);
+
+/**
+ * @name object_get_instance_from_id
+ *
+ * Gets an Object by its instance ID.
+ *
+ * @param p_instance_id The instance ID.
+ *
+ * @return A pointer to the Object.
+ */
+typedef GDExtensionObjectPtr (*GDExtensionInterfaceObjectGetInstanceFromId)(GDObjectInstanceID p_instance_id);
+
+/**
+ * @name object_get_instance_id
+ *
+ * Gets the instance ID from an Object.
+ *
+ * @param p_object A pointer to the Object.
+ *
+ * @return The instance ID.
+ */
+typedef GDObjectInstanceID (*GDExtensionInterfaceObjectGetInstanceId)(GDExtensionConstObjectPtr p_object);
+
+/* INTERFACE: Reference */
+
+/**
+ * @name ref_get_object
+ *
+ * Gets the Object from a reference.
+ *
+ * @param p_ref A pointer to the reference.
+ *
+ * @return A pointer to the Object from the reference or NULL.
+ */
+typedef GDExtensionObjectPtr (*GDExtensionInterfaceRefGetObject)(GDExtensionConstRefPtr p_ref);
+
+/**
+ * @name ref_set_object
+ *
+ * Sets the Object referred to by a reference.
+ *
+ * @param p_ref A pointer to the reference.
+ * @param p_object A pointer to the Object to refer to.
+ */
+typedef void (*GDExtensionInterfaceRefSetObject)(GDExtensionRefPtr p_ref, GDExtensionObjectPtr p_object);
+
+/* INTERFACE: Script Instance */
+
+/**
+ * @name script_instance_create
+ *
+ * Creates a script instance that contains the given info and instance data.
+ *
+ * @param p_info A pointer to a GDExtensionScriptInstanceInfo struct.
+ * @param p_instance_data A pointer to a data representing the script instance in the GDExtension. This will be passed to all the function pointers on p_info.
+ *
+ * @return A pointer to a ScriptInstanceExtension object.
+ */
+typedef GDExtensionScriptInstancePtr (*GDExtensionInterfaceScriptInstanceCreate)(const GDExtensionScriptInstanceInfo *p_info, GDExtensionScriptInstanceDataPtr p_instance_data);
+
+/* INTERFACE: ClassDB */
+
+/**
+ * @name classdb_construct_object
+ *
+ * Constructs an Object of the requested class.
+ *
+ * The passed class must be a built-in godot class, or an already-registered extension class. In both cases, object_set_instance() should be called to fully initialize the object.
+ *
+ * @param p_classname A pointer to a StringName with the class name.
+ *
+ * @return A pointer to the newly created Object.
+ */
+typedef GDExtensionObjectPtr (*GDExtensionInterfaceClassdbConstructObject)(GDExtensionConstStringNamePtr p_classname);
+
+/**
+ * @name classdb_get_method_bind
+ *
+ * Gets a pointer to the MethodBind in ClassDB for the given class, method and hash.
+ *
+ * @param p_classname A pointer to a StringName with the class name.
+ * @param p_methodname A pointer to a StringName with the method name.
+ * @param p_hash A hash representing the function signature.
+ *
+ * @return A pointer to the MethodBind from ClassDB.
+ */
+typedef GDExtensionMethodBindPtr (*GDExtensionInterfaceClassdbGetMethodBind)(GDExtensionConstStringNamePtr p_classname, GDExtensionConstStringNamePtr p_methodname, GDExtensionInt p_hash);
+
+/**
+ * @name classdb_get_class_tag
+ *
+ * Gets a pointer uniquely identifying the given built-in class in the ClassDB.
+ *
+ * @param p_classname A pointer to a StringName with the class name.
+ *
+ * @return A pointer uniquely identifying the built-in class in the ClassDB.
+ */
+typedef void *(*GDExtensionInterfaceClassdbGetClassTag)(GDExtensionConstStringNamePtr p_classname);
+
+/* INTERFACE: ClassDB Extension */
+
+/**
+ * @name classdb_register_extension_class
+ *
+ * Registers an extension class in the ClassDB.
+ *
+ * Provided struct can be safely freed once the function returns.
+ *
+ * @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_parent_class_name A pointer to a StringName with the parent class name.
+ * @param p_extension_funcs A pointer to a GDExtensionClassCreationInfo struct.
+ */
+typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClass)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs);
+
+/**
+ * @name classdb_register_extension_class_method
+ *
+ * Registers a method on an extension class in the ClassDB.
+ *
+ * Provided struct can be safely freed once the function returns.
+ *
+ * @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_method_info A pointer to a GDExtensionClassMethodInfo struct.
+ */
+typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassMethod)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info);
+
+/**
+ * @name classdb_register_extension_class_integer_constant
+ *
+ * Registers an integer constant on an extension class in the ClassDB.
+ *
+ * @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.
+ */
+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);
+
+/**
+ * @name classdb_register_extension_class_property
+ *
+ * Registers a property on an extension class in the ClassDB.
+ *
+ * Provided struct can be safely freed once the function returns.
+ *
+ * @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_info A pointer to a GDExtensionPropertyInfo struct.
+ * @param p_setter A pointer to a StringName with the name of the setter method.
+ * @param p_getter A pointer to a StringName with the name of the getter method.
+ */
+typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassProperty)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter);
+
+/**
+ * @name classdb_register_extension_class_property_group
+ *
+ * Registers a property group on an extension class in the ClassDB.
+ *
+ * @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_group_name A pointer to a String with the group name.
+ * @param p_prefix A pointer to a String with the prefix used by properties in this group.
+ */
+typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassPropertyGroup)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_group_name, GDExtensionConstStringPtr p_prefix);
+
+/**
+ * @name classdb_register_extension_class_property_subgroup
+ *
+ * Registers a property subgroup on an extension class in the ClassDB.
+ *
+ * @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_subgroup_name A pointer to a String with the subgroup name.
+ * @param p_prefix A pointer to a String with the prefix used by properties in this subgroup.
+ */
+typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassPropertySubgroup)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_subgroup_name, GDExtensionConstStringPtr p_prefix);
+
+/**
+ * @name classdb_register_extension_class_signal
+ *
+ * Registers a signal on an extension class in the ClassDB.
+ *
+ * Provided structs can be safely freed once the function returns.
+ *
+ * @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_signal_name A pointer to a StringName with the signal name.
+ * @param p_argument_info A pointer to a GDExtensionPropertyInfo struct.
+ * @param p_argument_count The number of arguments the signal receives.
+ */
+typedef void (*GDExtensionInterfaceClassdbRegisterExtensionClassSignal)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_signal_name, const GDExtensionPropertyInfo *p_argument_info, GDExtensionInt p_argument_count);
+
+/**
+ * @name classdb_unregister_extension_class
+ *
+ * Unregisters an extension class in the ClassDB.
+ *
+ * @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.
+ */
+typedef void (*GDExtensionInterfaceClassdbUnregisterExtensionClass)(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name); /* Unregistering a parent class before a class that inherits it will result in failure. Inheritors must be unregistered first. */
+
+/**
+ * @name get_library_path
+ *
+ * Gets the path to the current GDExtension library.
+ *
+ * @param p_library A pointer the library received by the GDExtension's entry point function.
+ * @param r_path A pointer to a String which will receive the path.
+ */
+typedef void (*GDExtensionInterfaceGetLibraryPath)(GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringPtr r_path);
+
+/**
+ * @name editor_add_plugin
+ *
+ * Adds an editor plugin.
+ *
+ * It's safe to call during initialization.
+ *
+ * @param p_class_name A pointer to a StringName with the name of a class (descending from EditorPlugin) which is already registered with ClassDB.
+ */
+typedef void (*GDExtensionInterfaceEditorAddPlugin)(GDExtensionConstStringNamePtr p_class_name);
+
+/**
+ * @name editor_remove_plugin
+ *
+ * Removes an editor plugin.
+ *
+ * @param p_class_name A pointer to a StringName with the name of a class that was previously added as an editor plugin.
*/
-typedef GDExtensionBool (*GDExtensionInitializationFunction)(const GDExtensionInterface *p_interface, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization);
+typedef void (*GDExtensionInterfaceEditorRemovePlugin)(GDExtensionConstStringNamePtr p_class_name);
#ifdef __cplusplus
}
diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp
index 8701e6d77b..63e809bc7c 100644
--- a/core/extension/gdextension_manager.cpp
+++ b/core/extension/gdextension_manager.cpp
@@ -50,6 +50,11 @@ GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &
extension->initialize_library(GDExtension::InitializationLevel(i));
}
}
+
+ for (const KeyValue<String, String> &kv : extension->class_icon_paths) {
+ gdextension_class_icon_paths[kv.key] = kv.value;
+ }
+
gdextension_map[p_path] = extension;
return LOAD_STATUS_OK;
}
@@ -74,6 +79,11 @@ GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String
extension->deinitialize_library(GDExtension::InitializationLevel(i));
}
}
+
+ for (const KeyValue<String, String> &kv : extension->class_icon_paths) {
+ gdextension_class_icon_paths.erase(kv.key);
+ }
+
gdextension_map.erase(p_path);
return LOAD_STATUS_OK;
}
@@ -95,6 +105,19 @@ Ref<GDExtension> GDExtensionManager::get_extension(const String &p_path) {
return E->value;
}
+bool GDExtensionManager::class_has_icon_path(const String &p_class) const {
+ // TODO: Check that the icon belongs to a registered class somehow.
+ return gdextension_class_icon_paths.has(p_class);
+}
+
+String GDExtensionManager::class_get_icon_path(const String &p_class) const {
+ // TODO: Check that the icon belongs to a registered class somehow.
+ if (gdextension_class_icon_paths.has(p_class)) {
+ return gdextension_class_icon_paths[p_class];
+ }
+ return "";
+}
+
void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) {
ERR_FAIL_COND(int32_t(p_level) - 1 != level);
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h
index 456942af0d..3643f043d8 100644
--- a/core/extension/gdextension_manager.h
+++ b/core/extension/gdextension_manager.h
@@ -38,6 +38,7 @@ class GDExtensionManager : public Object {
int32_t level = -1;
HashMap<String, Ref<GDExtension>> gdextension_map;
+ HashMap<String, String> gdextension_class_icon_paths;
static void _bind_methods();
@@ -59,6 +60,9 @@ public:
Vector<String> get_loaded_extensions() const;
Ref<GDExtension> get_extension(const String &p_path);
+ bool class_has_icon_path(const String &p_class) const;
+ String class_get_icon_path(const String &p_class) const;
+
void initialize_extensions(GDExtension::InitializationLevel p_level);
void deinitialize_extensions(GDExtension::InitializationLevel p_level);
diff --git a/core/extension/make_interface_dumper.py b/core/extension/make_interface_dumper.py
index a604112d13..a85d62eff3 100644
--- a/core/extension/make_interface_dumper.py
+++ b/core/extension/make_interface_dumper.py
@@ -1,9 +1,19 @@
+import zlib
+
+
def run(target, source, env):
src = source[0]
dst = target[0]
- f = open(src, "r", encoding="utf-8")
+ f = open(src, "rb")
g = open(dst, "w", encoding="utf-8")
+ buf = f.read()
+ decomp_size = len(buf)
+
+ # Use maximum zlib compression level to further reduce file size
+ # (at the cost of initial build times).
+ buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION)
+
g.write(
"""/* THIS FILE IS GENERATED DO NOT EDIT */
#ifndef GDEXTENSION_INTERFACE_DUMP_H
@@ -11,25 +21,32 @@ def run(target, source, env):
#ifdef TOOLS_ENABLED
+#include "core/io/compression.h"
#include "core/io/file_access.h"
#include "core/string/ustring.h"
-class GDExtensionInterfaceDump {
- private:
- static constexpr char const *gdextension_interface_dump ="""
+"""
)
- for line in f:
- g.write('"' + line.rstrip().replace('"', '\\"') + '\\n"\n')
- g.write(";\n")
+
+ g.write("static const int _gdextension_interface_data_compressed_size = " + str(len(buf)) + ";\n")
+ g.write("static const int _gdextension_interface_data_uncompressed_size = " + str(decomp_size) + ";\n")
+ g.write("static const unsigned char _gdextension_interface_data_compressed[] = {\n")
+ for i in range(len(buf)):
+ g.write("\t" + str(buf[i]) + ",\n")
+ g.write("};\n")
g.write(
"""
+class GDExtensionInterfaceDump {
public:
static void generate_gdextension_interface_file(const String &p_path) {
Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path));
- CharString cs(gdextension_interface_dump);
- fa->store_buffer((const uint8_t *)cs.ptr(), cs.length());
+ Vector<uint8_t> data;
+ data.resize(_gdextension_interface_data_uncompressed_size);
+ int ret = Compression::decompress(data.ptrw(), _gdextension_interface_data_uncompressed_size, _gdextension_interface_data_compressed, _gdextension_interface_data_compressed_size, Compression::MODE_DEFLATE);
+ ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");
+ fa->store_buffer(data.ptr(), data.size());
};
};
diff --git a/core/input/godotcontrollerdb.txt b/core/input/godotcontrollerdb.txt
index 6ead872149..7c51e20b4c 100644
--- a/core/input/godotcontrollerdb.txt
+++ b/core/input/godotcontrollerdb.txt
@@ -2,7 +2,7 @@
# Source: https://github.com/godotengine/godot
# Windows
-__XINPUT_DEVICE__,XInput Gamepad,a:b12,b:b13,x:b14,y:b15,start:b4,back:b5,leftstick:b6,rightstick:b7,leftshoulder:b8,rightshoulder:b9,dpup:b0,dpdown:b1,dpleft:b2,dpright:b3,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Windows,
+__XINPUT_DEVICE__,XInput Gamepad,a:b12,b:b13,x:b14,y:b15,start:b4,guide:b10,back:b5,leftstick:b6,rightstick:b7,leftshoulder:b8,rightshoulder:b9,dpup:b0,dpdown:b1,dpleft:b2,dpright:b3,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Windows,
# Android
Default Android Gamepad,Default Controller,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b8,rightshoulder:b10,rightx:a2,start:b6,righty:a3,dpleft:h0.8,lefttrigger:a4,x:b2,dpup:h0.1,back:b4,leftstick:b7,leftshoulder:b9,y:b3,a:b0,dpright:h0.2,righttrigger:a5,b:b1,platform:Android,
diff --git a/core/input/input.cpp b/core/input/input.cpp
index e74523e059..cf8d71b9a7 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -35,6 +35,10 @@
#include "core/input/input_map.h"
#include "core/os/os.h"
+#ifdef DEV_ENABLED
+#include "core/os/thread.h"
+#endif
+
static const char *_joy_buttons[(size_t)JoyButton::SDL_MAX] = {
"a",
"b",
@@ -293,10 +297,13 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con
return false;
}
+ // Backward compatibility for legacy behavior, only return true if currently pressed.
+ bool pressed_requirement = legacy_just_pressed_behavior ? E->value.pressed : true;
+
if (Engine::get_singleton()->is_in_physics_frame()) {
- return E->value.pressed && E->value.physics_frame == Engine::get_singleton()->get_physics_frames();
+ return pressed_requirement && E->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
- return E->value.pressed && E->value.process_frame == Engine::get_singleton()->get_process_frames();
+ return pressed_requirement && E->value.pressed_process_frame == Engine::get_singleton()->get_process_frames();
}
}
@@ -311,10 +318,13 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co
return false;
}
+ // Backward compatibility for legacy behavior, only return true if currently released.
+ bool released_requirement = legacy_just_pressed_behavior ? !E->value.pressed : true;
+
if (Engine::get_singleton()->is_in_physics_frame()) {
- return !E->value.pressed && E->value.physics_frame == Engine::get_singleton()->get_physics_frames();
+ return released_requirement && E->value.released_physics_frame == Engine::get_singleton()->get_physics_frames();
} else {
- return !E->value.pressed && E->value.process_frame == Engine::get_singleton()->get_process_frames();
+ return released_requirement && E->value.released_process_frame == Engine::get_singleton()->get_process_frames();
}
}
@@ -486,6 +496,10 @@ Vector3 Input::get_gyroscope() const {
}
void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_emulated) {
+ // This function does the final delivery of the input event to user land.
+ // Regardless where the event came from originally, this has to happen on the main thread.
+ DEV_ASSERT(Thread::get_caller_id() == Thread::get_main_id());
+
// Notes on mouse-touch emulation:
// - Emulated mouse events are parsed, that is, re-routed to this method, so they make the same effects
// as true mouse events. The only difference is the situation is flagged as emulated so they are not
@@ -534,10 +548,13 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
Ref<InputEventScreenTouch> touch_event;
touch_event.instantiate();
touch_event->set_pressed(mb->is_pressed());
+ touch_event->set_canceled(mb->is_canceled());
touch_event->set_position(mb->get_position());
touch_event->set_double_tap(mb->is_double_click());
touch_event->set_device(InputEvent::DEVICE_ID_EMULATION);
+ _THREAD_SAFE_UNLOCK_
event_dispatch_function(touch_event);
+ _THREAD_SAFE_LOCK_
}
}
@@ -563,7 +580,9 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
drag_event->set_velocity(get_last_mouse_velocity());
drag_event->set_device(InputEvent::DEVICE_ID_EMULATION);
+ _THREAD_SAFE_UNLOCK_
event_dispatch_function(drag_event);
+ _THREAD_SAFE_LOCK_
}
}
@@ -601,6 +620,7 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
button_event->set_position(st->get_position());
button_event->set_global_position(st->get_position());
button_event->set_pressed(st->is_pressed());
+ button_event->set_canceled(st->is_canceled());
button_event->set_button_index(MouseButton::LEFT);
button_event->set_double_click(st->is_double_tap());
@@ -664,30 +684,39 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em
if (ge.is_valid()) {
if (event_dispatch_function) {
+ _THREAD_SAFE_UNLOCK_
event_dispatch_function(ge);
+ _THREAD_SAFE_LOCK_
}
}
for (const KeyValue<StringName, InputMap::Action> &E : InputMap::get_singleton()->get_action_map()) {
if (InputMap::get_singleton()->event_is_action(p_event, E.key)) {
+ Action &action = action_state[E.key];
// If not echo and action pressed state has changed
if (!p_event->is_echo() && is_action_pressed(E.key, false) != p_event->is_action_pressed(E.key)) {
- Action action;
- action.physics_frame = Engine::get_singleton()->get_physics_frames();
- action.process_frame = Engine::get_singleton()->get_process_frames();
- action.pressed = p_event->is_action_pressed(E.key);
+ if (p_event->is_action_pressed(E.key)) {
+ action.pressed = true;
+ action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action.pressed_process_frame = Engine::get_singleton()->get_process_frames();
+ } else {
+ action.pressed = false;
+ action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action.released_process_frame = Engine::get_singleton()->get_process_frames();
+ }
action.strength = 0.0f;
action.raw_strength = 0.0f;
action.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true);
- action_state[E.key] = action;
}
- action_state[E.key].strength = p_event->get_action_strength(E.key);
- action_state[E.key].raw_strength = p_event->get_action_raw_strength(E.key);
+ action.strength = p_event->get_action_strength(E.key);
+ action.raw_strength = p_event->get_action_raw_strength(E.key);
}
}
if (event_dispatch_function) {
+ _THREAD_SAFE_UNLOCK_
event_dispatch_function(p_event);
+ _THREAD_SAFE_LOCK_
}
}
@@ -795,29 +824,27 @@ Point2i Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, con
}
void Input::action_press(const StringName &p_action, float p_strength) {
- Action action;
+ // Create or retrieve existing action.
+ Action &action = action_state[p_action];
- action.physics_frame = Engine::get_singleton()->get_physics_frames();
- action.process_frame = Engine::get_singleton()->get_process_frames();
+ action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action.pressed_process_frame = Engine::get_singleton()->get_process_frames();
action.pressed = true;
action.strength = p_strength;
action.raw_strength = p_strength;
action.exact = true;
-
- action_state[p_action] = action;
}
void Input::action_release(const StringName &p_action) {
- Action action;
+ // Create or retrieve existing action.
+ Action &action = action_state[p_action];
- action.physics_frame = Engine::get_singleton()->get_physics_frames();
- action.process_frame = Engine::get_singleton()->get_process_frames();
+ action.released_physics_frame = Engine::get_singleton()->get_physics_frames();
+ action.released_process_frame = Engine::get_singleton()->get_process_frames();
action.pressed = false;
- action.strength = 0.f;
- action.raw_strength = 0.f;
+ action.strength = 0.0f;
+ action.raw_strength = 0.0f;
action.exact = true;
-
- action_state[p_action] = action;
}
void Input::set_emulate_touch_from_mouse(bool p_emulate) {
@@ -831,6 +858,7 @@ bool Input::is_emulating_touch_from_mouse() const {
// Calling this whenever the game window is focused helps unsticking the "touch mouse"
// if the OS or its abstraction class hasn't properly reported that touch pointers raised
void Input::ensure_touch_mouse_raised() {
+ _THREAD_SAFE_METHOD_
if (mouse_from_touch_index != -1) {
mouse_from_touch_index = -1;
@@ -937,8 +965,15 @@ void Input::flush_buffered_events() {
_THREAD_SAFE_METHOD_
while (buffered_events.front()) {
- _parse_input_event_impl(buffered_events.front()->get(), false);
+ // The final delivery of the input event involves releasing the lock.
+ // While the lock is released, another thread may lock it and add new events to the back.
+ // Therefore, we get each event and pop it while we still have the lock,
+ // to ensure the list is in a consistent state.
+ List<Ref<InputEvent>>::Element *E = buffered_events.front();
+ Ref<InputEvent> e = E->get();
buffered_events.pop_front();
+
+ _parse_input_event_impl(e, false);
}
}
@@ -1330,8 +1365,9 @@ void Input::parse_mapping(String p_mapping) {
String output = entry[idx].get_slice(":", 0).replace(" ", "");
String input = entry[idx].get_slice(":", 1).replace(" ", "");
- ERR_CONTINUE_MSG(output.length() < 1 || input.length() < 2,
- vformat("Invalid device mapping entry \"%s\" in mapping:\n%s", entry[idx], p_mapping));
+ if (output.length() < 1 || input.length() < 2) {
+ continue;
+ }
if (output == "platform" || output == "hint") {
continue;
@@ -1505,6 +1541,12 @@ Input::Input() {
parse_mapping(entries[i]);
}
}
+
+ legacy_just_pressed_behavior = GLOBAL_DEF("input_devices/compatibility/legacy_just_pressed_behavior", false);
+ if (Engine::get_singleton()->is_editor_hint()) {
+ // Always use standard behavior in the editor.
+ legacy_just_pressed_behavior = false;
+ }
}
Input::~Input() {
diff --git a/core/input/input.h b/core/input/input.h
index c254650ef8..9cc596ee90 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -96,14 +96,17 @@ private:
Vector3 gyroscope;
Vector2 mouse_pos;
int64_t mouse_window = 0;
+ bool legacy_just_pressed_behavior = false;
struct Action {
- uint64_t physics_frame;
- uint64_t process_frame;
- bool pressed;
- bool exact;
- float strength;
- float raw_strength;
+ uint64_t pressed_physics_frame = UINT64_MAX;
+ uint64_t pressed_process_frame = UINT64_MAX;
+ uint64_t released_physics_frame = UINT64_MAX;
+ uint64_t released_process_frame = UINT64_MAX;
+ bool pressed = false;
+ bool exact = true;
+ float strength = 0.0f;
+ float raw_strength = 0.0f;
};
HashMap<StringName, Action> action_state;
diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp
index 9d5d84a508..e547b04d0b 100644
--- a/core/input/input_event.cpp
+++ b/core/input/input_event.cpp
@@ -33,6 +33,7 @@
#include "core/input/input_map.h"
#include "core/input/shortcut.h"
#include "core/os/keyboard.h"
+#include "core/os/os.h"
const int InputEvent::DEVICE_ID_EMULATION = -1;
const int InputEvent::DEVICE_ID_INTERNAL = -2;
@@ -51,15 +52,15 @@ bool InputEvent::is_action(const StringName &p_action, bool p_exact_match) const
}
bool InputEvent::is_action_pressed(const StringName &p_action, bool p_allow_echo, bool p_exact_match) const {
- bool pressed;
- bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed, nullptr, nullptr);
- return valid && pressed && (p_allow_echo || !is_echo());
+ bool pressed_state;
+ bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr);
+ return valid && pressed_state && (p_allow_echo || !is_echo());
}
bool InputEvent::is_action_released(const StringName &p_action, bool p_exact_match) const {
- bool pressed;
- bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed, nullptr, nullptr);
- return valid && !pressed;
+ bool pressed_state;
+ bool valid = InputMap::get_singleton()->event_get_action_status(Ref<InputEvent>(const_cast<InputEvent *>(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr);
+ return valid && !pressed_state;
}
float InputEvent::get_action_strength(const StringName &p_action, bool p_exact_match) const {
@@ -74,8 +75,16 @@ float InputEvent::get_action_raw_strength(const StringName &p_action, bool p_exa
return valid ? raw_strength : 0.0f;
}
+bool InputEvent::is_canceled() const {
+ return canceled;
+}
+
bool InputEvent::is_pressed() const {
- return false;
+ return pressed && !canceled;
+}
+
+bool InputEvent::is_released() const {
+ return !pressed && !canceled;
}
bool InputEvent::is_echo() const {
@@ -107,7 +116,9 @@ void InputEvent::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_action_released", "action", "exact_match"), &InputEvent::is_action_released, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match"), &InputEvent::get_action_strength, DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("is_canceled"), &InputEvent::is_canceled);
ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed);
+ ClassDB::bind_method(D_METHOD("is_released"), &InputEvent::is_released);
ClassDB::bind_method(D_METHOD("is_echo"), &InputEvent::is_echo);
ClassDB::bind_method(D_METHOD("as_text"), &InputEvent::as_text);
@@ -145,13 +156,13 @@ int64_t InputEventFromWindow::get_window_id() const {
void InputEventWithModifiers::set_command_or_control_autoremap(bool p_enabled) {
command_or_control_autoremap = p_enabled;
if (command_or_control_autoremap) {
-#ifdef MACOS_ENABLED
- ctrl_pressed = false;
- meta_pressed = true;
-#else
- ctrl_pressed = true;
- meta_pressed = false;
-#endif
+ if (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) {
+ ctrl_pressed = false;
+ meta_pressed = true;
+ } else {
+ ctrl_pressed = true;
+ meta_pressed = false;
+ }
} else {
ctrl_pressed = false;
meta_pressed = false;
@@ -164,11 +175,11 @@ bool InputEventWithModifiers::is_command_or_control_autoremap() const {
}
bool InputEventWithModifiers::is_command_or_control_pressed() const {
-#ifdef MACOS_ENABLED
- return meta_pressed;
-#else
- return ctrl_pressed;
-#endif
+ if (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) {
+ return meta_pressed;
+ } else {
+ return ctrl_pressed;
+ }
}
void InputEventWithModifiers::set_shift_pressed(bool p_enabled) {
@@ -190,7 +201,7 @@ bool InputEventWithModifiers::is_alt_pressed() const {
}
void InputEventWithModifiers::set_ctrl_pressed(bool p_enabled) {
- ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command/Control autoremaping is enabled, cannot set Control directly!");
+ ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command or Control autoremapping is enabled, cannot set Control directly!");
ctrl_pressed = p_enabled;
emit_changed();
}
@@ -200,7 +211,7 @@ bool InputEventWithModifiers::is_ctrl_pressed() const {
}
void InputEventWithModifiers::set_meta_pressed(bool p_enabled) {
- ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command/Control autoremaping is enabled, cannot set Meta directly!");
+ ERR_FAIL_COND_MSG(command_or_control_autoremap, "Command or Control autoremapping is enabled, cannot set Meta directly!");
meta_pressed = p_enabled;
emit_changed();
}
@@ -231,11 +242,11 @@ BitField<KeyModifierMask> InputEventWithModifiers::get_modifiers_mask() const {
mask.set_flag(KeyModifierMask::META);
}
if (is_command_or_control_autoremap()) {
-#ifdef MACOS_ENABLED
- mask.set_flag(KeyModifierMask::META);
-#else
- mask.set_flag(KeyModifierMask::CTRL);
-#endif
+ if (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) {
+ mask.set_flag(KeyModifierMask::META);
+ } else {
+ mask.set_flag(KeyModifierMask::CTRL);
+ }
}
return mask;
}
@@ -317,10 +328,6 @@ void InputEventKey::set_pressed(bool p_pressed) {
emit_changed();
}
-bool InputEventKey::is_pressed() const {
- return pressed;
-}
-
void InputEventKey::set_keycode(Key p_keycode) {
keycode = p_keycode;
emit_changed();
@@ -483,7 +490,10 @@ Ref<InputEventKey> InputEventKey::create_reference(Key p_keycode, bool p_physica
ie->set_keycode(p_keycode & KeyModifierMask::CODE_MASK);
}
- ie->set_unicode(char32_t(p_keycode & KeyModifierMask::CODE_MASK));
+ char32_t ch = char32_t(p_keycode & KeyModifierMask::CODE_MASK);
+ if (ch < 0xd800 || (ch > 0xdfff && ch <= 0x10ffff)) {
+ ie->set_unicode(ch);
+ }
if ((p_keycode & KeyModifierMask::SHIFT) != Key::NONE) {
ie->set_shift_pressed(true);
@@ -667,8 +677,8 @@ void InputEventMouseButton::set_pressed(bool p_pressed) {
pressed = p_pressed;
}
-bool InputEventMouseButton::is_pressed() const {
- return pressed;
+void InputEventMouseButton::set_canceled(bool p_canceled) {
+ canceled = p_canceled;
}
void InputEventMouseButton::set_double_click(bool p_double_click) {
@@ -695,6 +705,7 @@ Ref<InputEvent> InputEventMouseButton::xformed_by(const Transform2D &p_xform, co
mb->set_button_mask(get_button_mask());
mb->set_pressed(pressed);
+ mb->set_canceled(canceled);
mb->set_double_click(double_click);
mb->set_factor(factor);
mb->set_button_index(button_index);
@@ -790,6 +801,7 @@ String InputEventMouseButton::as_text() const {
String InputEventMouseButton::to_string() {
String p = is_pressed() ? "true" : "false";
+ String canceled_state = is_canceled() ? "true" : "false";
String d = double_click ? "true" : "false";
MouseButton idx = get_button_index();
@@ -816,7 +828,7 @@ String InputEventMouseButton::to_string() {
// Work around the fact vformat can only take 5 substitutions but 6 need to be passed.
String index_and_mods = vformat("button_index=%s, mods=%s", button_index, mods);
- return vformat("InputEventMouseButton: %s, pressed=%s, position=(%s), button_mask=%d, double_click=%s", index_and_mods, p, String(get_position()), get_button_mask(), d);
+ return vformat("InputEventMouseButton: %s, pressed=%s, canceled=%s, position=(%s), button_mask=%d, double_click=%s", index_and_mods, p, canceled_state, String(get_position()), get_button_mask(), d);
}
void InputEventMouseButton::_bind_methods() {
@@ -827,13 +839,14 @@ void InputEventMouseButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_button_index"), &InputEventMouseButton::get_button_index);
ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventMouseButton::set_pressed);
- // ClassDB::bind_method(D_METHOD("is_pressed"), &InputEventMouseButton::is_pressed);
+ ClassDB::bind_method(D_METHOD("set_canceled", "canceled"), &InputEventMouseButton::set_canceled);
ClassDB::bind_method(D_METHOD("set_double_click", "double_click"), &InputEventMouseButton::set_double_click);
ClassDB::bind_method(D_METHOD("is_double_click"), &InputEventMouseButton::is_double_click);
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "factor"), "set_factor", "get_factor");
ADD_PROPERTY(PropertyInfo(Variant::INT, "button_index"), "set_button_index", "get_button_index");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "canceled"), "set_canceled", "is_canceled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "double_click"), "set_double_click", "is_double_click");
}
@@ -941,6 +954,10 @@ bool InputEventMouseMotion::accumulate(const Ref<InputEvent> &p_event) {
return false;
}
+ if (is_canceled() != motion->is_canceled()) {
+ return false;
+ }
+
if (is_pressed() != motion->is_pressed()) {
return false;
}
@@ -1011,6 +1028,7 @@ JoyAxis InputEventJoypadMotion::get_axis() const {
void InputEventJoypadMotion::set_axis_value(float p_value) {
axis_value = p_value;
+ pressed = Math::abs(axis_value) >= 0.5f;
emit_changed();
}
@@ -1018,10 +1036,6 @@ float InputEventJoypadMotion::get_axis_value() const {
return axis_value;
}
-bool InputEventJoypadMotion::is_pressed() const {
- return Math::abs(axis_value) >= 0.5f;
-}
-
bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const {
Ref<InputEventJoypadMotion> jm = p_event;
if (jm.is_null()) {
@@ -1036,12 +1050,12 @@ bool InputEventJoypadMotion::action_match(const Ref<InputEvent> &p_event, bool p
if (match) {
float jm_abs_axis_value = Math::abs(jm->get_axis_value());
bool same_direction = (((axis_value < 0) == (jm->axis_value < 0)) || jm->axis_value == 0);
- bool pressed = same_direction && jm_abs_axis_value >= p_deadzone;
+ bool pressed_state = same_direction && jm_abs_axis_value >= p_deadzone;
if (r_pressed != nullptr) {
- *r_pressed = pressed;
+ *r_pressed = pressed_state;
}
if (r_strength != nullptr) {
- if (pressed) {
+ if (pressed_state) {
if (p_deadzone == 1.0f) {
*r_strength = 1.0f;
} else {
@@ -1095,6 +1109,15 @@ String InputEventJoypadMotion::to_string() {
return vformat("InputEventJoypadMotion: axis=%d, axis_value=%.2f", axis, axis_value);
}
+Ref<InputEventJoypadMotion> InputEventJoypadMotion::create_reference(JoyAxis p_axis, float p_value) {
+ Ref<InputEventJoypadMotion> ie;
+ ie.instantiate();
+ ie->set_axis(p_axis);
+ ie->set_axis_value(p_value);
+
+ return ie;
+}
+
void InputEventJoypadMotion::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_axis", "axis"), &InputEventJoypadMotion::set_axis);
ClassDB::bind_method(D_METHOD("get_axis"), &InputEventJoypadMotion::get_axis);
@@ -1121,10 +1144,6 @@ void InputEventJoypadButton::set_pressed(bool p_pressed) {
pressed = p_pressed;
}
-bool InputEventJoypadButton::is_pressed() const {
- return pressed;
-}
-
void InputEventJoypadButton::set_pressure(float p_pressure) {
pressure = p_pressure;
}
@@ -1205,7 +1224,7 @@ String InputEventJoypadButton::as_text() const {
}
String InputEventJoypadButton::to_string() {
- String p = pressed ? "true" : "false";
+ String p = is_pressed() ? "true" : "false";
return vformat("InputEventJoypadButton: button_index=%d, pressed=%s, pressure=%.2f", button_index, p, pressure);
}
@@ -1225,7 +1244,6 @@ void InputEventJoypadButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_pressure"), &InputEventJoypadButton::get_pressure);
ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventJoypadButton::set_pressed);
- // ClassDB::bind_method(D_METHOD("is_pressed"), &InputEventJoypadButton::is_pressed);
ADD_PROPERTY(PropertyInfo(Variant::INT, "button_index"), "set_button_index", "get_button_index");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pressure"), "set_pressure", "get_pressure");
@@ -1254,8 +1272,8 @@ void InputEventScreenTouch::set_pressed(bool p_pressed) {
pressed = p_pressed;
}
-bool InputEventScreenTouch::is_pressed() const {
- return pressed;
+void InputEventScreenTouch::set_canceled(bool p_canceled) {
+ canceled = p_canceled;
}
void InputEventScreenTouch::set_double_tap(bool p_double_tap) {
@@ -1273,21 +1291,23 @@ Ref<InputEvent> InputEventScreenTouch::xformed_by(const Transform2D &p_xform, co
st->set_index(index);
st->set_position(p_xform.xform(pos + p_local_ofs));
st->set_pressed(pressed);
+ st->set_canceled(canceled);
st->set_double_tap(double_tap);
return st;
}
String InputEventScreenTouch::as_text() const {
- String status = pressed ? RTR("touched") : RTR("released");
+ String status = canceled ? RTR("canceled") : (pressed ? RTR("touched") : RTR("released"));
return vformat(RTR("Screen %s at (%s) with %s touch points"), status, String(get_position()), itos(index));
}
String InputEventScreenTouch::to_string() {
String p = pressed ? "true" : "false";
+ String canceled_state = canceled ? "true" : "false";
String double_tap_string = double_tap ? "true" : "false";
- return vformat("InputEventScreenTouch: index=%d, pressed=%s, position=(%s), double_tap=%s", index, p, String(get_position()), double_tap_string);
+ return vformat("InputEventScreenTouch: index=%d, pressed=%s, canceled=%s, position=(%s), double_tap=%s", index, p, canceled_state, String(get_position()), double_tap_string);
}
void InputEventScreenTouch::_bind_methods() {
@@ -1298,13 +1318,14 @@ void InputEventScreenTouch::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_position"), &InputEventScreenTouch::get_position);
ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventScreenTouch::set_pressed);
- //ClassDB::bind_method(D_METHOD("is_pressed"),&InputEventScreenTouch::is_pressed);
+ ClassDB::bind_method(D_METHOD("set_canceled", "canceled"), &InputEventScreenTouch::set_canceled);
ClassDB::bind_method(D_METHOD("set_double_tap", "double_tap"), &InputEventScreenTouch::set_double_tap);
ClassDB::bind_method(D_METHOD("is_double_tap"), &InputEventScreenTouch::is_double_tap);
ADD_PROPERTY(PropertyInfo(Variant::INT, "index"), "set_index", "get_index");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "suffix:px"), "set_position", "get_position");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "canceled"), "set_canceled", "is_canceled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "double_tap"), "set_double_tap", "is_double_tap");
}
@@ -1456,10 +1477,6 @@ void InputEventAction::set_pressed(bool p_pressed) {
pressed = p_pressed;
}
-bool InputEventAction::is_pressed() const {
- return pressed;
-}
-
void InputEventAction::set_strength(float p_strength) {
strength = CLAMP(p_strength, 0.0f, 1.0f);
}
@@ -1488,7 +1505,7 @@ bool InputEventAction::action_match(const Ref<InputEvent> &p_event, bool p_exact
bool match = action == act->action;
if (match) {
- bool act_pressed = act->pressed;
+ bool act_pressed = act->is_pressed();
if (r_pressed != nullptr) {
*r_pressed = act_pressed;
}
@@ -1519,7 +1536,7 @@ String InputEventAction::as_text() const {
}
String InputEventAction::to_string() {
- String p = pressed ? "true" : "false";
+ String p = is_pressed() ? "true" : "false";
return vformat("InputEventAction: action=\"%s\", pressed=%s", action, p);
}
@@ -1528,13 +1545,10 @@ void InputEventAction::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_action"), &InputEventAction::get_action);
ClassDB::bind_method(D_METHOD("set_pressed", "pressed"), &InputEventAction::set_pressed);
- //ClassDB::bind_method(D_METHOD("is_pressed"), &InputEventAction::is_pressed);
ClassDB::bind_method(D_METHOD("set_strength", "strength"), &InputEventAction::set_strength);
ClassDB::bind_method(D_METHOD("get_strength"), &InputEventAction::get_strength);
- // ClassDB::bind_method(D_METHOD("is_action", "name"), &InputEventAction::is_action);
-
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "action"), "set_action", "get_action");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "pressed"), "set_pressed", "is_pressed");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_strength", "get_strength");
@@ -1757,10 +1771,6 @@ void InputEventShortcut::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut", PROPERTY_HINT_RESOURCE_TYPE, "Shortcut"), "set_shortcut", "get_shortcut");
}
-bool InputEventShortcut::is_pressed() const {
- return true;
-}
-
String InputEventShortcut::as_text() const {
ERR_FAIL_COND_V(shortcut.is_null(), "None");
diff --git a/core/input/input_event.h b/core/input/input_event.h
index 4be42d0bd2..e9d4fb8325 100644
--- a/core/input/input_event.h
+++ b/core/input/input_event.h
@@ -56,6 +56,9 @@ class InputEvent : public Resource {
int device = 0;
protected:
+ bool canceled = false;
+ bool pressed = false;
+
static void _bind_methods();
public:
@@ -71,8 +74,9 @@ public:
float get_action_strength(const StringName &p_action, bool p_exact_match = false) const;
float get_action_raw_strength(const StringName &p_action, bool p_exact_match = false) const;
- // To be removed someday, since they do not make sense for all events
- virtual bool is_pressed() const;
+ bool is_canceled() const;
+ bool is_pressed() const;
+ bool is_released() const;
virtual bool is_echo() const;
virtual String as_text() const = 0;
@@ -149,8 +153,6 @@ public:
class InputEventKey : public InputEventWithModifiers {
GDCLASS(InputEventKey, InputEventWithModifiers);
- bool pressed = false; /// otherwise release
-
Key keycode = Key::NONE; // Key enum, without modifier masks.
Key physical_keycode = Key::NONE;
Key key_label = Key::NONE;
@@ -163,7 +165,6 @@ protected:
public:
void set_pressed(bool p_pressed);
- virtual bool is_pressed() const override;
void set_keycode(Key p_keycode);
Key get_keycode() const;
@@ -229,7 +230,6 @@ class InputEventMouseButton : public InputEventMouse {
float factor = 1;
MouseButton button_index = MouseButton::NONE;
- bool pressed = false; //otherwise released
bool double_click = false; //last even less than double click time
protected:
@@ -243,7 +243,7 @@ public:
MouseButton get_button_index() const;
void set_pressed(bool p_pressed);
- virtual bool is_pressed() const override;
+ void set_canceled(bool p_canceled);
void set_double_click(bool p_double_click);
bool is_double_click() const;
@@ -312,8 +312,6 @@ public:
void set_axis_value(float p_value);
float get_axis_value() const;
- virtual bool is_pressed() const override;
-
virtual bool action_match(const Ref<InputEvent> &p_event, bool p_exact_match, float p_deadzone, bool *r_pressed, float *r_strength, float *r_raw_strength) const override;
virtual bool is_match(const Ref<InputEvent> &p_event, bool p_exact_match = true) const override;
@@ -321,6 +319,8 @@ public:
virtual String as_text() const override;
virtual String to_string() override;
+ static Ref<InputEventJoypadMotion> create_reference(JoyAxis p_axis, float p_value);
+
InputEventJoypadMotion() {}
};
@@ -328,7 +328,6 @@ class InputEventJoypadButton : public InputEvent {
GDCLASS(InputEventJoypadButton, InputEvent);
JoyButton button_index = (JoyButton)0;
- bool pressed = false;
float pressure = 0; //0 to 1
protected:
static void _bind_methods();
@@ -338,7 +337,6 @@ public:
JoyButton get_button_index() const;
void set_pressed(bool p_pressed);
- virtual bool is_pressed() const override;
void set_pressure(float p_pressure);
float get_pressure() const;
@@ -360,7 +358,6 @@ class InputEventScreenTouch : public InputEventFromWindow {
GDCLASS(InputEventScreenTouch, InputEventFromWindow);
int index = 0;
Vector2 pos;
- bool pressed = false;
bool double_tap = false;
protected:
@@ -374,7 +371,7 @@ public:
Vector2 get_position() const;
void set_pressed(bool p_pressed);
- virtual bool is_pressed() const override;
+ void set_canceled(bool p_canceled);
void set_double_tap(bool p_double_tap);
bool is_double_tap() const;
@@ -434,7 +431,6 @@ class InputEventAction : public InputEvent {
GDCLASS(InputEventAction, InputEvent);
StringName action;
- bool pressed = false;
float strength = 1.0f;
protected:
@@ -445,7 +441,6 @@ public:
StringName get_action() const;
void set_pressed(bool p_pressed);
- virtual bool is_pressed() const override;
void set_strength(float p_strength);
float get_strength() const;
@@ -569,7 +564,6 @@ protected:
public:
void set_shortcut(Ref<Shortcut> p_shortcut);
Ref<Shortcut> get_shortcut();
- virtual bool is_pressed() const override;
virtual String as_text() const override;
virtual String to_string() override;
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 910778324c..ddfde0e7cd 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -399,21 +399,25 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins() {
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::LEFT));
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_LEFT));
+ inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, -1.0));
default_builtin_cache.insert("ui_left", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::RIGHT));
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_RIGHT));
+ inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, 1.0));
default_builtin_cache.insert("ui_right", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::UP));
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_UP));
+ inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, -1.0));
default_builtin_cache.insert("ui_up", inputs);
inputs = List<Ref<InputEvent>>();
inputs.push_back(InputEventKey::create_reference(Key::DOWN));
inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_DOWN));
+ inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, 1.0));
default_builtin_cache.insert("ui_down", inputs);
inputs = List<Ref<InputEvent>>();
diff --git a/core/io/compression.cpp b/core/io/compression.cpp
index a6114e4f63..ac4a637597 100644
--- a/core/io/compression.cpp
+++ b/core/io/compression.cpp
@@ -35,11 +35,18 @@
#include "thirdparty/misc/fastlz.h"
+#ifdef BROTLI_ENABLED
+#include "thirdparty/brotli/include/brotli/decode.h"
+#endif
+
#include <zlib.h>
#include <zstd.h>
int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode) {
switch (p_mode) {
+ case MODE_BROTLI: {
+ ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
+ } break;
case MODE_FASTLZ: {
if (p_src_size < 16) {
uint8_t src[16];
@@ -95,6 +102,9 @@ int Compression::compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size,
int Compression::get_max_compressed_buffer_size(int p_src_size, Mode p_mode) {
switch (p_mode) {
+ case MODE_BROTLI: {
+ ERR_FAIL_V_MSG(-1, "Only brotli decompression is supported.");
+ } break;
case MODE_FASTLZ: {
int ss = p_src_size + p_src_size * 6 / 100;
if (ss < 66) {
@@ -129,6 +139,16 @@ int Compression::get_max_compressed_buffer_size(int p_src_size, Mode p_mode) {
int Compression::decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p_src, int p_src_size, Mode p_mode) {
switch (p_mode) {
+ case MODE_BROTLI: {
+#ifdef BROTLI_ENABLED
+ size_t ret_size = p_dst_max_size;
+ BrotliDecoderResult res = BrotliDecoderDecompress(p_src_size, p_src, &ret_size, p_dst);
+ ERR_FAIL_COND_V(res != BROTLI_DECODER_RESULT_SUCCESS, -1);
+ return ret_size;
+#else
+ ERR_FAIL_V_MSG(-1, "Godot was compiled without brotli support.");
+#endif
+ } break;
case MODE_FASTLZ: {
int ret_size = 0;
@@ -186,87 +206,147 @@ int Compression::decompress(uint8_t *p_dst, int p_dst_max_size, const uint8_t *p
This is much slower however than using Compression::decompress because it may result in multiple full copies of the output buffer.
*/
int Compression::decompress_dynamic(Vector<uint8_t> *p_dst_vect, int p_max_dst_size, const uint8_t *p_src, int p_src_size, Mode p_mode) {
- int ret;
uint8_t *dst = nullptr;
int out_mark = 0;
- z_stream strm;
ERR_FAIL_COND_V(p_src_size <= 0, Z_DATA_ERROR);
- // This function only supports GZip and Deflate
- int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
- ERR_FAIL_COND_V(p_mode != MODE_DEFLATE && p_mode != MODE_GZIP, Z_ERRNO);
-
- // Initialize the stream
- strm.zalloc = Z_NULL;
- strm.zfree = Z_NULL;
- strm.opaque = Z_NULL;
- strm.avail_in = 0;
- strm.next_in = Z_NULL;
-
- int err = inflateInit2(&strm, window_bits);
- ERR_FAIL_COND_V(err != Z_OK, -1);
-
- // Setup the stream inputs
- strm.next_in = (Bytef *)p_src;
- strm.avail_in = p_src_size;
-
- // Ensure the destination buffer is empty
- p_dst_vect->clear();
-
- // decompress until deflate stream ends or end of file
- do {
- // Add another chunk size to the output buffer
- // This forces a copy of the whole buffer
- p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
- // Get pointer to the actual output buffer
- dst = p_dst_vect->ptrw();
-
- // Set the stream to the new output stream
- // Since it was copied, we need to reset the stream to the new buffer
- strm.next_out = &(dst[out_mark]);
- strm.avail_out = gzip_chunk;
-
- // run inflate() on input until output buffer is full and needs to be resized
- // or input runs out
+ if (p_mode == MODE_BROTLI) {
+#ifdef BROTLI_ENABLED
+ BrotliDecoderResult ret;
+ BrotliDecoderState *state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
+ ERR_FAIL_COND_V(state == nullptr, Z_DATA_ERROR);
+
+ // Setup the stream inputs.
+ const uint8_t *next_in = p_src;
+ size_t avail_in = p_src_size;
+ uint8_t *next_out = nullptr;
+ size_t avail_out = 0;
+ size_t total_out = 0;
+
+ // Ensure the destination buffer is empty.
+ p_dst_vect->clear();
+
+ // Decompress until stream ends or end of file.
do {
- ret = inflate(&strm, Z_SYNC_FLUSH);
-
- switch (ret) {
- case Z_NEED_DICT:
- ret = Z_DATA_ERROR;
- [[fallthrough]];
- case Z_DATA_ERROR:
- case Z_MEM_ERROR:
- case Z_STREAM_ERROR:
- case Z_BUF_ERROR:
- if (strm.msg) {
- WARN_PRINT(strm.msg);
- }
- (void)inflateEnd(&strm);
- p_dst_vect->clear();
- return ret;
+ // Add another chunk size to the output buffer.
+ // This forces a copy of the whole buffer.
+ p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
+ // Get pointer to the actual output buffer.
+ dst = p_dst_vect->ptrw();
+
+ // Set the stream to the new output stream.
+ // Since it was copied, we need to reset the stream to the new buffer.
+ next_out = &(dst[out_mark]);
+ avail_out += gzip_chunk;
+
+ ret = BrotliDecoderDecompressStream(state, &avail_in, &next_in, &avail_out, &next_out, &total_out);
+ if (ret == BROTLI_DECODER_RESULT_ERROR) {
+ WARN_PRINT(BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state)));
+ BrotliDecoderDestroyInstance(state);
+ p_dst_vect->clear();
+ return Z_DATA_ERROR;
}
- } while (strm.avail_out > 0 && strm.avail_in > 0);
- out_mark += gzip_chunk;
+ out_mark += gzip_chunk - avail_out;
- // Enforce max output size
- if (p_max_dst_size > -1 && strm.total_out > (uint64_t)p_max_dst_size) {
- (void)inflateEnd(&strm);
- p_dst_vect->clear();
- return Z_BUF_ERROR;
+ // Enforce max output size.
+ if (p_max_dst_size > -1 && total_out > (uint64_t)p_max_dst_size) {
+ BrotliDecoderDestroyInstance(state);
+ p_dst_vect->clear();
+ return Z_BUF_ERROR;
+ }
+ } while (ret != BROTLI_DECODER_RESULT_SUCCESS);
+
+ // If all done successfully, resize the output if it's larger than the actual output.
+ if ((unsigned long)p_dst_vect->size() > total_out) {
+ p_dst_vect->resize(total_out);
}
- } while (ret != Z_STREAM_END);
- // If all done successfully, resize the output if it's larger than the actual output
- if ((unsigned long)p_dst_vect->size() > strm.total_out) {
- p_dst_vect->resize(strm.total_out);
- }
+ // Clean up and return.
+ BrotliDecoderDestroyInstance(state);
+ return Z_OK;
+#else
+ ERR_FAIL_V_MSG(Z_ERRNO, "Godot was compiled without brotli support.");
+#endif
+ } else {
+ // This function only supports GZip and Deflate.
+ ERR_FAIL_COND_V(p_mode != MODE_DEFLATE && p_mode != MODE_GZIP, Z_ERRNO);
+
+ int ret;
+ z_stream strm;
+ int window_bits = p_mode == MODE_DEFLATE ? 15 : 15 + 16;
+
+ // Initialize the stream.
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = 0;
+ strm.next_in = Z_NULL;
+
+ int err = inflateInit2(&strm, window_bits);
+ ERR_FAIL_COND_V(err != Z_OK, -1);
+
+ // Setup the stream inputs.
+ strm.next_in = (Bytef *)p_src;
+ strm.avail_in = p_src_size;
+
+ // Ensure the destination buffer is empty.
+ p_dst_vect->clear();
+
+ // Decompress until deflate stream ends or end of file.
+ do {
+ // Add another chunk size to the output buffer.
+ // This forces a copy of the whole buffer.
+ p_dst_vect->resize(p_dst_vect->size() + gzip_chunk);
+ // Get pointer to the actual output buffer.
+ dst = p_dst_vect->ptrw();
+
+ // Set the stream to the new output stream.
+ // Since it was copied, we need to reset the stream to the new buffer.
+ strm.next_out = &(dst[out_mark]);
+ strm.avail_out = gzip_chunk;
+
+ // Run inflate() on input until output buffer is full and needs to be resized or input runs out.
+ do {
+ ret = inflate(&strm, Z_SYNC_FLUSH);
+
+ switch (ret) {
+ case Z_NEED_DICT:
+ ret = Z_DATA_ERROR;
+ [[fallthrough]];
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ case Z_STREAM_ERROR:
+ case Z_BUF_ERROR:
+ if (strm.msg) {
+ WARN_PRINT(strm.msg);
+ }
+ (void)inflateEnd(&strm);
+ p_dst_vect->clear();
+ return ret;
+ }
+ } while (strm.avail_out > 0 && strm.avail_in > 0);
+
+ out_mark += gzip_chunk;
+
+ // Enforce max output size.
+ if (p_max_dst_size > -1 && strm.total_out > (uint64_t)p_max_dst_size) {
+ (void)inflateEnd(&strm);
+ p_dst_vect->clear();
+ return Z_BUF_ERROR;
+ }
+ } while (ret != Z_STREAM_END);
+
+ // If all done successfully, resize the output if it's larger than the actual output.
+ if ((unsigned long)p_dst_vect->size() > strm.total_out) {
+ p_dst_vect->resize(strm.total_out);
+ }
- // clean up and return
- (void)inflateEnd(&strm);
- return Z_OK;
+ // Clean up and return.
+ (void)inflateEnd(&strm);
+ return Z_OK;
+ }
}
int Compression::zlib_level = Z_DEFAULT_COMPRESSION;
diff --git a/core/io/compression.h b/core/io/compression.h
index 063da6dc7d..a5a2d657da 100644
--- a/core/io/compression.h
+++ b/core/io/compression.h
@@ -47,7 +47,8 @@ public:
MODE_FASTLZ,
MODE_DEFLATE,
MODE_ZSTD,
- MODE_GZIP
+ MODE_GZIP,
+ MODE_BROTLI
};
static int compress(uint8_t *p_dst, const uint8_t *p_src, int p_src_size, Mode p_mode = MODE_ZSTD);
diff --git a/core/io/dir_access.h b/core/io/dir_access.h
index 51eb68eaea..52ed688deb 100644
--- a/core/io/dir_access.h
+++ b/core/io/dir_access.h
@@ -68,7 +68,7 @@ protected:
virtual String _get_root_string() const;
AccessType get_access_type() const;
- String fix_path(String p_path) const;
+ virtual String fix_path(String p_path) const;
template <class T>
static Ref<DirAccess> _create_builtin() {
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index 3d10151327..b669afdc99 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -441,6 +441,11 @@ Vector<String> FileAccess::get_csv_line(const String &p_delim) const {
current += c;
}
}
+
+ if (in_quote) {
+ WARN_PRINT(vformat("Reached end of file before closing '\"' in CSV file '%s'.", get_path()));
+ }
+
strings.push_back(current);
return strings;
@@ -871,4 +876,5 @@ void FileAccess::_bind_methods() {
BIND_ENUM_CONSTANT(COMPRESSION_DEFLATE);
BIND_ENUM_CONSTANT(COMPRESSION_ZSTD);
BIND_ENUM_CONSTANT(COMPRESSION_GZIP);
+ BIND_ENUM_CONSTANT(COMPRESSION_BROTLI);
}
diff --git a/core/io/file_access.h b/core/io/file_access.h
index 47770cad87..ad1ac665f3 100644
--- a/core/io/file_access.h
+++ b/core/io/file_access.h
@@ -64,7 +64,8 @@ public:
COMPRESSION_FASTLZ = Compression::MODE_FASTLZ,
COMPRESSION_DEFLATE = Compression::MODE_DEFLATE,
COMPRESSION_ZSTD = Compression::MODE_ZSTD,
- COMPRESSION_GZIP = Compression::MODE_GZIP
+ COMPRESSION_GZIP = Compression::MODE_GZIP,
+ COMPRESSION_BROTLI = Compression::MODE_BROTLI,
};
typedef void (*FileCloseFailNotify)(const String &);
@@ -80,7 +81,7 @@ protected:
static void _bind_methods();
AccessType get_access_type() const;
- String fix_path(const String &p_path) const;
+ virtual String fix_path(const String &p_path) const;
virtual Error open_internal(const String &p_path, int p_mode_flags) = 0; ///< open a file
virtual uint64_t _get_modified_time(const String &p_file) = 0;
virtual void _set_access_type(AccessType p_access);
diff --git a/core/io/file_access_compressed.cpp b/core/io/file_access_compressed.cpp
index da59ae8c59..3e5a1217dd 100644
--- a/core/io/file_access_compressed.cpp
+++ b/core/io/file_access_compressed.cpp
@@ -34,13 +34,7 @@
void FileAccessCompressed::configure(const String &p_magic, Compression::Mode p_mode, uint32_t p_block_size) {
magic = p_magic.ascii().get_data();
- if (magic.length() > 4) {
- magic = magic.substr(0, 4);
- } else {
- while (magic.length() < 4) {
- magic += " ";
- }
- }
+ magic = (magic + " ").substr(0, 4);
cmode = p_mode;
block_size = p_block_size;
diff --git a/core/io/file_access_memory.cpp b/core/io/file_access_memory.cpp
index 1052170f3c..14ec0be092 100644
--- a/core/io/file_access_memory.cpp
+++ b/core/io/file_access_memory.cpp
@@ -144,7 +144,7 @@ uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
}
memcpy(p_dst, &data[pos], read);
- pos += p_length;
+ pos += read;
return read;
}
@@ -172,5 +172,5 @@ void FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) {
}
memcpy(&data[pos], p_src, write);
- pos += p_length;
+ pos += write;
}
diff --git a/core/io/file_access_network.cpp b/core/io/file_access_network.cpp
deleted file mode 100644
index 7fabff26ac..0000000000
--- a/core/io/file_access_network.cpp
+++ /dev/null
@@ -1,498 +0,0 @@
-/**************************************************************************/
-/* file_access_network.cpp */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#include "file_access_network.h"
-
-#include "core/config/project_settings.h"
-#include "core/io/ip.h"
-#include "core/io/marshalls.h"
-#include "core/os/os.h"
-
-//#define DEBUG_PRINT(m_p) print_line(m_p)
-//#define DEBUG_TIME(m_what) printf("MS: %s - %lli\n",m_what,OS::get_singleton()->get_ticks_usec());
-#define DEBUG_PRINT(m_p)
-#define DEBUG_TIME(m_what)
-
-void FileAccessNetworkClient::lock_mutex() {
- mutex.lock();
- lockcount++;
-}
-
-void FileAccessNetworkClient::unlock_mutex() {
- lockcount--;
- mutex.unlock();
-}
-
-void FileAccessNetworkClient::put_32(int p_32) {
- uint8_t buf[4];
- encode_uint32(p_32, buf);
- client->put_data(buf, 4);
- DEBUG_PRINT("put32: " + itos(p_32));
-}
-
-void FileAccessNetworkClient::put_64(int64_t p_64) {
- uint8_t buf[8];
- encode_uint64(p_64, buf);
- client->put_data(buf, 8);
- DEBUG_PRINT("put64: " + itos(p_64));
-}
-
-int FileAccessNetworkClient::get_32() {
- uint8_t buf[4];
- client->get_data(buf, 4);
- return decode_uint32(buf);
-}
-
-int64_t FileAccessNetworkClient::get_64() {
- uint8_t buf[8];
- client->get_data(buf, 8);
- return decode_uint64(buf);
-}
-
-void FileAccessNetworkClient::_thread_func() {
- client->set_no_delay(true);
- while (!quit) {
- DEBUG_PRINT("SEM WAIT - " + itos(sem->get()));
- sem.wait();
- DEBUG_TIME("sem_unlock");
- //DEBUG_PRINT("semwait returned "+itos(werr));
- DEBUG_PRINT("MUTEX LOCK " + itos(lockcount));
- lock_mutex();
- DEBUG_PRINT("MUTEX PASS");
-
- {
- MutexLock lock(blockrequest_mutex);
- while (block_requests.size()) {
- put_32(block_requests.front()->get().id);
- put_32(FileAccessNetwork::COMMAND_READ_BLOCK);
- put_64(block_requests.front()->get().offset);
- put_32(block_requests.front()->get().size);
- block_requests.pop_front();
- }
- }
-
- DEBUG_PRINT("THREAD ITER");
-
- DEBUG_TIME("sem_read");
- int id = get_32();
-
- int response = get_32();
- DEBUG_PRINT("GET RESPONSE: " + itos(response));
-
- FileAccessNetwork *fa = nullptr;
-
- if (response != FileAccessNetwork::RESPONSE_DATA) {
- if (!accesses.has(id)) {
- unlock_mutex();
- ERR_FAIL_COND(!accesses.has(id));
- }
- }
-
- if (accesses.has(id)) {
- fa = accesses[id];
- }
-
- switch (response) {
- case FileAccessNetwork::RESPONSE_OPEN: {
- DEBUG_TIME("sem_open");
- int status = get_32();
- if (status != OK) {
- fa->_respond(0, Error(status));
- } else {
- int64_t len = get_64();
- fa->_respond(len, Error(status));
- }
-
- fa->sem.post();
-
- } break;
- case FileAccessNetwork::RESPONSE_DATA: {
- int64_t offset = get_64();
- int32_t len = get_32();
-
- Vector<uint8_t> resp_block;
- resp_block.resize(len);
- client->get_data(resp_block.ptrw(), len);
-
- if (fa) { //may have been queued
- fa->_set_block(offset, resp_block);
- }
-
- } break;
- case FileAccessNetwork::RESPONSE_FILE_EXISTS: {
- int status = get_32();
- fa->exists_modtime = status != 0;
- fa->sem.post();
-
- } break;
- case FileAccessNetwork::RESPONSE_GET_MODTIME: {
- uint64_t status = get_64();
- fa->exists_modtime = status;
- fa->sem.post();
-
- } break;
- }
-
- unlock_mutex();
- }
-}
-
-void FileAccessNetworkClient::_thread_func(void *s) {
- FileAccessNetworkClient *self = static_cast<FileAccessNetworkClient *>(s);
-
- self->_thread_func();
-}
-
-Error FileAccessNetworkClient::connect(const String &p_host, int p_port, const String &p_password) {
- IPAddress ip;
-
- if (p_host.is_valid_ip_address()) {
- ip = p_host;
- } else {
- ip = IP::get_singleton()->resolve_hostname(p_host);
- }
-
- DEBUG_PRINT("IP: " + String(ip) + " port " + itos(p_port));
- Error err = client->connect_to_host(ip, p_port);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot connect to host with IP: " + String(ip) + " and port: " + itos(p_port));
- while (client->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
- //DEBUG_PRINT("trying to connect....");
- OS::get_singleton()->delay_usec(1000);
- }
-
- if (client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
- return ERR_CANT_CONNECT;
- }
-
- CharString cs = p_password.utf8();
- put_32(cs.length());
- client->put_data((const uint8_t *)cs.ptr(), cs.length());
-
- int e = get_32();
-
- if (e != OK) {
- return ERR_INVALID_PARAMETER;
- }
-
- thread.start(_thread_func, this);
-
- return OK;
-}
-
-FileAccessNetworkClient *FileAccessNetworkClient::singleton = nullptr;
-
-FileAccessNetworkClient::FileAccessNetworkClient() {
- singleton = this;
- client.instantiate();
-}
-
-FileAccessNetworkClient::~FileAccessNetworkClient() {
- quit = true;
- sem.post();
- thread.wait_to_finish();
-}
-
-void FileAccessNetwork::_set_block(uint64_t p_offset, const Vector<uint8_t> &p_block) {
- int32_t page = p_offset / page_size;
- ERR_FAIL_INDEX(page, pages.size());
- if (page < pages.size() - 1) {
- ERR_FAIL_COND(p_block.size() != page_size);
- } else {
- ERR_FAIL_COND((uint64_t)p_block.size() != total_size % page_size);
- }
-
- {
- MutexLock lock(buffer_mutex);
- pages.write[page].buffer = p_block;
- pages.write[page].queued = false;
- }
-
- if (waiting_on_page == page) {
- waiting_on_page = -1;
- page_sem.post();
- }
-}
-
-void FileAccessNetwork::_respond(uint64_t p_len, Error p_status) {
- DEBUG_PRINT("GOT RESPONSE - len: " + itos(p_len) + " status: " + itos(p_status));
- response = p_status;
- if (response != OK) {
- return;
- }
- opened = true;
- total_size = p_len;
- int32_t pc = ((total_size - 1) / page_size) + 1;
- pages.resize(pc);
-}
-
-Error FileAccessNetwork::open_internal(const String &p_path, int p_mode_flags) {
- ERR_FAIL_COND_V(p_mode_flags != READ, ERR_UNAVAILABLE);
- _close();
-
- FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton;
- DEBUG_PRINT("open: " + p_path);
-
- DEBUG_TIME("open_begin");
-
- nc->lock_mutex();
- nc->put_32(id);
- nc->accesses[id] = this;
- nc->put_32(COMMAND_OPEN_FILE);
- CharString cs = p_path.utf8();
- nc->put_32(cs.length());
- nc->client->put_data((const uint8_t *)cs.ptr(), cs.length());
- pos = 0;
- eof_flag = false;
- last_page = -1;
- last_page_buff = nullptr;
-
- //buffers.clear();
- nc->unlock_mutex();
- DEBUG_PRINT("OPEN POST");
- DEBUG_TIME("open_post");
- nc->sem.post(); //awaiting answer
- DEBUG_PRINT("WAIT...");
- sem.wait();
- DEBUG_TIME("open_end");
- DEBUG_PRINT("WAIT ENDED...");
-
- return response;
-}
-
-void FileAccessNetwork::_close() {
- if (!opened) {
- return;
- }
-
- FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton;
-
- DEBUG_PRINT("CLOSE");
- nc->lock_mutex();
- nc->put_32(id);
- nc->put_32(COMMAND_CLOSE);
- pages.clear();
- opened = false;
- nc->unlock_mutex();
-}
-
-bool FileAccessNetwork::is_open() const {
- return opened;
-}
-
-void FileAccessNetwork::seek(uint64_t p_position) {
- ERR_FAIL_COND_MSG(!opened, "File must be opened before use.");
-
- eof_flag = p_position > total_size;
-
- if (p_position >= total_size) {
- p_position = total_size;
- }
-
- pos = p_position;
-}
-
-void FileAccessNetwork::seek_end(int64_t p_position) {
- seek(total_size + p_position);
-}
-
-uint64_t FileAccessNetwork::get_position() const {
- ERR_FAIL_COND_V_MSG(!opened, 0, "File must be opened before use.");
- return pos;
-}
-
-uint64_t FileAccessNetwork::get_length() const {
- ERR_FAIL_COND_V_MSG(!opened, 0, "File must be opened before use.");
- return total_size;
-}
-
-bool FileAccessNetwork::eof_reached() const {
- ERR_FAIL_COND_V_MSG(!opened, false, "File must be opened before use.");
- return eof_flag;
-}
-
-uint8_t FileAccessNetwork::get_8() const {
- uint8_t v;
- get_buffer(&v, 1);
- return v;
-}
-
-void FileAccessNetwork::_queue_page(int32_t p_page) const {
- if (p_page >= pages.size()) {
- return;
- }
- if (pages[p_page].buffer.is_empty() && !pages[p_page].queued) {
- FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton;
- {
- MutexLock lock(nc->blockrequest_mutex);
-
- FileAccessNetworkClient::BlockRequest br;
- br.id = id;
- br.offset = (uint64_t)p_page * page_size;
- br.size = page_size;
- nc->block_requests.push_back(br);
- pages.write[p_page].queued = true;
- }
- DEBUG_PRINT("QUEUE PAGE POST");
- nc->sem.post();
- DEBUG_PRINT("queued " + itos(p_page));
- }
-}
-
-uint64_t FileAccessNetwork::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
- ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
-
- if (pos + p_length > total_size) {
- eof_flag = true;
- }
- if (pos + p_length >= total_size) {
- p_length = total_size - pos;
- }
-
- uint8_t *buff = last_page_buff;
-
- for (uint64_t i = 0; i < p_length; i++) {
- int32_t page = pos / page_size;
-
- if (page != last_page) {
- buffer_mutex.lock();
- if (pages[page].buffer.is_empty()) {
- waiting_on_page = page;
- for (int32_t j = 0; j < read_ahead; j++) {
- _queue_page(page + j);
- }
- buffer_mutex.unlock();
- DEBUG_PRINT("wait");
- page_sem.wait();
- DEBUG_PRINT("done");
- } else {
- for (int32_t j = 0; j < read_ahead; j++) {
- _queue_page(page + j);
- }
- buffer_mutex.unlock();
- }
-
- buff = pages.write[page].buffer.ptrw();
- last_page_buff = buff;
- last_page = page;
- }
-
- p_dst[i] = buff[pos - uint64_t(page) * page_size];
- pos++;
- }
-
- return p_length;
-}
-
-Error FileAccessNetwork::get_error() const {
- return pos == total_size ? ERR_FILE_EOF : OK;
-}
-
-void FileAccessNetwork::flush() {
- ERR_FAIL();
-}
-
-void FileAccessNetwork::store_8(uint8_t p_dest) {
- ERR_FAIL();
-}
-
-bool FileAccessNetwork::file_exists(const String &p_path) {
- FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton;
- nc->lock_mutex();
- nc->put_32(id);
- nc->put_32(COMMAND_FILE_EXISTS);
- CharString cs = p_path.utf8();
- nc->put_32(cs.length());
- nc->client->put_data((const uint8_t *)cs.ptr(), cs.length());
- nc->unlock_mutex();
- DEBUG_PRINT("FILE EXISTS POST");
- nc->sem.post();
- sem.wait();
-
- return exists_modtime != 0;
-}
-
-uint64_t FileAccessNetwork::_get_modified_time(const String &p_file) {
- FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton;
- nc->lock_mutex();
- nc->put_32(id);
- nc->put_32(COMMAND_GET_MODTIME);
- CharString cs = p_file.utf8();
- nc->put_32(cs.length());
- nc->client->put_data((const uint8_t *)cs.ptr(), cs.length());
- nc->unlock_mutex();
- DEBUG_PRINT("MODTIME POST");
- nc->sem.post();
- sem.wait();
-
- return exists_modtime;
-}
-
-uint32_t FileAccessNetwork::_get_unix_permissions(const String &p_file) {
- ERR_PRINT("Getting UNIX permissions from network drives is not implemented yet");
- return 0;
-}
-
-Error FileAccessNetwork::_set_unix_permissions(const String &p_file, uint32_t p_permissions) {
- ERR_PRINT("Setting UNIX permissions on network drives is not implemented yet");
- return ERR_UNAVAILABLE;
-}
-
-void FileAccessNetwork::configure() {
- GLOBAL_DEF(PropertyInfo(Variant::INT, "network/remote_fs/page_size", PROPERTY_HINT_RANGE, "1,65536,1,or_greater"), 65536); // Is used as denominator and can't be zero
- GLOBAL_DEF(PropertyInfo(Variant::INT, "network/remote_fs/page_read_ahead", PROPERTY_HINT_RANGE, "0,8,1,or_greater"), 4);
-}
-
-void FileAccessNetwork::close() {
- _close();
-
- FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton;
- nc->lock_mutex();
- nc->accesses.erase(id);
- nc->unlock_mutex();
-}
-
-FileAccessNetwork::FileAccessNetwork() {
- FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton;
- nc->lock_mutex();
- id = nc->last_id++;
- nc->accesses[id] = this;
- nc->unlock_mutex();
- page_size = GLOBAL_GET("network/remote_fs/page_size");
- read_ahead = GLOBAL_GET("network/remote_fs/page_read_ahead");
-}
-
-FileAccessNetwork::~FileAccessNetwork() {
- _close();
-
- FileAccessNetworkClient *nc = FileAccessNetworkClient::singleton;
- nc->lock_mutex();
- nc->accesses.erase(id);
- nc->unlock_mutex();
-}
diff --git a/core/io/file_access_network.h b/core/io/file_access_network.h
deleted file mode 100644
index 78c19347ce..0000000000
--- a/core/io/file_access_network.h
+++ /dev/null
@@ -1,167 +0,0 @@
-/**************************************************************************/
-/* file_access_network.h */
-/**************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/**************************************************************************/
-/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
-/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/**************************************************************************/
-
-#ifndef FILE_ACCESS_NETWORK_H
-#define FILE_ACCESS_NETWORK_H
-
-#include "core/io/file_access.h"
-#include "core/io/stream_peer_tcp.h"
-#include "core/os/semaphore.h"
-#include "core/os/thread.h"
-
-class FileAccessNetwork;
-
-class FileAccessNetworkClient {
- struct BlockRequest {
- int32_t id;
- uint64_t offset;
- int32_t size;
- };
-
- List<BlockRequest> block_requests;
-
- Semaphore sem;
- Thread thread;
- bool quit = false;
- Mutex mutex;
- Mutex blockrequest_mutex;
- HashMap<int, FileAccessNetwork *> accesses;
- Ref<StreamPeerTCP> client;
- int32_t last_id = 0;
- int32_t lockcount = 0;
-
- Vector<uint8_t> block;
-
- void _thread_func();
- static void _thread_func(void *s);
-
- void put_32(int32_t p_32);
- void put_64(int64_t p_64);
- int32_t get_32();
- int64_t get_64();
- void lock_mutex();
- void unlock_mutex();
-
- friend class FileAccessNetwork;
- static FileAccessNetworkClient *singleton;
-
-public:
- static FileAccessNetworkClient *get_singleton() { return singleton; }
-
- Error connect(const String &p_host, int p_port, const String &p_password = "");
-
- FileAccessNetworkClient();
- ~FileAccessNetworkClient();
-};
-
-class FileAccessNetwork : public FileAccess {
- Semaphore sem;
- Semaphore page_sem;
- Mutex buffer_mutex;
- bool opened = false;
- uint64_t total_size = 0;
- mutable uint64_t pos = 0;
- int32_t id = -1;
- mutable bool eof_flag = false;
- mutable int32_t last_page = -1;
- mutable uint8_t *last_page_buff = nullptr;
-
- int32_t page_size = 0;
- int32_t read_ahead = 0;
-
- mutable int waiting_on_page = -1;
-
- struct Page {
- int activity = 0;
- bool queued = false;
- Vector<uint8_t> buffer;
- };
-
- mutable Vector<Page> pages;
-
- mutable Error response;
-
- uint64_t exists_modtime = 0;
-
- friend class FileAccessNetworkClient;
- void _queue_page(int32_t p_page) const;
- void _respond(uint64_t p_len, Error p_status);
- void _set_block(uint64_t p_offset, const Vector<uint8_t> &p_block);
- void _close();
-
-public:
- enum Command {
- COMMAND_OPEN_FILE,
- COMMAND_READ_BLOCK,
- COMMAND_CLOSE,
- COMMAND_FILE_EXISTS,
- COMMAND_GET_MODTIME,
- };
-
- enum Response {
- RESPONSE_OPEN,
- RESPONSE_DATA,
- RESPONSE_FILE_EXISTS,
- RESPONSE_GET_MODTIME,
- };
-
- virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
- virtual bool is_open() const override; ///< true when file is open
-
- virtual void seek(uint64_t p_position) override; ///< seek to a given position
- virtual void seek_end(int64_t p_position = 0) override; ///< seek from the end of file
- virtual uint64_t get_position() const override; ///< get position in the file
- virtual uint64_t get_length() const override; ///< get size of the file
-
- virtual bool eof_reached() const override; ///< reading passed EOF
-
- virtual uint8_t get_8() const override; ///< get a byte
- virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override;
-
- virtual Error get_error() const override; ///< get last error
-
- virtual void flush() override;
- virtual void store_8(uint8_t p_dest) override; ///< store a byte
-
- virtual bool file_exists(const String &p_path) override; ///< return true if a file exists
-
- virtual uint64_t _get_modified_time(const String &p_file) override;
- virtual uint32_t _get_unix_permissions(const String &p_file) override;
- virtual Error _set_unix_permissions(const String &p_file, uint32_t p_permissions) override;
-
- virtual void close() override;
-
- static void configure();
-
- FileAccessNetwork();
- ~FileAccessNetwork();
-};
-
-#endif // FILE_ACCESS_NETWORK_H
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 9553f35b19..74c5c1c191 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -48,7 +48,8 @@ Error PackedData::add_pack(const String &p_path, bool p_replace_files, uint64_t
}
void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64_t p_ofs, uint64_t p_size, const uint8_t *p_md5, PackSource *p_src, bool p_replace_files, bool p_encrypted) {
- PathMD5 pmd5(p_path.md5_buffer());
+ String simplified_path = p_path.simplify_path();
+ PathMD5 pmd5(simplified_path.md5_buffer());
bool exists = files.has(pmd5);
@@ -68,7 +69,7 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
if (!exists) {
//search for dir
- String p = p_path.replace_first("res://", "");
+ String p = simplified_path.replace_first("res://", "");
PackedDir *cd = root;
if (p.contains("/")) { //in a subdir
@@ -87,7 +88,7 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
}
}
}
- String filename = p_path.get_file();
+ String filename = simplified_path.get_file();
// Don't add as a file if the path points to a directory
if (!filename.is_empty()) {
cd->files.insert(filename);
@@ -260,7 +261,7 @@ Ref<FileAccess> PackedSourcePCK::get_file(const String &p_path, PackedData::Pack
//////////////////////////////////////////////////////////////////
Error FileAccessPack::open_internal(const String &p_path, int p_mode_flags) {
- ERR_FAIL_V(ERR_UNAVAILABLE);
+ ERR_PRINT("Can't open pack-referenced file.");
return ERR_UNAVAILABLE;
}
diff --git a/core/io/file_access_pack.h b/core/io/file_access_pack.h
index 8bfabc9529..1538b302c2 100644
--- a/core/io/file_access_pack.h
+++ b/core/io/file_access_pack.h
@@ -184,7 +184,8 @@ public:
};
Ref<FileAccess> PackedData::try_open_path(const String &p_path) {
- PathMD5 pmd5(p_path.md5_buffer());
+ String simplified_path = p_path.simplify_path();
+ PathMD5 pmd5(simplified_path.md5_buffer());
HashMap<PathMD5, PackedFile, PathMD5>::Iterator E = files.find(pmd5);
if (!E) {
return nullptr; //not found
@@ -197,7 +198,7 @@ Ref<FileAccess> PackedData::try_open_path(const String &p_path) {
}
bool PackedData::has_path(const String &p_path) {
- return files.has(PathMD5(p_path.md5_buffer()));
+ return files.has(PathMD5(p_path.simplify_path().md5_buffer()));
}
bool PackedData::has_directory(const String &p_path) {
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
index 190edbfb82..09505ea05d 100644
--- a/core/io/http_client.cpp
+++ b/core/io/http_client.cpp
@@ -63,8 +63,9 @@ Error HTTPClient::_request_raw(Method p_method, const String &p_url, const Vecto
}
Error HTTPClient::_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
- int size = p_body.length();
- return request(p_method, p_url, p_headers, size > 0 ? (const uint8_t *)p_body.utf8().get_data() : nullptr, size);
+ CharString body_utf8 = p_body.utf8();
+ int size = body_utf8.length();
+ return request(p_method, p_url, p_headers, size > 0 ? (const uint8_t *)body_utf8.get_data() : nullptr, size);
}
String HTTPClient::query_string_from_dict(const Dictionary &p_dict) {
diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp
index 3788fa501e..2f45238951 100644
--- a/core/io/http_client_tcp.cpp
+++ b/core/io/http_client_tcp.cpp
@@ -60,6 +60,7 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, Ref<TLSOp
}
ERR_FAIL_COND_V(tls_options.is_valid() && tls_options->is_server(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(tls_options.is_valid() && !StreamPeerTLS::is_available(), ERR_UNAVAILABLE, "HTTPS is not available in this build.");
ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER);
if (conn_port < 0) {
diff --git a/core/io/image.cpp b/core/io/image.cpp
index 736a3ec82e..9bb987b670 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -468,7 +468,7 @@ int Image::get_mipmap_count() const {
//using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers
template <uint32_t read_bytes, bool read_alpha, uint32_t write_bytes, bool write_alpha, bool read_gray, bool write_gray>
static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p_dst) {
- uint32_t max_bytes = MAX(read_bytes, write_bytes);
+ constexpr uint32_t max_bytes = MAX(read_bytes, write_bytes);
for (int y = 0; y < p_height; y++) {
for (int x = 0; x < p_width; x++) {
@@ -492,8 +492,9 @@ static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p
}
if constexpr (write_gray) {
- //TODO: not correct grayscale, should use fixed point version of actual weights
- wofs[0] = uint8_t((uint16_t(rgba[0]) + uint16_t(rgba[1]) + uint16_t(rgba[2])) / 3);
+ // REC.709
+ const uint8_t luminance = (13938U * rgba[0] + 46869U * rgba[1] + 4729U * rgba[2] + 32768U) >> 16U;
+ wofs[0] = luminance;
} else {
for (uint32_t i = 0; i < write_bytes; i++) {
wofs[i] = rgba[i];
@@ -2649,7 +2650,7 @@ Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels
_image_compress_bptc_func(this, p_channels);
} break;
case COMPRESS_ASTC: {
- ERR_FAIL_COND_V(!_image_compress_bptc_func, ERR_UNAVAILABLE);
+ ERR_FAIL_COND_V(!_image_compress_astc_func, ERR_UNAVAILABLE);
_image_compress_astc_func(this, p_astc_format);
} break;
case COMPRESS_MAX: {
@@ -3535,6 +3536,8 @@ void Image::_bind_methods() {
BIND_ENUM_CONSTANT(COMPRESS_ETC);
BIND_ENUM_CONSTANT(COMPRESS_ETC2);
BIND_ENUM_CONSTANT(COMPRESS_BPTC);
+ BIND_ENUM_CONSTANT(COMPRESS_ASTC);
+ BIND_ENUM_CONSTANT(COMPRESS_MAX);
BIND_ENUM_CONSTANT(USED_CHANNELS_L);
BIND_ENUM_CONSTANT(USED_CHANNELS_LA);
@@ -3716,9 +3719,9 @@ void Image::premultiply_alpha() {
for (int j = 0; j < width; j++) {
uint8_t *ptr = &data_ptr[(i * width + j) * 4];
- ptr[0] = (uint16_t(ptr[0]) * uint16_t(ptr[3])) >> 8;
- ptr[1] = (uint16_t(ptr[1]) * uint16_t(ptr[3])) >> 8;
- ptr[2] = (uint16_t(ptr[2]) * uint16_t(ptr[3])) >> 8;
+ ptr[0] = (uint16_t(ptr[0]) * uint16_t(ptr[3]) + 255U) >> 8;
+ ptr[1] = (uint16_t(ptr[1]) * uint16_t(ptr[3]) + 255U) >> 8;
+ ptr[2] = (uint16_t(ptr[2]) * uint16_t(ptr[3]) + 255U) >> 8;
}
}
}
diff --git a/core/io/ip.cpp b/core/io/ip.cpp
index 65728f34f6..772f700916 100644
--- a/core/io/ip.cpp
+++ b/core/io/ip.cpp
@@ -35,8 +35,6 @@
#include "core/templates/hash_map.h"
#include "core/variant/typed_array.h"
-VARIANT_ENUM_CAST(IP::ResolverStatus);
-
/************* RESOLVER ******************/
struct _IP_ResolverPrivate {
diff --git a/core/io/ip.h b/core/io/ip.h
index b768f0b9d4..4816d59ac2 100644
--- a/core/io/ip.h
+++ b/core/io/ip.h
@@ -109,5 +109,6 @@ public:
};
VARIANT_ENUM_CAST(IP::Type);
+VARIANT_ENUM_CAST(IP::ResolverStatus);
#endif // IP_H
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 8d0fe53ed4..a6e054a9fe 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -47,13 +47,7 @@ const char *JSON::tk_name[TK_MAX] = {
};
String JSON::_make_indent(const String &p_indent, int p_size) {
- String indent_text = "";
- if (!p_indent.is_empty()) {
- for (int i = 0; i < p_size; i++) {
- indent_text += p_indent;
- }
- }
- return indent_text;
+ return p_indent.repeat(p_size);
}
String JSON::_stringify(const Variant &p_var, const String &p_indent, int p_cur_indent, bool p_sort_keys, HashSet<const void *> &p_markers, bool p_full_precision) {
diff --git a/core/io/packed_data_container.cpp b/core/io/packed_data_container.cpp
index 6c16401f17..ce4edb18fe 100644
--- a/core/io/packed_data_container.cpp
+++ b/core/io/packed_data_container.cpp
@@ -320,6 +320,8 @@ uint32_t PackedDataContainer::_pack(const Variant &p_data, Vector<uint8_t> &tmpd
}
Error PackedDataContainer::pack(const Variant &p_data) {
+ ERR_FAIL_COND_V_MSG(p_data.get_type() != Variant::ARRAY && p_data.get_type() != Variant::DICTIONARY, ERR_INVALID_DATA, "PackedDataContainer can pack only Array and Dictionary type.");
+
Vector<uint8_t> tmpdata;
HashMap<String, uint32_t> string_cache;
_pack(p_data, tmpdata, string_cache);
@@ -361,7 +363,9 @@ void PackedDataContainer::_bind_methods() {
ClassDB::bind_method(D_METHOD("pack", "value"), &PackedDataContainer::pack);
ClassDB::bind_method(D_METHOD("size"), &PackedDataContainer::size);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "__data__"), "_set_data", "_get_data");
+ BIND_METHOD_ERR_RETURN_DOC("pack", ERR_INVALID_DATA);
+
+ ADD_PROPERTY(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "__data__", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data");
}
//////////////////
@@ -378,16 +382,11 @@ Variant PackedDataContainerRef::_iter_get(const Variant &p_iter) {
return from->_iter_get_ofs(p_iter, offset);
}
-bool PackedDataContainerRef::_is_dictionary() const {
- return from->_type_at_ofs(offset) == PackedDataContainer::TYPE_DICT;
-}
-
void PackedDataContainerRef::_bind_methods() {
ClassDB::bind_method(D_METHOD("size"), &PackedDataContainerRef::size);
ClassDB::bind_method(D_METHOD("_iter_init"), &PackedDataContainerRef::_iter_init);
ClassDB::bind_method(D_METHOD("_iter_get"), &PackedDataContainerRef::_iter_get);
ClassDB::bind_method(D_METHOD("_iter_next"), &PackedDataContainerRef::_iter_next);
- ClassDB::bind_method(D_METHOD("_is_dictionary"), &PackedDataContainerRef::_is_dictionary);
}
Variant PackedDataContainerRef::getvar(const Variant &p_key, bool *r_valid) const {
diff --git a/core/io/packed_data_container.h b/core/io/packed_data_container.h
index a77970a0bd..cc9996101e 100644
--- a/core/io/packed_data_container.h
+++ b/core/io/packed_data_container.h
@@ -94,7 +94,6 @@ public:
Variant _iter_init(const Array &p_iter);
Variant _iter_next(const Array &p_iter);
Variant _iter_get(const Variant &p_iter);
- bool _is_dictionary() const;
int size() const;
virtual Variant getvar(const Variant &p_key, bool *r_valid = nullptr) const override;
diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp
index e7f4980e94..9b49cc3d8c 100644
--- a/core/io/pck_packer.cpp
+++ b/core/io/pck_packer.cpp
@@ -115,7 +115,9 @@ Error PCKPacker::add_file(const String &p_file, const String &p_src, bool p_encr
}
File pf;
- pf.path = p_file;
+ // Simplify path here and on every 'files' access so that paths that have extra '/'
+ // symbols in them still match to the MD5 hash for the saved path.
+ pf.path = p_file.simplify_path();
pf.src_path = p_src;
pf.ofs = ofs;
pf.size = f->get_length();
diff --git a/core/io/remote_filesystem_client.cpp b/core/io/remote_filesystem_client.cpp
new file mode 100644
index 0000000000..f22e442a34
--- /dev/null
+++ b/core/io/remote_filesystem_client.cpp
@@ -0,0 +1,329 @@
+/**************************************************************************/
+/* remote_filesystem_client.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "remote_filesystem_client.h"
+
+#include "core/io/dir_access.h"
+#include "core/io/file_access.h"
+#include "core/io/stream_peer_tcp.h"
+#include "core/string/string_builder.h"
+
+#define FILESYSTEM_CACHE_VERSION 1
+#define FILESYSTEM_PROTOCOL_VERSION 1
+#define PASSWORD_LENGTH 32
+
+#define FILES_SUBFOLDER "remote_filesystem_files"
+#define FILES_CACHE_FILE "remote_filesystem.cache"
+
+Vector<RemoteFilesystemClient::FileCache> RemoteFilesystemClient::_load_cache_file() {
+ Ref<FileAccess> fa = FileAccess::open(cache_path.path_join(FILES_CACHE_FILE), FileAccess::READ);
+ if (!fa.is_valid()) {
+ return Vector<FileCache>(); // No cache, return empty
+ }
+
+ int version = fa->get_line().to_int();
+ if (version != FILESYSTEM_CACHE_VERSION) {
+ return Vector<FileCache>(); // Version mismatch, ignore everything.
+ }
+
+ String file_path = cache_path.path_join(FILES_SUBFOLDER);
+
+ Vector<FileCache> file_cache;
+
+ while (!fa->eof_reached()) {
+ String l = fa->get_line();
+ Vector<String> fields = l.split("::");
+ if (fields.size() != 3) {
+ break;
+ }
+ FileCache fc;
+ fc.path = fields[0];
+ fc.server_modified_time = fields[1].to_int();
+ fc.modified_time = fields[2].to_int();
+
+ String full_path = file_path.path_join(fc.path);
+ if (!FileAccess::exists(full_path)) {
+ continue; // File is gone.
+ }
+
+ if (FileAccess::get_modified_time(full_path) != fc.modified_time) {
+ DirAccess::remove_absolute(full_path); // Take the chance to remove this file and assume we no longer have it.
+ continue;
+ }
+
+ file_cache.push_back(fc);
+ }
+
+ return file_cache;
+}
+
+Error RemoteFilesystemClient::_store_file(const String &p_path, const LocalVector<uint8_t> &p_file, uint64_t &modified_time) {
+ modified_time = 0;
+ String full_path = cache_path.path_join(FILES_SUBFOLDER).path_join(p_path);
+ String base_file_dir = full_path.get_base_dir();
+
+ if (!validated_directories.has(base_file_dir)) {
+ // Verify that path exists before writing file, but only verify once for performance.
+ DirAccess::make_dir_recursive_absolute(base_file_dir);
+ validated_directories.insert(base_file_dir);
+ }
+
+ Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open file for writing to remote filesystem cache: " + p_path);
+ f->store_buffer(p_file.ptr(), p_file.size());
+ Error err = f->get_error();
+ if (err) {
+ return err;
+ }
+ f.unref(); // Unref to ensure file is not locked and modified time can be obtained.
+
+ modified_time = FileAccess::get_modified_time(full_path);
+ return OK;
+}
+
+Error RemoteFilesystemClient::_remove_file(const String &p_path) {
+ return DirAccess::remove_absolute(cache_path.path_join(FILES_SUBFOLDER).path_join(p_path));
+}
+Error RemoteFilesystemClient::_store_cache_file(const Vector<FileCache> &p_cache) {
+ String full_path = cache_path.path_join(FILES_CACHE_FILE);
+ String base_file_dir = full_path.get_base_dir();
+ Error err = DirAccess::make_dir_recursive_absolute(base_file_dir);
+ ERR_FAIL_COND_V_MSG(err != OK && err != ERR_ALREADY_EXISTS, err, "Unable to create base directory to store cache file: " + base_file_dir);
+
+ Ref<FileAccess> f = FileAccess::open(full_path, FileAccess::WRITE);
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Unable to open the remote cache file for writing: " + full_path);
+ f->store_line(itos(FILESYSTEM_CACHE_VERSION));
+ for (int i = 0; i < p_cache.size(); i++) {
+ String l = p_cache[i].path + "::" + itos(p_cache[i].server_modified_time) + "::" + itos(p_cache[i].modified_time);
+ f->store_line(l);
+ }
+ return OK;
+}
+
+Error RemoteFilesystemClient::synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path) {
+ Error err = _synchronize_with_server(p_host, p_port, p_password, r_cache_path);
+ // Ensure no memory is kept
+ validated_directories.reset();
+ cache_path = String();
+ return err;
+}
+
+void RemoteFilesystemClient::_update_cache_path(String &r_cache_path) {
+ r_cache_path = cache_path.path_join(FILES_SUBFOLDER);
+}
+
+Error RemoteFilesystemClient::_synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path) {
+ cache_path = r_cache_path;
+ {
+ Ref<DirAccess> dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ dir->change_dir(cache_path);
+ cache_path = dir->get_current_dir();
+ }
+
+ Ref<StreamPeerTCP> tcp_client;
+ tcp_client.instantiate();
+
+ IPAddress ip = p_host.is_valid_ip_address() ? IPAddress(p_host) : IP::get_singleton()->resolve_hostname(p_host);
+ ERR_FAIL_COND_V_MSG(!ip.is_valid(), ERR_INVALID_PARAMETER, "Unable to resolve remote filesystem server hostname: " + p_host);
+ print_verbose(vformat("Remote Filesystem: Connecting to host %s, port %d.", ip, p_port));
+ Error err = tcp_client->connect_to_host(ip, p_port);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to open connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
+
+ while (tcp_client->get_status() == StreamPeerTCP::STATUS_CONNECTING) {
+ tcp_client->poll();
+ OS::get_singleton()->delay_usec(100);
+ }
+
+ if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
+ ERR_FAIL_V_MSG(ERR_CANT_CONNECT, "Connection to remote file server (" + String(p_host) + ", port " + itos(p_port) + ") failed.");
+ }
+
+ // Connection OK, now send the current file state.
+ print_verbose("Remote Filesystem: Connection OK.");
+
+ // Header (GRFS) - Godot Remote File System
+ print_verbose("Remote Filesystem: Sending header");
+ tcp_client->put_u8('G');
+ tcp_client->put_u8('R');
+ tcp_client->put_u8('F');
+ tcp_client->put_u8('S');
+ // Protocol version
+ tcp_client->put_32(FILESYSTEM_PROTOCOL_VERSION);
+ print_verbose("Remote Filesystem: Sending password");
+ uint8_t password[PASSWORD_LENGTH]; // Send fixed size password, since it's easier and safe to validate.
+ for (int i = 0; i < PASSWORD_LENGTH; i++) {
+ if (i < p_password.length()) {
+ password[i] = p_password[i];
+ } else {
+ password[i] = 0;
+ }
+ }
+ tcp_client->put_data(password, PASSWORD_LENGTH);
+ print_verbose("Remote Filesystem: Tags.");
+ Vector<String> tags;
+ {
+ tags.push_back(OS::get_singleton()->get_identifier());
+ switch (OS::get_singleton()->get_preferred_texture_format()) {
+ case OS::PREFERRED_TEXTURE_FORMAT_S3TC_BPTC: {
+ tags.push_back("bptc");
+ tags.push_back("s3tc");
+ } break;
+ case OS::PREFERRED_TEXTURE_FORMAT_ETC2_ASTC: {
+ tags.push_back("etc2");
+ tags.push_back("astc");
+ } break;
+ }
+ }
+
+ tcp_client->put_32(tags.size());
+ for (int i = 0; i < tags.size(); i++) {
+ tcp_client->put_utf8_string(tags[i]);
+ }
+ // Size of compressed list of files
+ print_verbose("Remote Filesystem: Sending file list");
+
+ Vector<FileCache> file_cache = _load_cache_file();
+
+ // Encode file cache to send it via network.
+ Vector<uint8_t> file_cache_buffer;
+ if (file_cache.size()) {
+ StringBuilder sbuild;
+ for (int i = 0; i < file_cache.size(); i++) {
+ sbuild.append(file_cache[i].path);
+ sbuild.append("::");
+ sbuild.append(itos(file_cache[i].server_modified_time));
+ sbuild.append("\n");
+ }
+ String s = sbuild.as_string();
+ CharString cs = s.utf8();
+ file_cache_buffer.resize(Compression::get_max_compressed_buffer_size(cs.length(), Compression::MODE_ZSTD));
+ int res_len = Compression::compress(file_cache_buffer.ptrw(), (const uint8_t *)cs.ptr(), cs.length(), Compression::MODE_ZSTD);
+ file_cache_buffer.resize(res_len);
+
+ tcp_client->put_32(cs.length()); // Size of buffer uncompressed
+ tcp_client->put_32(file_cache_buffer.size()); // Size of buffer compressed
+ tcp_client->put_data(file_cache_buffer.ptr(), file_cache_buffer.size()); // Buffer
+ } else {
+ tcp_client->put_32(0); // No file cache buffer
+ }
+
+ tcp_client->poll();
+ ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected after sending header.");
+
+ uint32_t file_count = tcp_client->get_32();
+
+ ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected while waiting for file list");
+
+ LocalVector<uint8_t> file_buffer;
+
+ Vector<FileCache> temp_file_cache;
+
+ HashSet<String> files_processed;
+ for (uint32_t i = 0; i < file_count; i++) {
+ String file = tcp_client->get_utf8_string();
+ ERR_FAIL_COND_V_MSG(file == String(), ERR_CONNECTION_ERROR, "Invalid file name received from remote filesystem.");
+ uint64_t server_modified_time = tcp_client->get_u64();
+ ERR_FAIL_COND_V_MSG(tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED, ERR_CONNECTION_ERROR, "Remote filesystem server disconnected while waiting for file info.");
+
+ FileCache fc;
+ fc.path = file;
+ fc.server_modified_time = server_modified_time;
+ temp_file_cache.push_back(fc);
+
+ files_processed.insert(file);
+ }
+
+ Vector<FileCache> new_file_cache;
+
+ // Get the actual files. As a robustness measure, if the connection is interrupted here, any file not yet received will be considered removed.
+ // Since the file changed anyway, this makes it the easiest way to keep robustness.
+
+ bool server_disconnected = false;
+ for (uint32_t i = 0; i < file_count; i++) {
+ String file = temp_file_cache[i].path;
+
+ if (temp_file_cache[i].server_modified_time == 0 || server_disconnected) {
+ // File was removed, or server disconnected before tranferring it. Since it's no longer valid, remove anyway.
+ _remove_file(file);
+ continue;
+ }
+
+ uint64_t file_size = tcp_client->get_u64();
+ file_buffer.resize(file_size);
+
+ err = tcp_client->get_data(file_buffer.ptr(), file_size);
+ if (err != OK) {
+ ERR_PRINT("Error retrieving file from remote filesystem: " + file);
+ server_disconnected = true;
+ }
+
+ if (tcp_client->get_status() != StreamPeerTCP::STATUS_CONNECTED) {
+ // Early disconnect, stop accepting files.
+ server_disconnected = true;
+ }
+
+ if (server_disconnected) {
+ // No more server, transfer is invalid, remove this file.
+ _remove_file(file);
+ continue;
+ }
+
+ uint64_t modified_time = 0;
+ err = _store_file(file, file_buffer, modified_time);
+ if (err != OK) {
+ server_disconnected = true;
+ continue;
+ }
+ FileCache fc = temp_file_cache[i];
+ fc.modified_time = modified_time;
+ new_file_cache.push_back(fc);
+ }
+
+ print_verbose("Remote Filesystem: Updating the cache file.");
+
+ // Go through the list of local files read initially (file_cache) and see which ones are
+ // unchanged (not sent again from the server).
+ // These need to be re-saved in the new list (new_file_cache).
+
+ for (int i = 0; i < file_cache.size(); i++) {
+ if (files_processed.has(file_cache[i].path)) {
+ continue; // This was either added or removed, so skip.
+ }
+ new_file_cache.push_back(file_cache[i]);
+ }
+
+ err = _store_cache_file(new_file_cache);
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_CANT_OPEN, "Error writing the remote filesystem file cache.");
+
+ print_verbose("Remote Filesystem: Update success.");
+
+ _update_cache_path(r_cache_path);
+ return OK;
+}
diff --git a/core/io/remote_filesystem_client.h b/core/io/remote_filesystem_client.h
new file mode 100644
index 0000000000..42eba98eb1
--- /dev/null
+++ b/core/io/remote_filesystem_client.h
@@ -0,0 +1,65 @@
+/**************************************************************************/
+/* remote_filesystem_client.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef REMOTE_FILESYSTEM_CLIENT_H
+#define REMOTE_FILESYSTEM_CLIENT_H
+
+#include "core/io/ip_address.h"
+#include "core/string/ustring.h"
+#include "core/templates/hash_set.h"
+#include "core/templates/local_vector.h"
+
+class RemoteFilesystemClient {
+ String cache_path;
+ HashSet<String> validated_directories;
+
+protected:
+ String _get_cache_path() { return cache_path; }
+ struct FileCache {
+ String path; // Local path (as in "folder/to/file.png")
+ uint64_t server_modified_time; // MD5 checksum.
+ uint64_t modified_time;
+ };
+ virtual bool _is_configured() { return !cache_path.is_empty(); }
+ // Can be re-implemented per platform. If so, feel free to ignore get_cache_path()
+ virtual Vector<FileCache> _load_cache_file();
+ virtual Error _store_file(const String &p_path, const LocalVector<uint8_t> &p_file, uint64_t &modified_time);
+ virtual Error _remove_file(const String &p_path);
+ virtual Error _store_cache_file(const Vector<FileCache> &p_cache);
+ virtual Error _synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path);
+
+ virtual void _update_cache_path(String &r_cache_path);
+
+public:
+ Error synchronize_with_server(const String &p_host, int p_port, const String &p_password, String &r_cache_path);
+ virtual ~RemoteFilesystemClient() {}
+};
+
+#endif // REMOTE_FILESYSTEM_CLIENT_H
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index 38f41d645c..2a7a675f2d 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -445,13 +445,12 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
WARN_PRINT("Broken external resource! (index out of size)");
r_v = Variant();
} else {
- if (external_resources[erindex].cache.is_null()) {
- //cache not here yet, wait for it?
- if (use_sub_threads) {
- Error err;
- external_resources.write[erindex].cache = ResourceLoader::load_threaded_get(external_resources[erindex].path, &err);
-
- if (err != OK || external_resources[erindex].cache.is_null()) {
+ Ref<ResourceLoader::LoadToken> &load_token = external_resources.write[erindex].load_token;
+ if (load_token.is_valid()) { // If not valid, it's OK since then we know this load accepts broken dependencies.
+ Error err;
+ Ref<Resource> res = ResourceLoader::_load_complete(*load_token.ptr(), &err);
+ if (res.is_null()) {
+ if (!ResourceLoader::is_cleaning_tasks()) {
if (!ResourceLoader::get_abort_on_missing_resources()) {
ResourceLoader::notify_dependency_error(local_path, external_resources[erindex].path, external_resources[erindex].type);
} else {
@@ -459,12 +458,11 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
ERR_FAIL_V_MSG(error, "Can't load dependency: " + external_resources[erindex].path + ".");
}
}
+ } else {
+ r_v = res;
}
}
-
- r_v = external_resources[erindex].cache;
}
-
} break;
default: {
ERR_FAIL_V(ERR_FILE_CORRUPT);
@@ -684,28 +682,13 @@ Error ResourceLoaderBinary::load() {
}
external_resources.write[i].path = path; //remap happens here, not on load because on load it can actually be used for filesystem dock resource remap
-
- if (!use_sub_threads) {
- external_resources.write[i].cache = ResourceLoader::load(path, external_resources[i].type);
-
- if (external_resources[i].cache.is_null()) {
- if (!ResourceLoader::get_abort_on_missing_resources()) {
- ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type);
- } else {
- error = ERR_FILE_MISSING_DEPENDENCIES;
- ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + ".");
- }
- }
-
- } else {
- Error err = ResourceLoader::load_threaded_request(path, external_resources[i].type, use_sub_threads, ResourceFormatLoader::CACHE_MODE_REUSE, local_path);
- if (err != OK) {
- if (!ResourceLoader::get_abort_on_missing_resources()) {
- ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type);
- } else {
- error = ERR_FILE_MISSING_DEPENDENCIES;
- ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + ".");
- }
+ external_resources.write[i].load_token = ResourceLoader::_load_start(path, external_resources[i].type, use_sub_threads ? ResourceLoader::LOAD_THREAD_DISTRIBUTE : ResourceLoader::LOAD_THREAD_FROM_CURRENT, ResourceFormatLoader::CACHE_MODE_REUSE);
+ if (!external_resources[i].load_token.is_valid()) {
+ if (!ResourceLoader::get_abort_on_missing_resources()) {
+ ResourceLoader::notify_dependency_error(local_path, path, external_resources[i].type);
+ } else {
+ error = ERR_FILE_MISSING_DEPENDENCIES;
+ ERR_FAIL_V_MSG(error, "Can't load dependency: " + path + ".");
}
}
}
@@ -937,8 +920,11 @@ void ResourceLoaderBinary::get_dependencies(Ref<FileAccess> p_f, List<String> *p
for (int i = 0; i < external_resources.size(); i++) {
String dep;
+ String fallback_path;
+
if (external_resources[i].uid != ResourceUID::INVALID_ID) {
dep = ResourceUID::get_singleton()->id_to_text(external_resources[i].uid);
+ fallback_path = external_resources[i].path; // Used by Dependency Editor, in case uid path fails.
} else {
dep = external_resources[i].path;
}
@@ -946,6 +932,12 @@ void ResourceLoaderBinary::get_dependencies(Ref<FileAccess> p_f, List<String> *p
if (p_add_types && !external_resources[i].type.is_empty()) {
dep += "::" + external_resources[i].type;
}
+ if (!fallback_path.is_empty()) {
+ if (!p_add_types) {
+ dep += "::"; // Ensure that path comes third, even if there is no type.
+ }
+ dep += "::" + fallback_path;
+ }
p_dependencies->push_back(dep);
}
diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h
index add7cdf297..30f1664983 100644
--- a/core/io/resource_format_binary.h
+++ b/core/io/resource_format_binary.h
@@ -60,7 +60,7 @@ class ResourceLoaderBinary {
String path;
String type;
ResourceUID::ID uid = ResourceUID::INVALID_ID;
- Ref<Resource> cache;
+ Ref<ResourceLoader::LoadToken> load_token;
};
bool using_named_scene_ids = false;
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index a46fac4e7a..525c41cf87 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -202,20 +202,71 @@ void ResourceFormatLoader::_bind_methods() {
///////////////////////////////////
+// This should be robust enough to be called redundantly without issues.
+void ResourceLoader::LoadToken::clear() {
+ thread_load_mutex.lock();
+
+ WorkerThreadPool::TaskID task_to_await = 0;
+
+ if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered.
+ DEV_ASSERT(thread_load_tasks.has(local_path));
+ ThreadLoadTask &load_task = thread_load_tasks[local_path];
+ if (!load_task.awaited) {
+ task_to_await = load_task.task_id;
+ load_task.awaited = true;
+ }
+ thread_load_tasks.erase(local_path);
+ local_path.clear();
+ }
+
+ if (!user_path.is_empty()) {
+ DEV_ASSERT(user_load_tokens.has(user_path));
+ user_load_tokens.erase(user_path);
+ user_path.clear();
+ }
+
+ thread_load_mutex.unlock();
+
+ // If task is unused, await it here, locally, now the token data is consistent.
+ if (task_to_await) {
+ WorkerThreadPool::get_singleton()->wait_for_task_completion(task_to_await);
+ }
+}
+
+ResourceLoader::LoadToken::~LoadToken() {
+ clear();
+}
+
Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) {
- bool found = false;
+ load_nesting++;
+ if (load_paths_stack.size()) {
+ thread_load_mutex.lock();
+ HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(load_paths_stack[load_paths_stack.size() - 1]);
+ if (E) {
+ E->value.sub_tasks.insert(p_path);
+ }
+ thread_load_mutex.unlock();
+ }
+ load_paths_stack.push_back(p_path);
// Try all loaders and pick the first match for the type hint
+ bool found = false;
+ Ref<Resource> res;
for (int i = 0; i < loader_count; i++) {
if (!loader[i]->recognize_path(p_path, p_type_hint)) {
continue;
}
found = true;
- Ref<Resource> res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
- if (res.is_null()) {
- continue;
+ res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
+ if (!res.is_null()) {
+ break;
}
+ }
+
+ load_paths_stack.resize(load_paths_stack.size() - 1);
+ load_nesting--;
+ if (!res.is_null()) {
return res;
}
@@ -232,47 +283,64 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
void ResourceLoader::_thread_load_function(void *p_userdata) {
ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata;
- load_task.loader_id = Thread::get_caller_id();
- if (load_task.cond_var) {
- //this is an actual thread, so wait for Ok from semaphore
- thread_load_semaphore->wait(); //wait until its ok to start loading
+ thread_load_mutex.lock();
+ caller_task_id = load_task.task_id;
+ if (cleaning_tasks) {
+ load_task.status = THREAD_LOAD_FAILED;
+ thread_load_mutex.unlock();
+ return;
}
- load_task.resource = _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);
+ thread_load_mutex.unlock();
- load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0
+ // Thread-safe either if it's the current thread or a brand new one.
+ CallQueue *mq_override = nullptr;
+ if (load_nesting == 0) {
+ 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);
+ set_current_thread_safe_for_nodes(true);
+ }
+ } else {
+ DEV_ASSERT(load_task.dependent_path.is_empty());
+ }
+ // --
- thread_load_mutex->lock();
+ 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();
+ }
+
+ 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
if (load_task.error != OK) {
load_task.status = THREAD_LOAD_FAILED;
} else {
load_task.status = THREAD_LOAD_LOADED;
}
- if (load_task.cond_var) {
- if (load_task.start_next && thread_waiting_count > 0) {
- thread_waiting_count--;
- //thread loading count remains constant, this ends but another one begins
- thread_load_semaphore->post();
- } else {
- thread_loading_count--; //no threads waiting, just reduce loading count
- }
-
- print_lt("END: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count));
+ if (load_task.cond_var) {
load_task.cond_var->notify_all();
memdelete(load_task.cond_var);
load_task.cond_var = nullptr;
}
if (load_task.resource.is_valid()) {
- load_task.resource->set_path(load_task.local_path);
+ if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
+ load_task.resource->set_path(load_task.local_path);
+ }
if (load_task.xl_remapped) {
load_task.resource->set_as_translation_remapped(true);
}
#ifdef TOOLS_ENABLED
-
load_task.resource->set_edited(false);
if (timestamp_on_load) {
uint64_t mt = FileAccess::get_modified_time(load_task.remapped_path);
@@ -286,7 +354,11 @@ void ResourceLoader::_thread_load_function(void *p_userdata) {
}
}
- thread_load_mutex->unlock();
+ thread_load_mutex.unlock();
+
+ if (load_nesting == 0 && mq_override) {
+ memdelete(mq_override);
+ }
}
static String _validate_local_path(const String &p_path) {
@@ -299,91 +371,127 @@ static String _validate_local_path(const String &p_path) {
return ProjectSettings::get_singleton()->localize_path(p_path);
}
}
-Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode, const String &p_source_resource) {
- String local_path = _validate_local_path(p_path);
- thread_load_mutex->lock();
+Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode) {
+ thread_load_mutex.lock();
+ if (user_load_tokens.has(p_path)) {
+ print_verbose("load_threaded_request(): Another threaded load for resource path '" + p_path + "' has been initiated. Not an error.");
+ user_load_tokens[p_path]->reference(); // Additional request.
+ thread_load_mutex.unlock();
+ return OK;
+ }
+ user_load_tokens[p_path] = nullptr;
+ thread_load_mutex.unlock();
+
+ Ref<ResourceLoader::LoadToken> token = _load_start(p_path, p_type_hint, p_use_sub_threads ? LOAD_THREAD_DISTRIBUTE : LOAD_THREAD_SPAWN_SINGLE, p_cache_mode);
+ if (token.is_valid()) {
+ thread_load_mutex.lock();
+ token->user_path = p_path;
+ token->reference(); // First request.
+ user_load_tokens[p_path] = token.ptr();
+ print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size()));
+ thread_load_mutex.unlock();
+ return OK;
+ } else {
+ return FAILED;
+ }
+}
- if (!p_source_resource.is_empty()) {
- //must be loading from this resource
- if (!thread_load_tasks.has(p_source_resource)) {
- thread_load_mutex->unlock();
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "There is no thread loading source resource '" + p_source_resource + "'.");
- }
- //must not be already added as s sub tasks
- if (thread_load_tasks[p_source_resource].sub_tasks.has(local_path)) {
- thread_load_mutex->unlock();
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Thread loading source resource '" + p_source_resource + "' already is loading '" + local_path + "'.");
- }
+Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) {
+ if (r_error) {
+ *r_error = OK;
}
- if (thread_load_tasks.has(local_path)) {
- thread_load_tasks[local_path].requests++;
- if (!p_source_resource.is_empty()) {
- thread_load_tasks[p_source_resource].sub_tasks.insert(local_path);
+ Ref<LoadToken> load_token = _load_start(p_path, p_type_hint, LOAD_THREAD_FROM_CURRENT, p_cache_mode);
+ if (!load_token.is_valid()) {
+ if (r_error) {
+ *r_error = FAILED;
}
- thread_load_mutex->unlock();
- return OK;
+ return Ref<Resource>();
}
- {
- //create load task
-
- ThreadLoadTask load_task;
+ Ref<Resource> res = _load_complete(*load_token.ptr(), r_error);
+ return res;
+}
- load_task.requests = 1;
- load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped);
- load_task.local_path = local_path;
- load_task.type_hint = p_type_hint;
- load_task.cache_mode = p_cache_mode;
- load_task.use_sub_threads = p_use_sub_threads;
+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);
- { //must check if resource is already loaded before attempting to load it in a thread
+ 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 (load_task.loader_id == Thread::get_caller_id()) {
- thread_load_mutex->unlock();
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Attempted to load a resource already being loaded from this thread, cyclic reference?");
+ if (thread_load_tasks.has(local_path)) {
+ load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token);
+ if (!load_token.is_valid()) {
+ // 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;
+ }
}
+ }
- Ref<Resource> existing = ResourceCache::get_ref(local_path);
+ load_token.instantiate();
+ load_token->local_path = local_path;
- if (existing.is_valid()) {
- //referencing is fine
- load_task.resource = existing;
- load_task.status = THREAD_LOAD_LOADED;
- load_task.progress = 1.0;
+ //create load task
+ {
+ ThreadLoadTask load_task;
+
+ load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped);
+ load_task.load_token = load_token.ptr();
+ load_task.local_path = local_path;
+ load_task.type_hint = p_type_hint;
+ load_task.cache_mode = p_cache_mode;
+ load_task.use_sub_threads = p_thread_mode == LOAD_THREAD_DISTRIBUTE;
+ if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
+ Ref<Resource> existing = ResourceCache::get_ref(local_path);
+ if (existing.is_valid()) {
+ //referencing is fine
+ load_task.resource = existing;
+ load_task.status = THREAD_LOAD_LOADED;
+ load_task.progress = 1.0;
+ thread_load_tasks[local_path] = load_task;
+ return load_token;
+ }
}
- }
- if (!p_source_resource.is_empty()) {
- thread_load_tasks[p_source_resource].sub_tasks.insert(local_path);
- }
-
- thread_load_tasks[local_path] = load_task;
- }
+ // 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) {
+ load_token->local_path.clear();
+ unregistered_load_task = load_task;
+ } else {
+ thread_load_tasks[local_path] = load_task;
+ }
- ThreadLoadTask &load_task = thread_load_tasks[local_path];
+ load_task_ptr = must_not_register ? &unregistered_load_task : &thread_load_tasks[local_path];
+ }
- if (load_task.resource.is_null()) { //needs to be loaded in thread
+ run_on_current_thread = must_not_register || p_thread_mode == LOAD_THREAD_FROM_CURRENT;
- load_task.cond_var = memnew(ConditionVariable);
- if (thread_loading_count < thread_load_max) {
- thread_loading_count++;
- thread_load_semaphore->post(); //we have free threads, so allow one
+ if (run_on_current_thread) {
+ load_task_ptr->thread_id = Thread::get_caller_id();
} else {
- thread_waiting_count++;
+ load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_thread_load_function, load_task_ptr);
}
-
- print_lt("REQUEST: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count));
-
- load_task.thread = memnew(Thread);
- load_task.thread->start(_thread_load_function, &thread_load_tasks[local_path]);
- load_task.loader_id = load_task.thread->get_id();
}
- thread_load_mutex->unlock();
+ if (run_on_current_thread) {
+ _thread_load_function(load_task_ptr);
+ if (must_not_register) {
+ load_token->res_if_unregistered = load_task_ptr->resource;
+ }
+ }
- return OK;
+ return load_token;
}
float ResourceLoader::_dependency_get_progress(const String &p_path) {
@@ -409,13 +517,22 @@ float ResourceLoader::_dependency_get_progress(const String &p_path) {
}
ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) {
- String local_path = _validate_local_path(p_path);
+ 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;
+ }
- thread_load_mutex->lock();
+ String local_path = _validate_local_path(p_path);
if (!thread_load_tasks.has(local_path)) {
- thread_load_mutex->unlock();
+#ifdef DEV_ENABLED
+ CRASH_NOW();
+#endif
+ // 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];
ThreadLoadStatus status;
status = load_task.status;
@@ -423,198 +540,139 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const
*r_progress = _dependency_get_progress(local_path);
}
- thread_load_mutex->unlock();
-
return status;
}
Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) {
- String local_path = _validate_local_path(p_path);
-
- MutexLock thread_load_lock(*thread_load_mutex);
- if (!thread_load_tasks.has(local_path)) {
- if (r_error) {
- *r_error = ERR_INVALID_PARAMETER;
- }
- return Ref<Resource>();
+ if (r_error) {
+ *r_error = OK;
}
- ThreadLoadTask &load_task = thread_load_tasks[local_path];
+ Ref<Resource> res;
+ {
+ MutexLock thread_load_lock(thread_load_mutex);
- if (load_task.status == THREAD_LOAD_IN_PROGRESS) {
- if (load_task.loader_id == Thread::get_caller_id()) {
- // Load is in progress, but it's precisely this thread the one in charge.
- // That means this is a cyclic load.
+ if (!user_load_tokens.has(p_path)) {
+ print_verbose("load_threaded_get(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected.");
if (r_error) {
- *r_error = ERR_BUSY;
+ *r_error = ERR_INVALID_PARAMETER;
}
return Ref<Resource>();
- } else if (!load_task.cond_var) {
- // Load is in progress, but a condition variable was never created for it.
- // That happens when a load has been initiated with subthreads disabled,
- // but now another load thread needs to interact with this one (either
- // because of subthreads being used this time, or because it's simply a
- // threaded load running on a different thread).
- // Since we want to be notified when the load ends, we must create the
- // condition variable now.
- load_task.cond_var = memnew(ConditionVariable);
- }
- }
-
- //cond var still exists, meaning it's still loading, request poll
- if (load_task.cond_var) {
- {
- // As we got a cond var, this means we are going to have to wait
- // until the sub-resource is done loading
- //
- // As this thread will become 'blocked' we should "exchange" its
- // active status with a waiting one, to ensure load continues.
- //
- // This ensures loading is never blocked and that is also within
- // the maximum number of active threads.
-
- if (thread_waiting_count > 0) {
- thread_waiting_count--;
- thread_loading_count++;
- thread_load_semaphore->post();
-
- load_task.start_next = false; //do not start next since we are doing it here
- }
-
- thread_suspended_count++;
-
- print_lt("GET: load count: " + itos(thread_loading_count) + " / wait count: " + itos(thread_waiting_count) + " / suspended count: " + itos(thread_suspended_count) + " / active: " + itos(thread_loading_count - thread_suspended_count));
- }
-
- bool still_valid = true;
- bool was_thread = load_task.thread;
- do {
- load_task.cond_var->wait(thread_load_lock);
- if (!thread_load_tasks.has(local_path)) { //may have been erased during unlock and this was always an invalid call
- still_valid = false;
- break;
- }
- } while (load_task.cond_var); // In case of spurious wakeup.
-
- if (was_thread) {
- thread_suspended_count--;
}
- if (!still_valid) {
+ LoadToken *load_token = user_load_tokens[p_path];
+ if (!load_token) {
+ // This happens if requested from one thread and rapidly querying from another.
if (r_error) {
- *r_error = ERR_INVALID_PARAMETER;
+ *r_error = ERR_BUSY;
}
return Ref<Resource>();
}
+ res = _load_complete_inner(*load_token, r_error, thread_load_lock);
+ if (load_token->unreference()) {
+ memdelete(load_token);
+ }
}
- Ref<Resource> resource = load_task.resource;
- if (r_error) {
- *r_error = load_task.error;
- }
-
- load_task.requests--;
+ print_lt("GET: user load tokens: " + itos(user_load_tokens.size()));
- if (load_task.requests == 0) {
- if (load_task.thread) { //thread may not have been used
- load_task.thread->wait_to_finish();
- memdelete(load_task.thread);
- }
- thread_load_tasks.erase(local_path);
- }
+ return res;
+}
- return resource;
+Ref<Resource> ResourceLoader::_load_complete(LoadToken &p_load_token, Error *r_error) {
+ MutexLock thread_load_lock(thread_load_mutex);
+ return _load_complete_inner(p_load_token, r_error, thread_load_lock);
}
-Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) {
+Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock) {
if (r_error) {
- *r_error = ERR_CANT_OPEN;
+ *r_error = OK;
}
- String local_path = _validate_local_path(p_path);
+ if (!p_load_token.local_path.is_empty()) {
+ if (!thread_load_tasks.has(p_load_token.local_path)) {
+#ifdef DEV_ENABLED
+ CRASH_NOW();
+#endif
+ // On non-dev, be defensive and at least avoid crashing (at this point at least).
+ if (r_error) {
+ *r_error = ERR_BUG;
+ }
+ return Ref<Resource>();
+ }
- if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
- thread_load_mutex->lock();
+ ThreadLoadTask &load_task = thread_load_tasks[p_load_token.local_path];
- //Is it already being loaded? poll until done
- if (thread_load_tasks.has(local_path)) {
- Error err = load_threaded_request(p_path, p_type_hint);
- if (err != OK) {
+ if (load_task.status == THREAD_LOAD_IN_PROGRESS) {
+ DEV_ASSERT((load_task.task_id == 0) != (load_task.thread_id == 0));
+
+ if ((load_task.task_id != 0 && load_task.task_id == caller_task_id) ||
+ (load_task.thread_id != 0 && load_task.thread_id == Thread::get_caller_id())) {
+ // Load is in progress, but it's precisely this thread the one in charge.
+ // That means this is a cyclic load.
if (r_error) {
- *r_error = err;
+ *r_error = ERR_BUSY;
}
- thread_load_mutex->unlock();
return Ref<Resource>();
}
- thread_load_mutex->unlock();
-
- return load_threaded_get(p_path, r_error);
- }
- //Is it cached?
-
- Ref<Resource> existing = ResourceCache::get_ref(local_path);
-
- if (existing.is_valid()) {
- thread_load_mutex->unlock();
-
- if (r_error) {
- *r_error = OK;
+ if (load_task.task_id != 0) {
+ // 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 scheduled tasks in a way that the current load depends on
+ // another one in a lower stack frame. Restart such load here. When the stack is eventually
+ // unrolled, the original load will have been notified to go on.
+#ifdef DEV_ENABLED
+ print_verbose("ResourceLoader: Load task happened to wait on another one deep in the call stack. Attempting to avoid deadlock by re-issuing the load now.");
+#endif
+ // 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;
+ }
+ thread_load_mutex.lock();
+ return resource;
+ } else {
+ DEV_ASSERT(err == OK);
+ thread_load_mutex.lock();
+ }
+ } else {
+ // 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);
}
-
- return existing; //use cached
- }
-
- //load using task (but this thread)
- ThreadLoadTask load_task;
-
- load_task.requests = 1;
- load_task.local_path = local_path;
- load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped);
- load_task.type_hint = p_type_hint;
- load_task.cache_mode = p_cache_mode; //ignore
- load_task.loader_id = Thread::get_caller_id();
-
- thread_load_tasks[local_path] = load_task;
-
- thread_load_mutex->unlock();
-
- _thread_load_function(&thread_load_tasks[local_path]);
-
- return load_threaded_get(p_path, r_error);
-
- } else {
- bool xl_remapped = false;
- String path = _path_remap(local_path, &xl_remapped);
-
- if (path.is_empty()) {
- ERR_FAIL_V_MSG(Ref<Resource>(), "Remapping '" + local_path + "' failed.");
}
- print_verbose("Loading resource: " + path);
- float p;
- Ref<Resource> res = _load(path, local_path, p_type_hint, p_cache_mode, r_error, false, &p);
-
- if (res.is_null()) {
- print_verbose("Failed loading resource: " + path);
- return Ref<Resource>();
+ if (cleaning_tasks) {
+ load_task.resource = Ref<Resource>();
+ load_task.error = FAILED;
}
- if (xl_remapped) {
- res->set_as_translation_remapped(true);
+ Ref<Resource> resource = load_task.resource;
+ if (r_error) {
+ *r_error = load_task.error;
}
-
-#ifdef TOOLS_ENABLED
-
- res->set_edited(false);
- if (timestamp_on_load) {
- uint64_t mt = FileAccess::get_modified_time(path);
- //printf("mt %s: %lli\n",remapped_path.utf8().get_data(),mt);
- res->set_last_modified_time(mt);
+ return resource;
+ } else {
+ // Special case of an unregistered task.
+ // The resource should have been loaded by now.
+ Ref<Resource> resource = p_load_token.res_if_unregistered;
+ if (!resource.is_valid()) {
+ if (r_error) {
+ *r_error = FAILED;
+ }
}
-#endif
-
- return res;
+ return resource;
}
}
@@ -958,32 +1016,42 @@ void ResourceLoader::clear_translation_remaps() {
}
void ResourceLoader::clear_thread_load_tasks() {
- thread_load_mutex->lock();
-
- for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) {
- switch (E.value.status) {
- case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_LOADED: {
- E.value.resource = Ref<Resource>();
- } break;
-
- case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_IN_PROGRESS: {
- if (E.value.thread != nullptr) {
- E.value.thread->wait_to_finish();
- memdelete(E.value.thread);
- E.value.thread = nullptr;
+ // Bring the thing down as quickly as possible without causing deadlocks or leaks.
+
+ thread_load_mutex.lock();
+ cleaning_tasks = true;
+
+ while (true) {
+ bool none_running = true;
+ if (thread_load_tasks.size()) {
+ for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) {
+ if (E.value.status == THREAD_LOAD_IN_PROGRESS) {
+ if (E.value.cond_var) {
+ E.value.cond_var->notify_all();
+ memdelete(E.value.cond_var);
+ E.value.cond_var = nullptr;
+ }
+ none_running = false;
}
- E.value.resource = Ref<Resource>();
- } break;
-
- case ResourceLoader::ThreadLoadStatus::THREAD_LOAD_FAILED:
- default: {
- // do nothing
}
}
+ if (none_running) {
+ break;
+ }
+ thread_load_mutex.unlock();
+ OS::get_singleton()->delay_usec(1000);
+ thread_load_mutex.lock();
+ }
+
+ for (KeyValue<String, LoadToken *> &E : user_load_tokens) {
+ memdelete(E.value);
}
+ user_load_tokens.clear();
+
thread_load_tasks.clear();
- thread_load_mutex->unlock();
+ cleaning_tasks = false;
+ thread_load_mutex.unlock();
}
void ResourceLoader::load_path_remaps() {
@@ -1080,40 +1148,33 @@ void ResourceLoader::remove_custom_loaders() {
}
}
-void ResourceLoader::initialize() {
- thread_load_mutex = memnew(SafeBinaryMutex<BINARY_MUTEX_TAG>);
- thread_load_max = OS::get_singleton()->get_processor_count();
- thread_loading_count = 0;
- thread_waiting_count = 0;
- thread_suspended_count = 0;
- thread_load_semaphore = memnew(Semaphore);
+bool ResourceLoader::is_cleaning_tasks() {
+ MutexLock lock(thread_load_mutex);
+ return cleaning_tasks;
}
-void ResourceLoader::finalize() {
- memdelete(thread_load_mutex);
- memdelete(thread_load_semaphore);
-}
+void ResourceLoader::initialize() {}
-ResourceLoadErrorNotify ResourceLoader::err_notify = nullptr;
-void *ResourceLoader::err_notify_ud = nullptr;
+void ResourceLoader::finalize() {}
+ResourceLoadErrorNotify ResourceLoader::err_notify = nullptr;
DependencyErrorNotify ResourceLoader::dep_err_notify = nullptr;
-void *ResourceLoader::dep_err_notify_ud = nullptr;
bool ResourceLoader::create_missing_resources_if_class_unavailable = false;
bool ResourceLoader::abort_on_missing_resource = true;
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;
+
template <>
thread_local uint32_t SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::count = 0;
-SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> *ResourceLoader::thread_load_mutex = nullptr;
+SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> ResourceLoader::thread_load_mutex;
HashMap<String, ResourceLoader::ThreadLoadTask> ResourceLoader::thread_load_tasks;
-Semaphore *ResourceLoader::thread_load_semaphore = nullptr;
+bool ResourceLoader::cleaning_tasks = false;
-int ResourceLoader::thread_loading_count = 0;
-int ResourceLoader::thread_waiting_count = 0;
-int ResourceLoader::thread_suspended_count = 0;
-int ResourceLoader::thread_load_max = 0;
+HashMap<String, ResourceLoader::LoadToken *> ResourceLoader::user_load_tokens;
SelfList<Resource>::List ResourceLoader::remapped_list;
HashMap<String, Vector<String>> ResourceLoader::translation_remaps;
diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h
index 72c1f90653..592befb603 100644
--- a/core/io/resource_loader.h
+++ b/core/io/resource_loader.h
@@ -34,6 +34,7 @@
#include "core/io/resource.h"
#include "core/object/gdvirtual.gen.inc"
#include "core/object/script_language.h"
+#include "core/object/worker_thread_pool.h"
#include "core/os/semaphore.h"
#include "core/os/thread.h"
@@ -88,8 +89,8 @@ public:
VARIANT_ENUM_CAST(ResourceFormatLoader::CacheMode)
-typedef void (*ResourceLoadErrorNotify)(void *p_ud, const String &p_text);
-typedef void (*DependencyErrorNotify)(void *p_ud, const String &p_loading, const String &p_which, const String &p_type);
+typedef void (*ResourceLoadErrorNotify)(const String &p_text);
+typedef void (*DependencyErrorNotify)(const String &p_loading, const String &p_which, const String &p_type);
typedef Error (*ResourceLoaderImport)(const String &p_path);
typedef void (*ResourceLoadedCallback)(Ref<Resource> p_resource, const String &p_path);
@@ -107,9 +108,30 @@ public:
THREAD_LOAD_LOADED
};
+ enum LoadThreadMode {
+ LOAD_THREAD_FROM_CURRENT,
+ LOAD_THREAD_SPAWN_SINGLE,
+ LOAD_THREAD_DISTRIBUTE,
+ };
+
+ struct LoadToken : public RefCounted {
+ String local_path;
+ String user_path;
+ Ref<Resource> res_if_unregistered;
+
+ void clear();
+
+ virtual ~LoadToken();
+ };
+
static const int BINARY_MUTEX_TAG = 1;
+ static Ref<LoadToken> _load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode);
+ static Ref<Resource> _load_complete(LoadToken &p_load_token, Error *r_error);
+
private:
+ static Ref<Resource> _load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock);
+
static Ref<ResourceFormatLoader> loader[MAX_LOADERS];
static int loader_count;
static bool timestamp_on_load;
@@ -129,8 +151,7 @@ private:
static SelfList<Resource>::List remapped_list;
friend class ResourceFormatImporter;
- friend class ResourceInteractiveLoader;
- // Internal load function.
+
static Ref<Resource> _load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress);
static ResourceLoadedCallback _loaded_callback;
@@ -138,11 +159,14 @@ private:
static Ref<ResourceFormatLoader> _find_custom_resource_format_loader(String path);
struct ThreadLoadTask {
- Thread *thread = nullptr;
- Thread::ID loader_id = 0;
- ConditionVariable *cond_var = nullptr;
+ WorkerThreadPool::TaskID task_id = 0; // Used if run on a worker thread from the pool.
+ Thread::ID thread_id = 0; // Used if running on an user thread (e.g., simple non-threaded load).
+ bool awaited = false; // If it's in the pool, this helps not awaiting from more than one dependent thread.
+ ConditionVariable *cond_var = nullptr; // In not in the worker pool or already awaiting, this is used as a secondary awaiting mechanism.
+ LoadToken *load_token = nullptr;
String local_path;
String remapped_path;
+ String dependent_path;
String type_hint;
float progress = 0.0;
ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS;
@@ -151,27 +175,29 @@ private:
Ref<Resource> resource;
bool xl_remapped = false;
bool use_sub_threads = false;
- bool start_next = true;
- int requests = 0;
HashSet<String> sub_tasks;
};
static void _thread_load_function(void *p_userdata);
- static SafeBinaryMutex<BINARY_MUTEX_TAG> *thread_load_mutex;
+
+ static thread_local int load_nesting;
+ static thread_local WorkerThreadPool::TaskID caller_task_id;
+ static thread_local Vector<String> load_paths_stack;
+ static SafeBinaryMutex<BINARY_MUTEX_TAG> thread_load_mutex;
static HashMap<String, ThreadLoadTask> thread_load_tasks;
- static Semaphore *thread_load_semaphore;
- static int thread_waiting_count;
- static int thread_loading_count;
- static int thread_suspended_count;
- static int thread_load_max;
+ static bool cleaning_tasks;
+
+ static HashMap<String, LoadToken *> user_load_tokens;
static float _dependency_get_progress(const String &p_path);
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, const String &p_source_resource = String());
+ 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);
static Ref<Resource> load_threaded_get(const String &p_path, Error *r_error = nullptr);
+ static bool is_within_load() { return load_nesting > 0; };
+
static Ref<Resource> load(const String &p_path, const String &p_type_hint = "", ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE, Error *r_error = nullptr);
static bool exists(const String &p_path, const String &p_type_hint = "");
@@ -192,24 +218,24 @@ public:
static void set_timestamp_on_load(bool p_timestamp) { timestamp_on_load = p_timestamp; }
static bool get_timestamp_on_load() { return timestamp_on_load; }
+ // Loaders can safely use this regardless which thread they are running on.
static void notify_load_error(const String &p_err) {
if (err_notify) {
- err_notify(err_notify_ud, p_err);
+ callable_mp_static(err_notify).bind(p_err).call_deferred();
}
}
- static void set_error_notify_func(void *p_ud, ResourceLoadErrorNotify p_err_notify) {
+ static void set_error_notify_func(ResourceLoadErrorNotify p_err_notify) {
err_notify = p_err_notify;
- err_notify_ud = p_ud;
}
+ // Loaders can safely use this regardless which thread they are running on.
static void notify_dependency_error(const String &p_path, const String &p_dependency, const String &p_type) {
if (dep_err_notify) {
- dep_err_notify(dep_err_notify_ud, p_path, p_dependency, p_type);
+ callable_mp_static(dep_err_notify).bind(p_path, p_dependency, p_type).call_deferred();
}
}
- static void set_dependency_error_notify_func(void *p_ud, DependencyErrorNotify p_err_notify) {
+ static void set_dependency_error_notify_func(DependencyErrorNotify p_err_notify) {
dep_err_notify = p_err_notify;
- dep_err_notify_ud = p_ud;
}
static void set_abort_on_missing_resources(bool p_abort) { abort_on_missing_resource = p_abort; }
@@ -237,6 +263,8 @@ 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 bool is_cleaning_tasks();
+
static void initialize();
static void finalize();
};
diff --git a/core/io/xml_parser.cpp b/core/io/xml_parser.cpp
index 5c0a017bfc..958734addf 100644
--- a/core/io/xml_parser.cpp
+++ b/core/io/xml_parser.cpp
@@ -34,8 +34,6 @@
//#define DEBUG_XML
-VARIANT_ENUM_CAST(XMLParser::NodeType);
-
static inline bool _is_white_space(char c) {
return (c == ' ' || c == '\t' || c == '\n' || c == '\r');
}
diff --git a/core/io/xml_parser.h b/core/io/xml_parser.h
index b96478c7a5..77df99a881 100644
--- a/core/io/xml_parser.h
+++ b/core/io/xml_parser.h
@@ -126,4 +126,6 @@ public:
~XMLParser();
};
+VARIANT_ENUM_CAST(XMLParser::NodeType);
+
#endif // XML_PARSER_H
diff --git a/core/io/zip_io.cpp b/core/io/zip_io.cpp
index 7f60039578..a0e6bd62de 100644
--- a/core/io/zip_io.cpp
+++ b/core/io/zip_io.cpp
@@ -30,6 +30,48 @@
#include "zip_io.h"
+#include "core/templates/local_vector.h"
+
+int godot_unzip_get_current_file_info(unzFile p_zip_file, unz_file_info64 &r_file_info, String &r_filepath) {
+ const uLong short_file_path_buffer_size = 16384ul;
+ char short_file_path_buffer[short_file_path_buffer_size];
+
+ int err = unzGetCurrentFileInfo64(p_zip_file, &r_file_info, short_file_path_buffer, short_file_path_buffer_size, nullptr, 0, nullptr, 0);
+ if (unlikely((err != UNZ_OK) || (r_file_info.size_filename > short_file_path_buffer_size))) {
+ LocalVector<char> long_file_path_buffer;
+ long_file_path_buffer.resize(r_file_info.size_filename);
+
+ err = unzGetCurrentFileInfo64(p_zip_file, &r_file_info, long_file_path_buffer.ptr(), long_file_path_buffer.size(), nullptr, 0, nullptr, 0);
+ if (err != UNZ_OK) {
+ return err;
+ }
+ r_filepath = String::utf8(long_file_path_buffer.ptr(), r_file_info.size_filename);
+ } else {
+ r_filepath = String::utf8(short_file_path_buffer, r_file_info.size_filename);
+ }
+
+ return err;
+}
+
+int godot_unzip_locate_file(unzFile p_zip_file, String p_filepath, bool p_case_sensitive) {
+ int err = unzGoToFirstFile(p_zip_file);
+ while (err == UNZ_OK) {
+ unz_file_info64 current_file_info;
+ String current_filepath;
+ err = godot_unzip_get_current_file_info(p_zip_file, current_file_info, current_filepath);
+ if (err == UNZ_OK) {
+ bool filepaths_are_equal = p_case_sensitive ? (p_filepath == current_filepath) : (p_filepath.nocasecmp_to(current_filepath) == 0);
+ if (filepaths_are_equal) {
+ return UNZ_OK;
+ }
+ err = unzGoToNextFile(p_zip_file);
+ }
+ }
+ return err;
+}
+
+//
+
void *zipio_open(voidpf opaque, const char *p_fname, int mode) {
Ref<FileAccess> *fa = reinterpret_cast<Ref<FileAccess> *>(opaque);
ERR_FAIL_COND_V(fa == nullptr, nullptr);
@@ -38,17 +80,17 @@ void *zipio_open(voidpf opaque, const char *p_fname, int mode) {
fname.parse_utf8(p_fname);
int file_access_mode = 0;
- if (mode & ZLIB_FILEFUNC_MODE_WRITE) {
- file_access_mode |= FileAccess::WRITE;
- }
if (mode & ZLIB_FILEFUNC_MODE_READ) {
file_access_mode |= FileAccess::READ;
}
+ if (mode & ZLIB_FILEFUNC_MODE_WRITE) {
+ file_access_mode |= FileAccess::WRITE;
+ }
if (mode & ZLIB_FILEFUNC_MODE_CREATE) {
file_access_mode |= FileAccess::WRITE_READ;
}
- (*fa) = FileAccess::open(fname, file_access_mode);
+ (*fa) = FileAccess::open(fname, file_access_mode);
if (fa->is_null()) {
return nullptr;
}
diff --git a/core/io/zip_io.h b/core/io/zip_io.h
index 094d490bcf..c59b981373 100644
--- a/core/io/zip_io.h
+++ b/core/io/zip_io.h
@@ -39,6 +39,13 @@
#include "thirdparty/minizip/unzip.h"
#include "thirdparty/minizip/zip.h"
+// Get the current file info and safely convert the full filepath to a String.
+int godot_unzip_get_current_file_info(unzFile p_zip_file, unz_file_info64 &r_file_info, String &r_filepath);
+// Try to locate the file in the archive specified by the filepath (works with large paths and Unicode).
+int godot_unzip_locate_file(unzFile p_zip_file, String p_filepath, bool p_case_sensitive = true);
+
+//
+
void *zipio_open(voidpf opaque, const char *p_fname, int mode);
uLong zipio_read(voidpf opaque, voidpf stream, void *buf, uLong size);
uLong zipio_write(voidpf opaque, voidpf stream, const void *buf, uLong size);
diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp
index 139dc3afb1..63f7c80bdd 100644
--- a/core/math/a_star_grid_2d.cpp
+++ b/core/math/a_star_grid_2d.cpp
@@ -32,6 +32,8 @@
#include "core/variant/typed_array.h"
+#define GET_POINT_UNCHECKED(m_id) points[m_id.y - region.position.y][m_id.x - region.position.x]
+
static real_t heuristic_euclidian(const Vector2i &p_from, const Vector2i &p_to) {
real_t dx = (real_t)ABS(p_to.x - p_from.x);
real_t dy = (real_t)ABS(p_to.y - p_from.y);
@@ -59,16 +61,29 @@ static real_t heuristic_chebyshev(const Vector2i &p_from, const Vector2i &p_to)
static real_t (*heuristics[AStarGrid2D::HEURISTIC_MAX])(const Vector2i &, const Vector2i &) = { heuristic_euclidian, heuristic_manhattan, heuristic_octile, heuristic_chebyshev };
+void AStarGrid2D::set_region(const Rect2i &p_region) {
+ ERR_FAIL_COND(p_region.size.x < 0 || p_region.size.y < 0);
+ if (p_region != region) {
+ region = p_region;
+ dirty = true;
+ }
+}
+
+Rect2i AStarGrid2D::get_region() const {
+ return region;
+}
+
void AStarGrid2D::set_size(const Size2i &p_size) {
+ WARN_DEPRECATED_MSG(R"(The "size" property is deprecated, use "region" instead.)");
ERR_FAIL_COND(p_size.x < 0 || p_size.y < 0);
- if (p_size != size) {
- size = p_size;
+ if (p_size != region.size) {
+ region.size = p_size;
dirty = true;
}
}
Size2i AStarGrid2D::get_size() const {
- return size;
+ return region.size;
}
void AStarGrid2D::set_offset(const Vector2 &p_offset) {
@@ -95,9 +110,11 @@ Size2 AStarGrid2D::get_cell_size() const {
void AStarGrid2D::update() {
points.clear();
- for (int64_t y = 0; y < size.y; y++) {
+ const int64_t end_x = region.position.x + region.size.width;
+ const int64_t end_y = region.position.y + region.size.height;
+ for (int64_t y = region.position.y; y < end_y; y++) {
LocalVector<Point> line;
- for (int64_t x = 0; x < size.x; x++) {
+ for (int64_t x = region.position.x; x < end_x; x++) {
line.push_back(Point(Vector2i(x, y), offset + Vector2(x, y) * cell_size));
}
points.push_back(line);
@@ -106,11 +123,11 @@ void AStarGrid2D::update() {
}
bool AStarGrid2D::is_in_bounds(int p_x, int p_y) const {
- return p_x >= 0 && p_x < size.width && p_y >= 0 && p_y < size.height;
+ return region.has_point(Vector2i(p_x, p_y));
}
bool AStarGrid2D::is_in_boundsv(const Vector2i &p_id) const {
- return p_id.x >= 0 && p_id.x < size.width && p_id.y >= 0 && p_id.y < size.height;
+ return region.has_point(p_id);
}
bool AStarGrid2D::is_dirty() const {
@@ -154,27 +171,27 @@ AStarGrid2D::Heuristic AStarGrid2D::get_default_estimate_heuristic() const {
void AStarGrid2D::set_point_solid(const Vector2i &p_id, bool p_solid) {
ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set if point is disabled. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
- points[p_id.y][p_id.x].solid = p_solid;
+ ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set if point is disabled. Point %s out of bounds %s.", p_id, region));
+ GET_POINT_UNCHECKED(p_id).solid = p_solid;
}
bool AStarGrid2D::is_point_solid(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, false, "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), false, vformat("Can't get if point is disabled. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
- return points[p_id.y][p_id.x].solid;
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), false, vformat("Can't get if point is disabled. Point %s out of bounds %s.", p_id, region));
+ return GET_POINT_UNCHECKED(p_id).solid;
}
void AStarGrid2D::set_point_weight_scale(const Vector2i &p_id, real_t p_weight_scale) {
ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set point's weight scale. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
+ ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set point's weight scale. Point %s out of bounds %s.", p_id, region));
ERR_FAIL_COND_MSG(p_weight_scale < 0.0, vformat("Can't set point's weight scale less than 0.0: %f.", p_weight_scale));
- points[p_id.y][p_id.x].weight_scale = p_weight_scale;
+ GET_POINT_UNCHECKED(p_id).weight_scale = p_weight_scale;
}
real_t AStarGrid2D::get_point_weight_scale(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, 0, "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), 0, vformat("Can't get point's weight scale. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
- return points[p_id.y][p_id.x].weight_scale;
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), 0, vformat("Can't get point's weight scale. Point %s out of bounds %s.", p_id, region));
+ return GET_POINT_UNCHECKED(p_id).weight_scale;
}
AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) {
@@ -285,15 +302,15 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) {
bool has_left = false;
bool has_right = false;
- if (p_point->id.x - 1 >= 0) {
+ if (p_point->id.x - 1 >= region.position.x) {
left = _get_point_unchecked(p_point->id.x - 1, p_point->id.y);
has_left = true;
}
- if (p_point->id.x + 1 < size.width) {
+ if (p_point->id.x + 1 < region.position.x + region.size.width) {
right = _get_point_unchecked(p_point->id.x + 1, p_point->id.y);
has_right = true;
}
- if (p_point->id.y - 1 >= 0) {
+ if (p_point->id.y - 1 >= region.position.y) {
top = _get_point_unchecked(p_point->id.x, p_point->id.y - 1);
if (has_left) {
top_left = _get_point_unchecked(p_point->id.x - 1, p_point->id.y - 1);
@@ -302,7 +319,7 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) {
top_right = _get_point_unchecked(p_point->id.x + 1, p_point->id.y - 1);
}
}
- if (p_point->id.y + 1 < size.height) {
+ if (p_point->id.y + 1 < region.position.y + region.size.height) {
bottom = _get_point_unchecked(p_point->id.x, p_point->id.y + 1);
if (has_left) {
bottom_left = _get_point_unchecked(p_point->id.x - 1, p_point->id.y + 1);
@@ -461,19 +478,19 @@ real_t AStarGrid2D::_compute_cost(const Vector2i &p_from_id, const Vector2i &p_t
void AStarGrid2D::clear() {
points.clear();
- size = Vector2i();
+ region = Rect2i();
}
Vector2 AStarGrid2D::get_point_position(const Vector2i &p_id) const {
ERR_FAIL_COND_V_MSG(dirty, Vector2(), "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), Vector2(), vformat("Can't get point's position. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height));
- return points[p_id.y][p_id.x].pos;
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), Vector2(), vformat("Can't get point's position. Point %s out of bounds %s.", p_id, region));
+ return GET_POINT_UNCHECKED(p_id).pos;
}
Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vector2i &p_to_id) {
ERR_FAIL_COND_V_MSG(dirty, Vector<Vector2>(), "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height));
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height));
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point %s out of bounds %s.", p_from_id, region));
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), Vector<Vector2>(), vformat("Can't get id path. Point %s out of bounds %s.", p_to_id, region));
Point *a = _get_point(p_from_id.x, p_from_id.y);
Point *b = _get_point(p_to_id.x, p_to_id.y);
@@ -520,8 +537,8 @@ Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec
TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector2i &p_to_id) {
ERR_FAIL_COND_V_MSG(dirty, TypedArray<Vector2i>(), "Grid is not initialized. Call the update method.");
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height));
- ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height));
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point %s out of bounds %s.", p_from_id, region));
+ ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), TypedArray<Vector2i>(), vformat("Can't get id path. Point %s out of bounds %s.", p_to_id, region));
Point *a = _get_point(p_from_id.x, p_from_id.y);
Point *b = _get_point(p_to_id.x, p_to_id.y);
@@ -565,6 +582,8 @@ TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const V
}
void AStarGrid2D::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_region", "region"), &AStarGrid2D::set_region);
+ ClassDB::bind_method(D_METHOD("get_region"), &AStarGrid2D::get_region);
ClassDB::bind_method(D_METHOD("set_size", "size"), &AStarGrid2D::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &AStarGrid2D::get_size);
ClassDB::bind_method(D_METHOD("set_offset", "offset"), &AStarGrid2D::set_offset);
@@ -596,6 +615,7 @@ void AStarGrid2D::_bind_methods() {
GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id")
GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id")
+ ADD_PROPERTY(PropertyInfo(Variant::RECT2I, "region"), "set_region", "get_region");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cell_size"), "set_cell_size", "get_cell_size");
@@ -617,3 +637,5 @@ void AStarGrid2D::_bind_methods() {
BIND_ENUM_CONSTANT(DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES);
BIND_ENUM_CONSTANT(DIAGONAL_MODE_MAX);
}
+
+#undef GET_POINT_UNCHECKED
diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h
index e4e62ec360..50df58e0e9 100644
--- a/core/math/a_star_grid_2d.h
+++ b/core/math/a_star_grid_2d.h
@@ -58,7 +58,7 @@ public:
};
private:
- Size2i size;
+ Rect2i region;
Vector2 offset;
Size2 cell_size = Size2(1, 1);
bool dirty = false;
@@ -107,21 +107,21 @@ private:
private: // Internal routines.
_FORCE_INLINE_ bool _is_walkable(int64_t p_x, int64_t p_y) const {
- if (p_x >= 0 && p_y >= 0 && p_x < size.width && p_y < size.height) {
- return !points[p_y][p_x].solid;
+ if (region.has_point(Vector2i(p_x, p_y))) {
+ return !points[p_y - region.position.y][p_x - region.position.x].solid;
}
return false;
}
_FORCE_INLINE_ Point *_get_point(int64_t p_x, int64_t p_y) {
- if (p_x >= 0 && p_y >= 0 && p_x < size.width && p_y < size.height) {
- return &points[p_y][p_x];
+ if (region.has_point(Vector2i(p_x, p_y))) {
+ return &points[p_y - region.position.y][p_x - region.position.x];
}
return nullptr;
}
_FORCE_INLINE_ Point *_get_point_unchecked(int64_t p_x, int64_t p_y) {
- return &points[p_y][p_x];
+ return &points[p_y - region.position.y][p_x - region.position.x];
}
void _get_nbors(Point *p_point, LocalVector<Point *> &r_nbors);
@@ -138,6 +138,9 @@ protected:
GDVIRTUAL2RC(real_t, _compute_cost, Vector2i, Vector2i)
public:
+ void set_region(const Rect2i &p_region);
+ Rect2i get_region() const;
+
void set_size(const Size2i &p_size);
Size2i get_size() const;
diff --git a/core/math/basis.cpp b/core/math/basis.cpp
index 95a4187062..6b0ecadc7f 100644
--- a/core/math/basis.cpp
+++ b/core/math/basis.cpp
@@ -397,7 +397,7 @@ void Basis::rotate_to_align(Vector3 p_start_direction, Vector3 p_end_direction)
real_t dot = p_start_direction.dot(p_end_direction);
dot = CLAMP(dot, -1.0f, 1.0f);
const real_t angle_rads = Math::acos(dot);
- set_axis_angle(axis, angle_rads);
+ *this = Basis(axis, angle_rads) * (*this);
}
}
@@ -807,8 +807,8 @@ void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const {
z = (rows[1][0] - rows[0][1]) / s;
r_axis = Vector3(x, y, z);
- // CLAMP to avoid NaN if the value passed to acos is not in [0,1].
- r_angle = Math::acos(CLAMP((rows[0][0] + rows[1][1] + rows[2][2] - 1) / 2, (real_t)0.0, (real_t)1.0));
+ // acos does clamping.
+ r_angle = Math::acos((rows[0][0] + rows[1][1] + rows[2][2] - 1) / 2);
}
void Basis::set_quaternion(const Quaternion &p_quaternion) {
@@ -1016,12 +1016,15 @@ void Basis::rotate_sh(real_t *p_values) {
p_values[8] = d4 * s_scale_dst4;
}
-Basis Basis::looking_at(const Vector3 &p_target, const Vector3 &p_up) {
+Basis Basis::looking_at(const Vector3 &p_target, const Vector3 &p_up, bool p_use_model_front) {
#ifdef MATH_CHECKS
ERR_FAIL_COND_V_MSG(p_target.is_zero_approx(), Basis(), "The target vector can't be zero.");
ERR_FAIL_COND_V_MSG(p_up.is_zero_approx(), Basis(), "The up vector can't be zero.");
#endif
- Vector3 v_z = -p_target.normalized();
+ Vector3 v_z = p_target.normalized();
+ if (!p_use_model_front) {
+ v_z = -v_z;
+ }
Vector3 v_x = p_up.cross(v_z);
#ifdef MATH_CHECKS
ERR_FAIL_COND_V_MSG(v_x.is_zero_approx(), Basis(), "The target vector and up vector can't be parallel to each other.");
diff --git a/core/math/basis.h b/core/math/basis.h
index bbc1d40469..1a68bee686 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -217,7 +217,7 @@ struct _NO_DISCARD_ Basis {
operator Quaternion() const { return get_quaternion(); }
- static Basis looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0));
+ static Basis looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false);
Basis(const Quaternion &p_quaternion) { set_quaternion(p_quaternion); };
Basis(const Quaternion &p_quaternion, const Vector3 &p_scale) { set_quaternion_scale(p_quaternion, p_scale); }
diff --git a/core/math/bvh_debug.inc b/core/math/bvh_debug.inc
index 2e519ceb3d..1964f2fa83 100644
--- a/core/math/bvh_debug.inc
+++ b/core/math/bvh_debug.inc
@@ -30,11 +30,7 @@ String _debug_aabb_to_string(const BVHABB_CLASS &aabb) const {
void _debug_recursive_print_tree_node(uint32_t p_node_id, int depth = 0) const {
const TNode &tnode = _nodes[p_node_id];
- String sz = "";
- for (int n = 0; n < depth; n++) {
- sz += "\t";
- }
- sz += itos(p_node_id);
+ String sz = String("\t").repeat(depth) + itos(p_node_id);
if (tnode.is_leaf()) {
sz += " L";
diff --git a/core/math/color.cpp b/core/math/color.cpp
index 3e5fa7b402..d36306d968 100644
--- a/core/math/color.cpp
+++ b/core/math/color.cpp
@@ -247,8 +247,7 @@ void Color::set_ok_hsl(float p_h, float p_s, float p_l, float p_alpha) {
hsl.h = p_h;
hsl.s = p_s;
hsl.l = p_l;
- ok_color new_ok_color;
- ok_color::RGB rgb = new_ok_color.okhsl_to_srgb(hsl);
+ ok_color::RGB rgb = ok_color::okhsl_to_srgb(hsl);
Color c = Color(rgb.r, rgb.g, rgb.b, p_alpha).clamp();
r = c.r;
g = c.g;
@@ -401,7 +400,6 @@ Color Color::named(const String &p_name) {
int idx = find_named_color(p_name);
if (idx == -1) {
ERR_FAIL_V_MSG(Color(), "Invalid color name: " + p_name + ".");
- return Color();
}
return named_colors[idx].color;
}
@@ -596,8 +594,7 @@ float Color::get_ok_hsl_h() const {
rgb.r = r;
rgb.g = g;
rgb.b = b;
- ok_color new_ok_color;
- ok_color::HSL ok_hsl = new_ok_color.srgb_to_okhsl(rgb);
+ ok_color::HSL ok_hsl = ok_color::srgb_to_okhsl(rgb);
if (Math::is_nan(ok_hsl.h)) {
return 0.0f;
}
@@ -609,8 +606,7 @@ float Color::get_ok_hsl_s() const {
rgb.r = r;
rgb.g = g;
rgb.b = b;
- ok_color new_ok_color;
- ok_color::HSL ok_hsl = new_ok_color.srgb_to_okhsl(rgb);
+ ok_color::HSL ok_hsl = ok_color::srgb_to_okhsl(rgb);
if (Math::is_nan(ok_hsl.s)) {
return 0.0f;
}
@@ -622,8 +618,7 @@ float Color::get_ok_hsl_l() const {
rgb.r = r;
rgb.g = g;
rgb.b = b;
- ok_color new_ok_color;
- ok_color::HSL ok_hsl = new_ok_color.srgb_to_okhsl(rgb);
+ ok_color::HSL ok_hsl = ok_color::srgb_to_okhsl(rgb);
if (Math::is_nan(ok_hsl.l)) {
return 0.0f;
}
diff --git a/core/math/convex_hull.cpp b/core/math/convex_hull.cpp
index a03438a339..f8456ec998 100644
--- a/core/math/convex_hull.cpp
+++ b/core/math/convex_hull.cpp
@@ -596,9 +596,9 @@ private:
}
};
- enum Orientation { NONE,
- CLOCKWISE,
- COUNTER_CLOCKWISE };
+ enum Orientation { ORIENTATION_NONE,
+ ORIENTATION_CLOCKWISE,
+ ORIENTATION_COUNTER_CLOCKWISE };
Vector3 scaling;
Vector3 center;
@@ -658,7 +658,7 @@ private:
Vector3 get_gd_normal(Face *p_face);
- bool shift_face(Face *p_face, real_t p_amount, LocalVector<Vertex *> p_stack);
+ bool shift_face(Face *p_face, real_t p_amount, LocalVector<Vertex *> &p_stack);
public:
~ConvexHullInternal() {
@@ -1140,13 +1140,13 @@ ConvexHullInternal::Orientation ConvexHullInternal::get_orientation(const Edge *
CHULL_ASSERT(!m.is_zero());
int64_t dot = n.dot(m);
CHULL_ASSERT(dot != 0);
- return (dot > 0) ? COUNTER_CLOCKWISE : CLOCKWISE;
+ return (dot > 0) ? ORIENTATION_COUNTER_CLOCKWISE : ORIENTATION_CLOCKWISE;
}
- return COUNTER_CLOCKWISE;
+ return ORIENTATION_COUNTER_CLOCKWISE;
} else if (p_prev->prev == p_next) {
- return CLOCKWISE;
+ return ORIENTATION_CLOCKWISE;
} else {
- return NONE;
+ return ORIENTATION_NONE;
}
}
@@ -1176,7 +1176,7 @@ ConvexHullInternal::Edge *ConvexHullInternal::find_max_angle(bool p_ccw, const V
} else if ((cmp = cot.compare(p_min_cot)) < 0) {
p_min_cot = cot;
min_edge = e;
- } else if ((cmp == 0) && (p_ccw == (get_orientation(min_edge, e, p_s, t) == COUNTER_CLOCKWISE))) {
+ } else if ((cmp == 0) && (p_ccw == (get_orientation(min_edge, e, p_s, t) == ORIENTATION_COUNTER_CLOCKWISE))) {
min_edge = e;
}
}
@@ -1375,7 +1375,7 @@ void ConvexHullInternal::merge(IntermediateHull &p_h0, IntermediateHull &p_h1) {
int64_t dot = (*e->target - *c0).dot(normal);
CHULL_ASSERT(dot <= 0);
if ((dot == 0) && ((*e->target - *c0).dot(t) > 0)) {
- if (!start0 || (get_orientation(start0, e, s, Point32(0, 0, -1)) == CLOCKWISE)) {
+ if (!start0 || (get_orientation(start0, e, s, Point32(0, 0, -1)) == ORIENTATION_CLOCKWISE)) {
start0 = e;
}
}
@@ -1390,7 +1390,7 @@ void ConvexHullInternal::merge(IntermediateHull &p_h0, IntermediateHull &p_h1) {
int64_t dot = (*e->target - *c1).dot(normal);
CHULL_ASSERT(dot <= 0);
if ((dot == 0) && ((*e->target - *c1).dot(t) > 0)) {
- if (!start1 || (get_orientation(start1, e, s, Point32(0, 0, -1)) == COUNTER_CLOCKWISE)) {
+ if (!start1 || (get_orientation(start1, e, s, Point32(0, 0, -1)) == ORIENTATION_COUNTER_CLOCKWISE)) {
start1 = e;
}
}
@@ -1775,7 +1775,7 @@ real_t ConvexHullInternal::shrink(real_t p_amount, real_t p_clamp_amount) {
return p_amount;
}
-bool ConvexHullInternal::shift_face(Face *p_face, real_t p_amount, LocalVector<Vertex *> p_stack) {
+bool ConvexHullInternal::shift_face(Face *p_face, real_t p_amount, LocalVector<Vertex *> &p_stack) {
Vector3 orig_shift = get_gd_normal(p_face) * -p_amount;
if (scaling[0] != 0) {
orig_shift[0] /= scaling[0];
diff --git a/core/math/delaunay_2d.h b/core/math/delaunay_2d.h
index 8d602da241..fc70724308 100644
--- a/core/math/delaunay_2d.h
+++ b/core/math/delaunay_2d.h
@@ -38,7 +38,8 @@ class Delaunay2D {
public:
struct Triangle {
int points[3];
- bool bad = false;
+ Vector2 circum_center;
+ real_t circum_radius_squared;
Triangle() {}
Triangle(int p_a, int p_b, int p_c) {
points[0] = p_a;
@@ -48,117 +49,109 @@ public:
};
struct Edge {
- int edge[2];
+ int points[2];
bool bad = false;
Edge() {}
Edge(int p_a, int p_b) {
- edge[0] = p_a;
- edge[1] = p_b;
+ // Store indices in a sorted manner to avoid having to check both orientations later.
+ if (p_a > p_b) {
+ points[0] = p_b;
+ points[1] = p_a;
+ } else {
+ points[0] = p_a;
+ points[1] = p_b;
+ }
}
};
- static bool circum_circle_contains(const Vector<Vector2> &p_vertices, const Triangle &p_triangle, int p_vertex) {
- Vector2 p1 = p_vertices[p_triangle.points[0]];
- Vector2 p2 = p_vertices[p_triangle.points[1]];
- Vector2 p3 = p_vertices[p_triangle.points[2]];
+ static Triangle create_triangle(const Vector<Vector2> &p_vertices, const int &p_a, const int &p_b, const int &p_c) {
+ Triangle triangle = Triangle(p_a, p_b, p_c);
- real_t ab = p1.x * p1.x + p1.y * p1.y;
- real_t cd = p2.x * p2.x + p2.y * p2.y;
- real_t ef = p3.x * p3.x + p3.y * p3.y;
+ // Get the values of the circumcircle and store them inside the triangle object.
+ Vector2 a = p_vertices[p_b] - p_vertices[p_a];
+ Vector2 b = p_vertices[p_c] - p_vertices[p_a];
- Vector2 circum(
- (ab * (p3.y - p2.y) + cd * (p1.y - p3.y) + ef * (p2.y - p1.y)) / (p1.x * (p3.y - p2.y) + p2.x * (p1.y - p3.y) + p3.x * (p2.y - p1.y)),
- (ab * (p3.x - p2.x) + cd * (p1.x - p3.x) + ef * (p2.x - p1.x)) / (p1.y * (p3.x - p2.x) + p2.y * (p1.x - p3.x) + p3.y * (p2.x - p1.x)));
+ Vector2 O = (b * a.length_squared() - a * b.length_squared()).orthogonal() / (a.cross(b) * 2.0f);
- circum *= 0.5;
- float r = p1.distance_squared_to(circum);
- float d = p_vertices[p_vertex].distance_squared_to(circum);
- return d <= r;
- }
+ triangle.circum_radius_squared = O.length_squared();
+ triangle.circum_center = O + p_vertices[p_a];
- static bool edge_compare(const Vector<Vector2> &p_vertices, const Edge &p_a, const Edge &p_b) {
- if (p_vertices[p_a.edge[0]].is_equal_approx(p_vertices[p_b.edge[0]]) && p_vertices[p_a.edge[1]].is_equal_approx(p_vertices[p_b.edge[1]])) {
- return true;
- }
-
- if (p_vertices[p_a.edge[0]].is_equal_approx(p_vertices[p_b.edge[1]]) && p_vertices[p_a.edge[1]].is_equal_approx(p_vertices[p_b.edge[0]])) {
- return true;
- }
-
- return false;
+ return triangle;
}
static Vector<Triangle> triangulate(const Vector<Vector2> &p_points) {
Vector<Vector2> points = p_points;
Vector<Triangle> triangles;
- Rect2 rect;
- for (int i = 0; i < p_points.size(); i++) {
- if (i == 0) {
- rect.position = p_points[i];
- } else {
- rect.expand_to(p_points[i]);
- }
+ int point_count = p_points.size();
+ if (point_count <= 2) {
+ return triangles;
}
- float delta_max = MAX(rect.size.width, rect.size.height);
+ // Get a bounding rectangle.
+ Rect2 rect = Rect2(p_points[0], Size2());
+ for (int i = 1; i < point_count; i++) {
+ rect.expand_to(p_points[i]);
+ }
+
+ real_t delta_max = MAX(rect.size.width, rect.size.height);
Vector2 center = rect.get_center();
- points.push_back(Vector2(center.x - 20 * delta_max, center.y - delta_max));
- points.push_back(Vector2(center.x, center.y + 20 * delta_max));
- points.push_back(Vector2(center.x + 20 * delta_max, center.y - delta_max));
+ // Construct a bounding triangle around the rectangle.
+ points.push_back(Vector2(center.x - delta_max * 16, center.y - delta_max));
+ points.push_back(Vector2(center.x, center.y + delta_max * 16));
+ points.push_back(Vector2(center.x + delta_max * 16, center.y - delta_max));
- triangles.push_back(Triangle(p_points.size() + 0, p_points.size() + 1, p_points.size() + 2));
+ Triangle bounding_triangle = create_triangle(points, point_count + 0, point_count + 1, point_count + 2);
+ triangles.push_back(bounding_triangle);
- for (int i = 0; i < p_points.size(); i++) {
+ for (int i = 0; i < point_count; i++) {
Vector<Edge> polygon;
- for (int j = 0; j < triangles.size(); j++) {
- if (circum_circle_contains(points, triangles[j], i)) {
- triangles.write[j].bad = true;
+ // Save the edges of the triangles whose circumcircles contain the i-th vertex. Delete the triangles themselves.
+ for (int j = triangles.size() - 1; j >= 0; j--) {
+ if (points[i].distance_squared_to(triangles[j].circum_center) < triangles[j].circum_radius_squared) {
polygon.push_back(Edge(triangles[j].points[0], triangles[j].points[1]));
polygon.push_back(Edge(triangles[j].points[1], triangles[j].points[2]));
polygon.push_back(Edge(triangles[j].points[2], triangles[j].points[0]));
- }
- }
- for (int j = 0; j < triangles.size(); j++) {
- if (triangles[j].bad) {
triangles.remove_at(j);
- j--;
}
}
+ // Create a triangle for every unique edge.
for (int j = 0; j < polygon.size(); j++) {
+ if (polygon[j].bad) {
+ continue;
+ }
+
for (int k = j + 1; k < polygon.size(); k++) {
- if (edge_compare(points, polygon[j], polygon[k])) {
+ // Compare the edges.
+ if (polygon[k].points[0] == polygon[j].points[0] && polygon[k].points[1] == polygon[j].points[1]) {
polygon.write[j].bad = true;
polygon.write[k].bad = true;
+
+ break; // Since no more than two triangles can share an edge, no more than two edges can share vertices.
}
}
- }
- for (int j = 0; j < polygon.size(); j++) {
- if (polygon[j].bad) {
- continue;
+ // Create triangles out of good edges.
+ if (!polygon[j].bad) {
+ triangles.push_back(create_triangle(points, polygon[j].points[0], polygon[j].points[1], i));
}
- triangles.push_back(Triangle(polygon[j].edge[0], polygon[j].edge[1], i));
}
}
+ // Filter out the triangles containing vertices of the bounding triangle.
+ int preserved_count = 0;
+ Triangle *triangles_ptrw = triangles.ptrw();
for (int i = 0; i < triangles.size(); i++) {
- bool invalid = false;
- for (int j = 0; j < 3; j++) {
- if (triangles[i].points[j] >= p_points.size()) {
- invalid = true;
- break;
- }
- }
- if (invalid) {
- triangles.remove_at(i);
- i--;
+ if (!(triangles[i].points[0] >= point_count || triangles[i].points[1] >= point_count || triangles[i].points[2] >= point_count)) {
+ triangles_ptrw[preserved_count] = triangles[i];
+ preserved_count++;
}
}
+ triangles.resize(preserved_count);
return triangles;
}
diff --git a/core/math/geometry_3d.cpp b/core/math/geometry_3d.cpp
index 590483bf20..9dd88485f9 100644
--- a/core/math/geometry_3d.cpp
+++ b/core/math/geometry_3d.cpp
@@ -30,7 +30,6 @@
#include "geometry_3d.h"
-#include "thirdparty/misc/clipper.hpp"
#include "thirdparty/misc/polypartition.h"
void Geometry3D::get_closest_points_between_segments(const Vector3 &p_p0, const Vector3 &p_p1, const Vector3 &p_q0, const Vector3 &p_q1, Vector3 &r_ps, Vector3 &r_qt) {
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index 078320d620..f96d3a909f 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -74,11 +74,13 @@ public:
static _ALWAYS_INLINE_ double tanh(double p_x) { return ::tanh(p_x); }
static _ALWAYS_INLINE_ float tanh(float p_x) { return ::tanhf(p_x); }
- static _ALWAYS_INLINE_ double asin(double p_x) { return ::asin(p_x); }
- static _ALWAYS_INLINE_ float asin(float p_x) { return ::asinf(p_x); }
+ // Always does clamping so always safe to use.
+ static _ALWAYS_INLINE_ double asin(double p_x) { return p_x < -1 ? (-Math_PI / 2) : (p_x > 1 ? (Math_PI / 2) : ::asin(p_x)); }
+ static _ALWAYS_INLINE_ float asin(float p_x) { return p_x < -1 ? (-Math_PI / 2) : (p_x > 1 ? (Math_PI / 2) : ::asinf(p_x)); }
- static _ALWAYS_INLINE_ double acos(double p_x) { return ::acos(p_x); }
- static _ALWAYS_INLINE_ float acos(float p_x) { return ::acosf(p_x); }
+ // Always does clamping so always safe to use.
+ static _ALWAYS_INLINE_ double acos(double p_x) { return p_x < -1 ? Math_PI : (p_x > 1 ? 0 : ::acos(p_x)); }
+ static _ALWAYS_INLINE_ float acos(float p_x) { return p_x < -1 ? Math_PI : (p_x > 1 ? 0 : ::acosf(p_x)); }
static _ALWAYS_INLINE_ double atan(double p_x) { return ::atan(p_x); }
static _ALWAYS_INLINE_ float atan(float p_x) { return ::atanf(p_x); }
diff --git a/core/math/plane.cpp b/core/math/plane.cpp
index 99694d7871..6b9bcea081 100644
--- a/core/math/plane.cpp
+++ b/core/math/plane.cpp
@@ -98,13 +98,11 @@ bool Plane::intersects_ray(const Vector3 &p_from, const Vector3 &p_dir, Vector3
Vector3 segment = p_dir;
real_t den = normal.dot(segment);
- //printf("den is %i\n",den);
if (Math::is_zero_approx(den)) {
return false;
}
real_t dist = (normal.dot(p_from) - d) / den;
- //printf("dist is %i\n",dist);
if (dist > (real_t)CMP_EPSILON) { //this is a ray, before the emitting pos (p_from) doesn't exist
@@ -121,13 +119,11 @@ bool Plane::intersects_segment(const Vector3 &p_begin, const Vector3 &p_end, Vec
Vector3 segment = p_begin - p_end;
real_t den = normal.dot(segment);
- //printf("den is %i\n",den);
if (Math::is_zero_approx(den)) {
return false;
}
real_t dist = (normal.dot(p_begin) - d) / den;
- //printf("dist is %i\n",dist);
if (dist < (real_t)-CMP_EPSILON || dist > (1.0f + (real_t)CMP_EPSILON)) {
return false;
diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp
index 34e212a5b6..e4ad17c8ef 100644
--- a/core/math/quaternion.cpp
+++ b/core/math/quaternion.cpp
@@ -35,7 +35,8 @@
real_t Quaternion::angle_to(const Quaternion &p_to) const {
real_t d = dot(p_to);
- return Math::acos(CLAMP(d * d * 2 - 1, -1, 1));
+ // acos does clamping.
+ return Math::acos(d * d * 2 - 1);
}
Vector3 Quaternion::get_euler(EulerOrder p_order) const {
diff --git a/core/math/static_raycaster.h b/core/math/static_raycaster.h
index 1bafc29c57..c53868e12d 100644
--- a/core/math/static_raycaster.h
+++ b/core/math/static_raycaster.h
@@ -59,15 +59,15 @@ public:
/*! Constructs a ray from origin, direction, and ray segment. Near
* has to be smaller than far. */
- _FORCE_INLINE_ Ray(const Vector3 &org,
- const Vector3 &dir,
- float tnear = 0.0f,
- float tfar = INFINITY) :
- org(org),
- tnear(tnear),
- dir(dir),
+ _FORCE_INLINE_ Ray(const Vector3 &p_org,
+ const Vector3 &p_dir,
+ float p_tnear = 0.0f,
+ float p_tfar = INFINITY) :
+ org(p_org),
+ tnear(p_tnear),
+ dir(p_dir),
time(0.0f),
- tfar(tfar),
+ tfar(p_tfar),
mask(-1),
u(0.0),
v(0.0),
diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp
index 868665cb5c..a0187e00b1 100644
--- a/core/math/transform_2d.cpp
+++ b/core/math/transform_2d.cpp
@@ -46,7 +46,7 @@ Transform2D Transform2D::inverse() const {
}
void Transform2D::affine_invert() {
- real_t det = basis_determinant();
+ real_t det = determinant();
#ifdef MATH_CHECKS
ERR_FAIL_COND(det == 0);
#endif
@@ -70,12 +70,12 @@ void Transform2D::rotate(const real_t p_angle) {
}
real_t Transform2D::get_skew() const {
- real_t det = basis_determinant();
+ real_t det = determinant();
return Math::acos(columns[0].normalized().dot(SIGN(det) * columns[1].normalized())) - (real_t)Math_PI * 0.5f;
}
void Transform2D::set_skew(const real_t p_angle) {
- real_t det = basis_determinant();
+ real_t det = determinant();
columns[1] = SIGN(det) * columns[0].rotated(((real_t)Math_PI * 0.5f + p_angle)).normalized() * columns[1].length();
}
@@ -113,7 +113,7 @@ Transform2D::Transform2D(const real_t p_rot, const Size2 &p_scale, const real_t
}
Size2 Transform2D::get_scale() const {
- real_t det_sign = SIGN(basis_determinant());
+ real_t det_sign = SIGN(determinant());
return Size2(columns[0].length(), det_sign * columns[1].length());
}
@@ -259,7 +259,7 @@ Transform2D Transform2D::rotated_local(const real_t p_angle) const {
return (*this) * Transform2D(p_angle, Vector2()); // Could be optimized, because origin transform can be skipped.
}
-real_t Transform2D::basis_determinant() const {
+real_t Transform2D::determinant() const {
return columns[0].x * columns[1].y - columns[0].y * columns[1].x;
}
diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h
index 4a17a9db37..c511034669 100644
--- a/core/math/transform_2d.h
+++ b/core/math/transform_2d.h
@@ -77,7 +77,7 @@ struct _NO_DISCARD_ Transform2D {
void translate_local(const real_t p_tx, const real_t p_ty);
void translate_local(const Vector2 &p_translation);
- real_t basis_determinant() const;
+ real_t determinant() const;
Size2 get_scale() const;
void set_scale(const Size2 &p_scale);
diff --git a/core/math/transform_3d.cpp b/core/math/transform_3d.cpp
index 8d497209f1..cdc94676c9 100644
--- a/core/math/transform_3d.cpp
+++ b/core/math/transform_3d.cpp
@@ -77,20 +77,20 @@ void Transform3D::rotate_basis(const Vector3 &p_axis, real_t p_angle) {
basis.rotate(p_axis, p_angle);
}
-Transform3D Transform3D::looking_at(const Vector3 &p_target, const Vector3 &p_up) const {
+Transform3D Transform3D::looking_at(const Vector3 &p_target, const Vector3 &p_up, bool p_use_model_front) const {
#ifdef MATH_CHECKS
ERR_FAIL_COND_V_MSG(origin.is_equal_approx(p_target), Transform3D(), "The transform's origin and target can't be equal.");
#endif
Transform3D t = *this;
- t.basis = Basis::looking_at(p_target - origin, p_up);
+ t.basis = Basis::looking_at(p_target - origin, p_up, p_use_model_front);
return t;
}
-void Transform3D::set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up) {
+void Transform3D::set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up, bool p_use_model_front) {
#ifdef MATH_CHECKS
ERR_FAIL_COND_MSG(p_eye.is_equal_approx(p_target), "The eye and target vectors can't be equal.");
#endif
- basis = Basis::looking_at(p_target - p_eye, p_up);
+ basis = Basis::looking_at(p_target - p_eye, p_up, p_use_model_front);
origin = p_eye;
}
diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h
index bf1b4cdb63..70141a3dbe 100644
--- a/core/math/transform_3d.h
+++ b/core/math/transform_3d.h
@@ -52,8 +52,8 @@ struct _NO_DISCARD_ Transform3D {
void rotate(const Vector3 &p_axis, real_t p_angle);
void rotate_basis(const Vector3 &p_axis, real_t p_angle);
- void set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0));
- Transform3D looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0)) const;
+ void set_look_at(const Vector3 &p_eye, const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false);
+ Transform3D looking_at(const Vector3 &p_target, const Vector3 &p_up = Vector3(0, 1, 0), bool p_use_model_front = false) const;
void scale(const Vector3 &p_scale);
Transform3D scaled(const Vector3 &p_scale) const;
diff --git a/core/object/callable_method_pointer.h b/core/object/callable_method_pointer.h
index 1584906710..2dbb7e468e 100644
--- a/core/object/callable_method_pointer.h
+++ b/core/object/callable_method_pointer.h
@@ -198,7 +198,7 @@ class CallableCustomMethodPointerRetC : public CallableCustomMethodPointerBase {
} data;
public:
- virtual ObjectID get_object() const {
+ virtual ObjectID get_object() const override {
#ifdef DEBUG_ENABLED
if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) {
return ObjectID();
@@ -207,7 +207,7 @@ public:
return data.instance->get_instance_id();
}
- virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+ virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_MSG(ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr, "Invalid Object id '" + uitos(data.object_id) + "', can't call method.");
#endif
@@ -254,11 +254,15 @@ class CallableCustomStaticMethodPointer : public CallableCustomMethodPointerBase
} data;
public:
- virtual ObjectID get_object() const {
+ virtual bool is_valid() const override {
+ return true;
+ }
+
+ virtual ObjectID get_object() const override {
return ObjectID();
}
- virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+ virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error);
r_return_value = Variant();
}
@@ -292,11 +296,15 @@ class CallableCustomStaticMethodPointerRet : public CallableCustomMethodPointerB
} data;
public:
- virtual ObjectID get_object() const {
+ virtual bool is_valid() const override {
+ return true;
+ }
+
+ virtual ObjectID get_object() const override {
return ObjectID();
}
- virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const {
+ virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override {
call_with_variant_args_static_ret(data.method, p_arguments, p_argcount, r_return_value, r_call_error);
}
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index a602dc9fb8..cc4a29164d 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -53,8 +53,10 @@ MethodDefinition D_METHODP(const char *p_name, const char *const **p_args, uint3
#endif
ClassDB::APIType ClassDB::current_api = API_CORE;
+HashMap<ClassDB::APIType, uint64_t> ClassDB::api_hashes_cache;
void ClassDB::set_current_api(APIType p_api) {
+ DEV_ASSERT(!api_hashes_cache.has(p_api)); // This API type may not be suitable for caching of hash if it can change later.
current_api = p_api;
}
@@ -165,6 +167,10 @@ uint64_t ClassDB::get_api_hash(APIType p_api) {
OBJTYPE_RLOCK;
#ifdef DEBUG_METHODS_ENABLED
+ if (api_hashes_cache.has(p_api)) {
+ return api_hashes_cache[p_api];
+ }
+
uint64_t hash = hash_murmur3_one_64(HashMapHasherDefault::hash(VERSION_FULL_CONFIG));
List<StringName> class_list;
@@ -290,7 +296,14 @@ uint64_t ClassDB::get_api_hash(APIType p_api) {
}
}
- return hash_fmix32(hash);
+ hash = hash_fmix32(hash);
+
+ // Extension API changes at runtime; let's just not cache them by now.
+ if (p_api != API_EXTENSION && p_api != API_EDITOR_EXTENSION) {
+ api_hashes_cache[p_api] = hash;
+ }
+
+ return hash;
#else
return 0;
#endif
@@ -549,6 +562,60 @@ MethodBind *ClassDB::get_method(const StringName &p_class, const StringName &p_n
return nullptr;
}
+Vector<uint32_t> ClassDB::get_method_compatibility_hashes(const StringName &p_class, const StringName &p_name) {
+ OBJTYPE_RLOCK;
+
+ ClassInfo *type = classes.getptr(p_class);
+
+ while (type) {
+ if (type->method_map_compatibility.has(p_name)) {
+ LocalVector<MethodBind *> *c = type->method_map_compatibility.getptr(p_name);
+ Vector<uint32_t> ret;
+ for (uint32_t i = 0; i < c->size(); i++) {
+ ret.push_back((*c)[i]->get_hash());
+ }
+ return ret;
+ }
+ type = type->inherits_ptr;
+ }
+ return Vector<uint32_t>();
+}
+
+MethodBind *ClassDB::get_method_with_compatibility(const StringName &p_class, const StringName &p_name, uint64_t p_hash, bool *r_method_exists, bool *r_is_deprecated) {
+ OBJTYPE_RLOCK;
+
+ ClassInfo *type = classes.getptr(p_class);
+
+ while (type) {
+ MethodBind **method = type->method_map.getptr(p_name);
+ if (method && *method) {
+ if (r_method_exists) {
+ *r_method_exists = true;
+ }
+ if ((*method)->get_hash() == p_hash) {
+ return *method;
+ }
+ }
+
+ LocalVector<MethodBind *> *compat = type->method_map_compatibility.getptr(p_name);
+ if (compat) {
+ if (r_method_exists) {
+ *r_method_exists = true;
+ }
+ for (uint32_t i = 0; i < compat->size(); i++) {
+ if ((*compat)[i]->get_hash() == p_hash) {
+ if (r_is_deprecated) {
+ *r_is_deprecated = true;
+ }
+ return (*compat)[i];
+ }
+ }
+ }
+ type = type->inherits_ptr;
+ }
+ return nullptr;
+}
+
void ClassDB::bind_integer_constant(const StringName &p_class, const StringName &p_enum, const StringName &p_name, int64_t p_constant, bool p_is_bitfield) {
OBJTYPE_WLOCK;
@@ -1261,11 +1328,30 @@ bool ClassDB::has_method(const StringName &p_class, const StringName &p_method,
}
void ClassDB::bind_method_custom(const StringName &p_class, MethodBind *p_method) {
+ _bind_method_custom(p_class, p_method, false);
+}
+void ClassDB::bind_compatibility_method_custom(const StringName &p_class, MethodBind *p_method) {
+ _bind_method_custom(p_class, p_method, true);
+}
+
+void ClassDB::_bind_compatibility(ClassInfo *type, MethodBind *p_method) {
+ if (!type->method_map_compatibility.has(p_method->get_name())) {
+ type->method_map_compatibility.insert(p_method->get_name(), LocalVector<MethodBind *>());
+ }
+ type->method_map_compatibility[p_method->get_name()].push_back(p_method);
+}
+
+void ClassDB::_bind_method_custom(const StringName &p_class, MethodBind *p_method, bool p_compatibility) {
ClassInfo *type = classes.getptr(p_class);
if (!type) {
ERR_FAIL_MSG("Couldn't bind custom method '" + p_method->get_name() + "' for instance '" + p_class + "'.");
}
+ if (p_compatibility) {
+ _bind_compatibility(type, p_method);
+ return;
+ }
+
if (type->method_map.has(p_method->get_name())) {
// overloading not supported
ERR_FAIL_MSG("Method already bound '" + p_class + "::" + p_method->get_name() + "'.");
@@ -1278,11 +1364,44 @@ void ClassDB::bind_method_custom(const StringName &p_class, MethodBind *p_method
type->method_map[p_method->get_name()] = p_method;
}
+MethodBind *ClassDB::_bind_vararg_method(MethodBind *p_bind, const StringName &p_name, const Vector<Variant> &p_default_args, bool p_compatibility) {
+ MethodBind *bind = p_bind;
+ bind->set_name(p_name);
+ bind->set_default_arguments(p_default_args);
+
+ String instance_type = bind->get_instance_class();
+
+ ClassInfo *type = classes.getptr(instance_type);
+ if (!type) {
+ memdelete(bind);
+ ERR_FAIL_COND_V(!type, nullptr);
+ }
+
+ if (p_compatibility) {
+ _bind_compatibility(type, bind);
+ return bind;
+ }
+
+ if (type->method_map.has(p_name)) {
+ memdelete(bind);
+ // Overloading not supported
+ ERR_FAIL_V_MSG(nullptr, "Method already bound: " + instance_type + "::" + p_name + ".");
+ }
+ type->method_map[p_name] = bind;
+#ifdef DEBUG_METHODS_ENABLED
+ // FIXME: <reduz> set_return_type is no longer in MethodBind, so I guess it should be moved to vararg method bind
+ //bind->set_return_type("Variant");
+ type->method_order.push_back(p_name);
+#endif
+
+ return bind;
+}
+
#ifdef DEBUG_METHODS_ENABLED
-MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount) {
+MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount) {
StringName mdname = method_name.name;
#else
-MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const char *method_name, const Variant **p_defs, int p_defcount) {
+MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const char *method_name, const Variant **p_defs, int p_defcount) {
StringName mdname = StaticCString::create(method_name);
#endif
@@ -1294,7 +1413,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const c
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_V_MSG(has_method(instance_type, mdname), nullptr, "Class " + String(instance_type) + " already has a method " + String(mdname) + ".");
+ ERR_FAIL_COND_V_MSG(!p_compatibility && has_method(instance_type, mdname), nullptr, "Class " + String(instance_type) + " already has a method " + String(mdname) + ".");
#endif
ClassInfo *type = classes.getptr(instance_type);
@@ -1303,7 +1422,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const c
ERR_FAIL_V_MSG(nullptr, "Couldn't bind method '" + mdname + "' for instance '" + instance_type + "'.");
}
- if (type->method_map.has(mdname)) {
+ if (!p_compatibility && type->method_map.has(mdname)) {
memdelete(p_bind);
// overloading not supported
ERR_FAIL_V_MSG(nullptr, "Method already bound '" + instance_type + "::" + mdname + "'.");
@@ -1318,10 +1437,16 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const c
p_bind->set_argument_names(method_name.args);
- type->method_order.push_back(mdname);
+ if (!p_compatibility) {
+ type->method_order.push_back(mdname);
+ }
#endif
- type->method_map[mdname] = p_bind;
+ if (p_compatibility) {
+ _bind_compatibility(type, p_bind);
+ } else {
+ type->method_map[mdname] = p_bind;
+ }
Vector<Variant> defvals;
@@ -1595,7 +1720,13 @@ void ClassDB::cleanup() {
for (KeyValue<StringName, MethodBind *> &F : ti.method_map) {
memdelete(F.value);
}
+ for (KeyValue<StringName, LocalVector<MethodBind *>> &F : ti.method_map_compatibility) {
+ for (uint32_t i = 0; i < F.value.size(); i++) {
+ memdelete(F.value[i]);
+ }
+ }
}
+
classes.clear();
resource_base_extensions.clear();
compat_classes.clear();
diff --git a/core/object/class_db.h b/core/object/class_db.h
index 0b62cf40f7..ce64336a45 100644
--- a/core/object/class_db.h
+++ b/core/object/class_db.h
@@ -105,6 +105,7 @@ public:
ObjectGDExtension *gdextension = nullptr;
HashMap<StringName, MethodBind *> method_map;
+ HashMap<StringName, LocalVector<MethodBind *>> method_map_compatibility;
HashMap<StringName, int64_t> constant_map;
struct EnumInfo {
List<StringName> constants;
@@ -148,12 +149,13 @@ public:
static HashMap<StringName, StringName> compat_classes;
#ifdef DEBUG_METHODS_ENABLED
- static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount);
+ static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const MethodDefinition &method_name, const Variant **p_defs, int p_defcount);
#else
- static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, const char *method_name, const Variant **p_defs, int p_defcount);
+ static MethodBind *bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_compatibility, const char *method_name, const Variant **p_defs, int p_defcount);
#endif
static APIType current_api;
+ static HashMap<APIType, uint64_t> api_hashes_cache;
static void _add_class2(const StringName &p_class, const StringName &p_inherits);
@@ -171,6 +173,9 @@ private:
// Non-locking variants of get_parent_class and is_parent_class.
static StringName _get_parent_class(const StringName &p_class);
static bool _is_parent_class(const StringName &p_class, const StringName &p_inherits);
+ static void _bind_compatibility(ClassInfo *type, MethodBind *p_method);
+ static MethodBind *_bind_vararg_method(MethodBind *p_bind, const StringName &p_name, const Vector<Variant> &p_default_args, bool p_compatibility);
+ static void _bind_method_custom(const StringName &p_class, MethodBind *p_method, bool p_compatibility);
public:
// DO NOT USE THIS!!!!!! NEEDS TO BE PUBLIC BUT DO NOT USE NO MATTER WHAT!!!
@@ -272,7 +277,7 @@ public:
if constexpr (std::is_same<typename member_function_traits<M>::return_type, Object *>::value) {
bind->set_return_type_is_raw_object_ptr(true);
}
- return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
+ return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, false, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
template <class N, class M, typename... VarArgs>
@@ -287,7 +292,36 @@ public:
if constexpr (std::is_same<typename member_function_traits<M>::return_type, Object *>::value) {
bind->set_return_type_is_raw_object_ptr(true);
}
- return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
+ return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, false, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
+ }
+
+ template <class N, class M, typename... VarArgs>
+ static MethodBind *bind_compatibility_method(N p_method_name, M p_method, VarArgs... p_args) {
+ Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
+ const Variant *argptrs[sizeof...(p_args) + 1];
+ for (uint32_t i = 0; i < sizeof...(p_args); i++) {
+ argptrs[i] = &args[i];
+ }
+ MethodBind *bind = create_method_bind(p_method);
+ if constexpr (std::is_same<typename member_function_traits<M>::return_type, Object *>::value) {
+ bind->set_return_type_is_raw_object_ptr(true);
+ }
+ return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, true, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
+ }
+
+ template <class N, class M, typename... VarArgs>
+ static MethodBind *bind_compatibility_static_method(const StringName &p_class, N p_method_name, M p_method, VarArgs... p_args) {
+ Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported.
+ const Variant *argptrs[sizeof...(p_args) + 1];
+ for (uint32_t i = 0; i < sizeof...(p_args); i++) {
+ argptrs[i] = &args[i];
+ }
+ MethodBind *bind = create_static_method_bind(p_method);
+ bind->set_instance_class(p_class);
+ if constexpr (std::is_same<typename member_function_traits<M>::return_type, Object *>::value) {
+ bind->set_return_type_is_raw_object_ptr(true);
+ }
+ return bind_methodfi(METHOD_FLAGS_DEFAULT, bind, true, p_method_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
template <class M>
@@ -297,36 +331,27 @@ public:
MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant);
ERR_FAIL_COND_V(!bind, nullptr);
- bind->set_name(p_name);
- bind->set_default_arguments(p_default_args);
if constexpr (std::is_same<typename member_function_traits<M>::return_type, Object *>::value) {
bind->set_return_type_is_raw_object_ptr(true);
}
+ return _bind_vararg_method(bind, p_name, p_default_args, false);
+ }
- String instance_type = bind->get_instance_class();
+ template <class M>
+ static MethodBind *bind_compatibility_vararg_method(uint32_t p_flags, const StringName &p_name, M p_method, const MethodInfo &p_info = MethodInfo(), const Vector<Variant> &p_default_args = Vector<Variant>(), bool p_return_nil_is_variant = true) {
+ GLOBAL_LOCK_FUNCTION;
- ClassInfo *type = classes.getptr(instance_type);
- if (!type) {
- memdelete(bind);
- ERR_FAIL_COND_V(!type, nullptr);
- }
+ MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant);
+ ERR_FAIL_COND_V(!bind, nullptr);
- if (type->method_map.has(p_name)) {
- memdelete(bind);
- // Overloading not supported
- ERR_FAIL_V_MSG(nullptr, "Method already bound: " + instance_type + "::" + p_name + ".");
+ if constexpr (std::is_same<typename member_function_traits<M>::return_type, Object *>::value) {
+ bind->set_return_type_is_raw_object_ptr(true);
}
- type->method_map[p_name] = bind;
-#ifdef DEBUG_METHODS_ENABLED
- // FIXME: <reduz> set_return_type is no longer in MethodBind, so I guess it should be moved to vararg method bind
- //bind->set_return_type("Variant");
- type->method_order.push_back(p_name);
-#endif
-
- return bind;
+ return _bind_vararg_method(bind, p_name, p_default_args, true);
}
static void bind_method_custom(const StringName &p_class, MethodBind *p_method);
+ static void bind_compatibility_method_custom(const StringName &p_class, MethodBind *p_method);
static void add_signal(const StringName &p_class, const MethodInfo &p_signal);
static bool has_signal(const StringName &p_class, const StringName &p_signal, bool p_no_inheritance = false);
@@ -357,6 +382,8 @@ public:
static void get_method_list(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
static bool get_method_info(const StringName &p_class, const StringName &p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false);
static MethodBind *get_method(const StringName &p_class, const StringName &p_name);
+ static MethodBind *get_method_with_compatibility(const StringName &p_class, const StringName &p_name, uint64_t p_hash, bool *r_method_exists = nullptr, bool *r_is_deprecated = nullptr);
+ static Vector<uint32_t> get_method_compatibility_hashes(const StringName &p_class, const StringName &p_name);
static void add_virtual_method(const StringName &p_class, const MethodInfo &p_method, bool p_virtual = true, const Vector<String> &p_arg_names = Vector<String>(), bool p_object_core = false);
static void get_virtual_methods(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false);
diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py
index 18f27ae4a4..5be9650b32 100644
--- a/core/object/make_virtuals.py
+++ b/core/object/make_virtuals.py
@@ -154,7 +154,6 @@ def generate_version(argcount, const=False, returns=False):
def run(target, source, env):
-
max_versions = 12
txt = """
@@ -165,7 +164,6 @@ def run(target, source, env):
"""
for i in range(max_versions + 1):
-
txt += "/* " + str(i) + " Arguments */\n\n"
txt += generate_version(i, False, False)
txt += generate_version(i, False, True)
diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp
index decf030e27..18ba5d5b30 100644
--- a/core/object/message_queue.cpp
+++ b/core/object/message_queue.cpp
@@ -35,187 +35,177 @@
#include "core/object/class_db.h"
#include "core/object/script_language.h"
-MessageQueue *MessageQueue::singleton = nullptr;
-
-MessageQueue *MessageQueue::get_singleton() {
- return singleton;
-}
-
-Error MessageQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
- return push_callablep(Callable(p_id, p_method), p_args, p_argcount, p_show_error);
-}
-
-Error MessageQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) {
- _THREAD_SAFE_METHOD_
-
- uint8_t room_needed = sizeof(Message) + sizeof(Variant);
-
- if ((buffer_end + room_needed) >= buffer_size) {
- String type;
- if (ObjectDB::get_instance(p_id)) {
- type = ObjectDB::get_instance(p_id)->get_class();
- }
- ERR_PRINT("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id) + ". Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_kb' in project settings.");
- statistics();
- return ERR_OUT_OF_MEMORY;
+#ifdef DEV_ENABLED
+// Includes sanity checks to ensure that a queue set as a thread singleton override
+// is only ever called from the thread it was set for.
+#define LOCK_MUTEX \
+ if (this != MessageQueue::thread_singleton) { \
+ DEV_ASSERT(!this->is_current_thread_override); \
+ mutex.lock(); \
+ } else { \
+ DEV_ASSERT(this->is_current_thread_override); \
}
-
- Message *msg = memnew_placement(&buffer[buffer_end], Message);
- msg->args = 1;
- msg->callable = Callable(p_id, p_prop);
- msg->type = TYPE_SET;
-
- buffer_end += sizeof(Message);
-
- Variant *v = memnew_placement(&buffer[buffer_end], Variant);
- buffer_end += sizeof(Variant);
- *v = p_value;
-
- return OK;
-}
-
-Error MessageQueue::push_notification(ObjectID p_id, int p_notification) {
- _THREAD_SAFE_METHOD_
-
- ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER);
-
- uint8_t room_needed = sizeof(Message);
-
- if ((buffer_end + room_needed) >= buffer_size) {
- ERR_PRINT("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id) + ". Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_kb' in project settings.");
- statistics();
- return ERR_OUT_OF_MEMORY;
+#else
+#define LOCK_MUTEX \
+ if (this != MessageQueue::thread_singleton) { \
+ mutex.lock(); \
}
+#endif
- Message *msg = memnew_placement(&buffer[buffer_end], Message);
-
- msg->type = TYPE_NOTIFICATION;
- msg->callable = Callable(p_id, CoreStringNames::get_singleton()->notification); //name is meaningless but callable needs it
- //msg->target;
- msg->notification = p_notification;
+#define UNLOCK_MUTEX \
+ if (this != MessageQueue::thread_singleton) { \
+ mutex.unlock(); \
+ }
- buffer_end += sizeof(Message);
+void CallQueue::_add_page() {
+ if (pages_used == page_bytes.size()) {
+ pages.push_back(allocator->alloc());
+ page_bytes.push_back(0);
+ }
+ page_bytes[pages_used] = 0;
+ pages_used++;
+}
- return OK;
+Error CallQueue::push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
+ return push_callablep(Callable(p_id, p_method), p_args, p_argcount, p_show_error);
}
-Error MessageQueue::push_callp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
+Error CallQueue::push_callp(Object *p_object, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
return push_callp(p_object->get_instance_id(), p_method, p_args, p_argcount, p_show_error);
}
-Error MessageQueue::push_notification(Object *p_object, int p_notification) {
+Error CallQueue::push_notification(Object *p_object, int p_notification) {
return push_notification(p_object->get_instance_id(), p_notification);
}
-Error MessageQueue::push_set(Object *p_object, const StringName &p_prop, const Variant &p_value) {
+Error CallQueue::push_set(Object *p_object, const StringName &p_prop, const Variant &p_value) {
return push_set(p_object->get_instance_id(), p_prop, p_value);
}
-Error MessageQueue::push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error) {
- _THREAD_SAFE_METHOD_
+Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error) {
+ uint32_t room_needed = sizeof(Message) + sizeof(Variant) * p_argcount;
+
+ ERR_FAIL_COND_V_MSG(room_needed > uint32_t(PAGE_SIZE_BYTES), ERR_INVALID_PARAMETER, "Message is too large to fit on a page (" + itos(PAGE_SIZE_BYTES) + " bytes), consider passing less arguments.");
+
+ LOCK_MUTEX;
- int room_needed = sizeof(Message) + sizeof(Variant) * p_argcount;
+ _ensure_first_page();
- if ((buffer_end + room_needed) >= buffer_size) {
- ERR_PRINT("Failed method: " + p_callable + ". Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_kb' in project settings.");
- statistics();
- return ERR_OUT_OF_MEMORY;
+ if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
+ if (pages_used == max_pages) {
+ ERR_PRINT("Failed method: " + p_callable + ". Message queue out of memory. " + error_text);
+ statistics();
+ UNLOCK_MUTEX;
+ return ERR_OUT_OF_MEMORY;
+ }
+ _add_page();
}
- Message *msg = memnew_placement(&buffer[buffer_end], Message);
+ Page *page = pages[pages_used - 1];
+
+ uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]];
+
+ Message *msg = memnew_placement(buffer_end, Message);
msg->args = p_argcount;
msg->callable = p_callable;
msg->type = TYPE_CALL;
if (p_show_error) {
msg->type |= FLAG_SHOW_ERROR;
}
+ // Support callables of static methods.
+ if (p_callable.get_object_id().is_null() && p_callable.is_valid()) {
+ msg->type |= FLAG_NULL_IS_OK;
+ }
buffer_end += sizeof(Message);
for (int i = 0; i < p_argcount; i++) {
- Variant *v = memnew_placement(&buffer[buffer_end], Variant);
+ Variant *v = memnew_placement(buffer_end, Variant);
buffer_end += sizeof(Variant);
*v = *p_args[i];
}
+ page_bytes[pages_used - 1] += room_needed;
+
+ UNLOCK_MUTEX;
+
return OK;
}
-void MessageQueue::statistics() {
- HashMap<StringName, int> set_count;
- HashMap<int, int> notify_count;
- HashMap<Callable, int> call_count;
- int null_count = 0;
+Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value) {
+ LOCK_MUTEX;
+ uint32_t room_needed = sizeof(Message) + sizeof(Variant);
- uint32_t read_pos = 0;
- while (read_pos < buffer_end) {
- Message *message = (Message *)&buffer[read_pos];
+ _ensure_first_page();
- Object *target = message->callable.get_object();
+ if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
+ if (pages_used == max_pages) {
+ String type;
+ if (ObjectDB::get_instance(p_id)) {
+ type = ObjectDB::get_instance(p_id)->get_class();
+ }
+ ERR_PRINT("Failed set: " + type + ":" + p_prop + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text);
+ statistics();
- if (target != nullptr) {
- switch (message->type & FLAG_MASK) {
- case TYPE_CALL: {
- if (!call_count.has(message->callable)) {
- call_count[message->callable] = 0;
- }
+ UNLOCK_MUTEX;
+ return ERR_OUT_OF_MEMORY;
+ }
+ _add_page();
+ }
- call_count[message->callable]++;
+ Page *page = pages[pages_used - 1];
+ uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]];
- } break;
- case TYPE_NOTIFICATION: {
- if (!notify_count.has(message->notification)) {
- notify_count[message->notification] = 0;
- }
+ Message *msg = memnew_placement(buffer_end, Message);
+ msg->args = 1;
+ msg->callable = Callable(p_id, p_prop);
+ msg->type = TYPE_SET;
- notify_count[message->notification]++;
+ buffer_end += sizeof(Message);
- } break;
- case TYPE_SET: {
- StringName t = message->callable.get_method();
- if (!set_count.has(t)) {
- set_count[t] = 0;
- }
+ Variant *v = memnew_placement(buffer_end, Variant);
+ *v = p_value;
- set_count[t]++;
+ page_bytes[pages_used - 1] += room_needed;
+ UNLOCK_MUTEX;
- } break;
- }
+ return OK;
+}
- } else {
- //object was deleted
- print_line("Object was deleted while awaiting a callback");
+Error CallQueue::push_notification(ObjectID p_id, int p_notification) {
+ ERR_FAIL_COND_V(p_notification < 0, ERR_INVALID_PARAMETER);
+ LOCK_MUTEX;
+ uint32_t room_needed = sizeof(Message);
- null_count++;
- }
+ _ensure_first_page();
- read_pos += sizeof(Message);
- if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
- read_pos += sizeof(Variant) * message->args;
+ if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) {
+ if (pages_used == max_pages) {
+ ERR_PRINT("Failed notification: " + itos(p_notification) + " target ID: " + itos(p_id) + ". Message queue out of memory. " + error_text);
+ statistics();
+ UNLOCK_MUTEX;
+ return ERR_OUT_OF_MEMORY;
}
+ _add_page();
}
- print_line("TOTAL BYTES: " + itos(buffer_end));
- print_line("NULL count: " + itos(null_count));
+ Page *page = pages[pages_used - 1];
+ uint8_t *buffer_end = &page->data[page_bytes[pages_used - 1]];
- for (const KeyValue<StringName, int> &E : set_count) {
- print_line("SET " + E.key + ": " + itos(E.value));
- }
+ Message *msg = memnew_placement(buffer_end, Message);
- for (const KeyValue<Callable, int> &E : call_count) {
- print_line("CALL " + E.key + ": " + itos(E.value));
- }
+ msg->type = TYPE_NOTIFICATION;
+ msg->callable = Callable(p_id, CoreStringNames::get_singleton()->notification); //name is meaningless but callable needs it
+ //msg->target;
+ msg->notification = p_notification;
- for (const KeyValue<int, int> &E : notify_count) {
- print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value));
- }
-}
+ page_bytes[pages_used - 1] += room_needed;
+ UNLOCK_MUTEX;
-int MessageQueue::get_max_buffer_usage() const {
- return buffer_max_used;
+ return OK;
}
-void MessageQueue::_call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error) {
+void CallQueue::_call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error) {
const Variant **argptrs = nullptr;
if (p_argcount) {
argptrs = (const Variant **)alloca(sizeof(Variant *) * p_argcount);
@@ -232,26 +222,84 @@ void MessageQueue::_call_function(const Callable &p_callable, const Variant *p_a
}
}
-void MessageQueue::flush() {
- if (buffer_end > buffer_max_used) {
- buffer_max_used = buffer_end;
- }
+Error CallQueue::flush() {
+ LOCK_MUTEX;
+
+ // Thread overrides are not meant to be flushed, but appended to the main one.
+ if (this == MessageQueue::thread_singleton) {
+ if (pages.size() == 0) {
+ return OK;
+ }
+
+ CallQueue *mq = MessageQueue::main_singleton;
+ DEV_ASSERT(!mq->allocator_is_custom && !allocator_is_custom); // Transferring pages is only safe if using the same alloator parameters.
+
+ mq->mutex.lock();
+
+ // Here we're transferring the data from this queue to the main one.
+ // However, it's very unlikely big amounts of messages will be queued here,
+ // so PagedArray/Pool would be overkill. Also, in most cases the data will fit
+ // an already existing page of the main queue.
+
+ // Let's see if our first (likely only) page fits the current target queue page.
+ uint32_t src_page = 0;
+ {
+ if (mq->pages_used) {
+ uint32_t dst_page = mq->pages_used - 1;
+ uint32_t dst_offset = mq->page_bytes[dst_page];
+ if (dst_offset + page_bytes[0] < uint32_t(PAGE_SIZE_BYTES)) {
+ memcpy(mq->pages[dst_page]->data + dst_offset, pages[0]->data, page_bytes[0]);
+ mq->page_bytes[dst_page] += page_bytes[0];
+ src_page++;
+ }
+ }
+ }
+
+ // Any other possibly existing source page needs to be added.
- uint32_t read_pos = 0;
+ if (mq->pages_used + (pages_used - src_page) > mq->max_pages) {
+ ERR_PRINT("Failed appending thread queue. Message queue out of memory. " + mq->error_text);
+ mq->statistics();
+ mq->mutex.unlock();
+ return ERR_OUT_OF_MEMORY;
+ }
- //using reverse locking strategy
- _THREAD_SAFE_LOCK_
+ for (; src_page < pages_used; src_page++) {
+ mq->_add_page();
+ memcpy(mq->pages[mq->pages_used - 1]->data, pages[src_page]->data, page_bytes[src_page]);
+ mq->page_bytes[mq->pages_used - 1] = page_bytes[src_page];
+ }
+
+ mq->mutex.unlock();
+
+ page_bytes[0] = 0;
+ pages_used = 1;
+
+ return OK;
+ }
+
+ if (pages.size() == 0) {
+ // Never allocated
+ UNLOCK_MUTEX;
+ return OK; // Do nothing.
+ }
if (flushing) {
- _THREAD_SAFE_UNLOCK_
- ERR_FAIL_COND(flushing); //already flushing, you did something odd
+ UNLOCK_MUTEX;
+ return ERR_BUSY;
}
+
flushing = true;
- while (read_pos < buffer_end) {
+ uint32_t i = 0;
+ uint32_t offset = 0;
+
+ while (i < pages_used && offset < page_bytes[i]) {
+ Page *page = pages[i];
+
//lock on each iteration, so a call can re-add itself to the message queue
- Message *message = (Message *)&buffer[read_pos];
+ Message *message = (Message *)&page->data[offset];
uint32_t advance = sizeof(Message);
if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
@@ -259,86 +307,266 @@ void MessageQueue::flush() {
}
//pre-advance so this function is reentrant
- read_pos += advance;
-
- _THREAD_SAFE_UNLOCK_
+ offset += advance;
Object *target = message->callable.get_object();
- if (target != nullptr) {
- switch (message->type & FLAG_MASK) {
- case TYPE_CALL: {
+ UNLOCK_MUTEX;
+
+ switch (message->type & FLAG_MASK) {
+ case TYPE_CALL: {
+ if (target || (message->type & FLAG_NULL_IS_OK)) {
Variant *args = (Variant *)(message + 1);
+ _call_function(message->callable, args, message->args, message->type & FLAG_SHOW_ERROR);
+ }
+ } break;
+ case TYPE_NOTIFICATION: {
+ if (target) {
+ target->notification(message->notification);
+ }
+ } break;
+ case TYPE_SET: {
+ if (target) {
+ Variant *arg = (Variant *)(message + 1);
+ target->set(message->callable.get_method(), *arg);
+ }
+ } break;
+ }
- // messages don't expect a return value
+ if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
+ Variant *args = (Variant *)(message + 1);
+ for (int k = 0; k < message->args; k++) {
+ args[k].~Variant();
+ }
+ }
- _call_function(message->callable, args, message->args, message->type & FLAG_SHOW_ERROR);
+ message->~Message();
+
+ LOCK_MUTEX;
+ if (offset == page_bytes[i]) {
+ i++;
+ offset = 0;
+ }
+ }
+
+ page_bytes[0] = 0;
+ pages_used = 1;
+
+ flushing = false;
+ UNLOCK_MUTEX;
+ return OK;
+}
+
+void CallQueue::clear() {
+ LOCK_MUTEX;
+
+ if (pages.size() == 0) {
+ UNLOCK_MUTEX;
+ return; // Nothing to clear.
+ }
+
+ for (uint32_t i = 0; i < pages_used; i++) {
+ uint32_t offset = 0;
+ while (offset < page_bytes[i]) {
+ Page *page = pages[i];
+
+ //lock on each iteration, so a call can re-add itself to the message queue
+
+ Message *message = (Message *)&page->data[offset];
+
+ uint32_t advance = sizeof(Message);
+ if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
+ advance += sizeof(Variant) * message->args;
+ }
+
+ offset += advance;
+
+ if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
+ Variant *args = (Variant *)(message + 1);
+ for (int k = 0; k < message->args; k++) {
+ args[k].~Variant();
+ }
+ }
+
+ message->~Message();
+ }
+ }
+
+ pages_used = 1;
+ page_bytes[0] = 0;
+
+ UNLOCK_MUTEX;
+}
+
+void CallQueue::statistics() {
+ LOCK_MUTEX;
+ HashMap<StringName, int> set_count;
+ HashMap<int, int> notify_count;
+ HashMap<Callable, int> call_count;
+ int null_count = 0;
+
+ for (uint32_t i = 0; i < pages_used; i++) {
+ uint32_t offset = 0;
+ while (offset < page_bytes[i]) {
+ Page *page = pages[i];
+
+ //lock on each iteration, so a call can re-add itself to the message queue
+
+ Message *message = (Message *)&page->data[offset];
+
+ uint32_t advance = sizeof(Message);
+ if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
+ advance += sizeof(Variant) * message->args;
+ }
+
+ Object *target = message->callable.get_object();
+
+ bool null_target = true;
+ switch (message->type & FLAG_MASK) {
+ case TYPE_CALL: {
+ if (target || (message->type & FLAG_NULL_IS_OK)) {
+ if (!call_count.has(message->callable)) {
+ call_count[message->callable] = 0;
+ }
+ call_count[message->callable]++;
+ null_target = false;
+ }
} break;
case TYPE_NOTIFICATION: {
- // messages don't expect a return value
- target->notification(message->notification);
+ if (target) {
+ if (!notify_count.has(message->notification)) {
+ notify_count[message->notification] = 0;
+ }
+ notify_count[message->notification]++;
+ null_target = false;
+ }
} break;
case TYPE_SET: {
- Variant *arg = (Variant *)(message + 1);
- // messages don't expect a return value
- target->set(message->callable.get_method(), *arg);
-
+ if (target) {
+ StringName t = message->callable.get_method();
+ if (!set_count.has(t)) {
+ set_count[t] = 0;
+ }
+
+ set_count[t]++;
+ null_target = false;
+ }
} break;
}
- }
+ if (null_target) {
+ //object was deleted
+ print_line("Object was deleted while awaiting a callback");
- if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
- Variant *args = (Variant *)(message + 1);
- for (int i = 0; i < message->args; i++) {
- args[i].~Variant();
+ null_count++;
+ }
+
+ offset += advance;
+
+ if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
+ Variant *args = (Variant *)(message + 1);
+ for (int k = 0; k < message->args; k++) {
+ args[k].~Variant();
+ }
}
+
+ message->~Message();
}
+ }
- message->~Message();
+ print_line("TOTAL PAGES: " + itos(pages_used) + " (" + itos(pages_used * PAGE_SIZE_BYTES) + " bytes).");
+ print_line("NULL count: " + itos(null_count));
- _THREAD_SAFE_LOCK_
+ for (const KeyValue<StringName, int> &E : set_count) {
+ print_line("SET " + E.key + ": " + itos(E.value));
}
- buffer_end = 0; // reset buffer
- flushing = false;
- _THREAD_SAFE_UNLOCK_
+ for (const KeyValue<Callable, int> &E : call_count) {
+ print_line("CALL " + E.key + ": " + itos(E.value));
+ }
+
+ for (const KeyValue<int, int> &E : notify_count) {
+ print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value));
+ }
+
+ UNLOCK_MUTEX;
}
-bool MessageQueue::is_flushing() const {
+bool CallQueue::is_flushing() const {
return flushing;
}
-MessageQueue::MessageQueue() {
- ERR_FAIL_COND_MSG(singleton != nullptr, "A MessageQueue singleton already exists.");
- singleton = this;
+bool CallQueue::has_messages() const {
+ if (pages_used == 0) {
+ return false;
+ }
+ if (pages_used == 1 && page_bytes[0] == 0) {
+ return false;
+ }
- buffer_size = GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_kb", PROPERTY_HINT_RANGE, "1024,4096,1,or_greater"), DEFAULT_QUEUE_SIZE_KB);
- buffer_size *= 1024;
- buffer = memnew_arr(uint8_t, buffer_size);
+ return true;
}
-MessageQueue::~MessageQueue() {
- uint32_t read_pos = 0;
+int CallQueue::get_max_buffer_usage() const {
+ return pages.size() * PAGE_SIZE_BYTES;
+}
- while (read_pos < buffer_end) {
- Message *message = (Message *)&buffer[read_pos];
- Variant *args = (Variant *)(message + 1);
- int argc = message->args;
- if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
- for (int i = 0; i < argc; i++) {
- args[i].~Variant();
- }
- }
- message->~Message();
+CallQueue::CallQueue(Allocator *p_custom_allocator, uint32_t p_max_pages, const String &p_error_text) {
+ if (p_custom_allocator) {
+ allocator = p_custom_allocator;
+ allocator_is_custom = true;
+ } else {
+ allocator = memnew(Allocator(16)); // 16 elements per allocator page, 64kb per allocator page. Anything small will do, though.
+ allocator_is_custom = false;
+ }
+ max_pages = p_max_pages;
+ error_text = p_error_text;
+}
- read_pos += sizeof(Message);
- if ((message->type & FLAG_MASK) != TYPE_NOTIFICATION) {
- read_pos += sizeof(Variant) * message->args;
- }
+CallQueue::~CallQueue() {
+ clear();
+ // Let go of pages.
+ for (uint32_t i = 0; i < pages.size(); i++) {
+ allocator->free(pages[i]);
+ }
+ if (!allocator_is_custom) {
+ memdelete(allocator);
}
+ // This is done here to avoid a circular dependency between the sanity checks and the thread singleton pointer.
+ if (this == MessageQueue::thread_singleton) {
+ MessageQueue::thread_singleton = nullptr;
+ }
+}
- singleton = nullptr;
- memdelete_arr(buffer);
+//////////////////////
+
+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;
+ }
+#endif
+ thread_singleton = p_thread_singleton;
+#ifdef DEV_ENABLED
+ if (thread_singleton) {
+ thread_singleton->is_current_thread_override = true;
+ }
+#endif
+}
+
+MessageQueue::MessageQueue() :
+ CallQueue(nullptr,
+ int(GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "memory/limits/message_queue/max_size_mb", PROPERTY_HINT_RANGE, "1,512,1,or_greater"), 32)) * 1024 * 1024 / PAGE_SIZE_BYTES,
+ "Message queue out of memory. Try increasing 'memory/limits/message_queue/max_size_mb' in project settings.") {
+ ERR_FAIL_COND_MSG(main_singleton != nullptr, "A MessageQueue singleton already exists.");
+ main_singleton = this;
+}
+
+MessageQueue::~MessageQueue() {
+ main_singleton = nullptr;
}
diff --git a/core/object/message_queue.h b/core/object/message_queue.h
index ceb6f2a3f1..9f567e4dd0 100644
--- a/core/object/message_queue.h
+++ b/core/object/message_queue.h
@@ -33,26 +33,54 @@
#include "core/object/object_id.h"
#include "core/os/thread_safe.h"
+#include "core/templates/local_vector.h"
+#include "core/templates/paged_allocator.h"
#include "core/variant/variant.h"
class Object;
-class MessageQueue {
- _THREAD_SAFE_CLASS_
+class CallQueue {
+ friend class MessageQueue;
+public:
enum {
- DEFAULT_QUEUE_SIZE_KB = 4096
+ PAGE_SIZE_BYTES = 4096
+ };
+
+ struct Page {
+ uint8_t data[PAGE_SIZE_BYTES];
};
+ // Needs to be public to be able to define it outside the class.
+ // Needs to lock because there can be multiple of these allocators in several threads.
+ typedef PagedAllocator<Page, true> Allocator;
+
+private:
enum {
TYPE_CALL,
TYPE_NOTIFICATION,
TYPE_SET,
+ TYPE_END, // End marker.
+ FLAG_NULL_IS_OK = 1 << 13,
FLAG_SHOW_ERROR = 1 << 14,
- FLAG_MASK = FLAG_SHOW_ERROR - 1
-
+ FLAG_MASK = FLAG_NULL_IS_OK - 1,
};
+ Mutex mutex;
+
+ Allocator *allocator = nullptr;
+ bool allocator_is_custom = false;
+
+ LocalVector<Page *> pages;
+ LocalVector<uint32_t> page_bytes;
+ uint32_t max_pages = 0;
+ uint32_t pages_used = 0;
+ bool flushing = false;
+
+#ifdef DEV_ENABLED
+ bool is_current_thread_override = false;
+#endif
+
struct Message {
Callable callable;
int16_t type;
@@ -62,20 +90,21 @@ class MessageQueue {
};
};
- uint8_t *buffer = nullptr;
- uint32_t buffer_end = 0;
- uint32_t buffer_max_used = 0;
- uint32_t buffer_size = 0;
+ _FORCE_INLINE_ void _ensure_first_page() {
+ if (unlikely(pages.is_empty())) {
+ pages.push_back(allocator->alloc());
+ page_bytes.push_back(0);
+ pages_used = 1;
+ }
+ }
- void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error);
+ void _add_page();
- static MessageQueue *singleton;
+ void _call_function(const Callable &p_callable, const Variant *p_args, int p_argcount, bool p_show_error);
- bool flushing = false;
+ String error_text;
public:
- static MessageQueue *get_singleton();
-
Error push_callp(ObjectID p_id, const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error = false);
template <typename... VarArgs>
Error push_call(ObjectID p_id, const StringName &p_method, VarArgs... p_args) {
@@ -87,9 +116,9 @@ public:
return push_callp(p_id, p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
- Error push_notification(ObjectID p_id, int p_notification);
- Error push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value);
Error push_callablep(const Callable &p_callable, const Variant **p_args, int p_argcount, bool p_show_error = false);
+ Error push_set(ObjectID p_id, const StringName &p_prop, const Variant &p_value);
+ Error push_notification(ObjectID p_id, int p_notification);
template <typename... VarArgs>
Error push_callable(const Callable &p_callable, VarArgs... p_args) {
@@ -115,13 +144,29 @@ public:
Error push_notification(Object *p_object, int p_notification);
Error push_set(Object *p_object, const StringName &p_prop, const Variant &p_value);
+ Error flush();
+ void clear();
void statistics();
- void flush();
- bool is_flushing() const;
+ bool has_messages() const;
+ bool is_flushing() const;
int get_max_buffer_usage() const;
+ CallQueue(Allocator *p_custom_allocator = 0, uint32_t p_max_pages = 8192, const String &p_error_text = String());
+ virtual ~CallQueue();
+};
+
+class MessageQueue : public CallQueue {
+ static CallQueue *main_singleton;
+ static thread_local CallQueue *thread_singleton;
+ friend class CallQueue;
+
+public:
+ _FORCE_INLINE_ static CallQueue *get_singleton() { return thread_singleton ? thread_singleton : main_singleton; }
+
+ static void set_thread_singleton_override(CallQueue *p_thread_singleton);
+
MessageQueue();
~MessageQueue();
};
diff --git a/core/object/method_bind.h b/core/object/method_bind.h
index d37479f45b..84f0941b94 100644
--- a/core/object/method_bind.h
+++ b/core/object/method_bind.h
@@ -112,6 +112,8 @@ public:
_FORCE_INLINE_ int get_argument_count() const { return argument_count; };
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const = 0;
+ virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const = 0;
+
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const = 0;
StringName get_name() const;
@@ -162,8 +164,12 @@ public:
}
#endif
+ virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
+ ERR_FAIL_MSG("Validated call can't be used with vararg methods. This is a bug.");
+ }
+
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
- ERR_FAIL(); // Can't call.
+ ERR_FAIL_MSG("ptrcall can't be used with vararg methods. This is a bug.");
}
virtual bool is_const() const { return false; }
@@ -253,6 +259,7 @@ public:
virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override {
return (static_cast<T *>(p_object)->*MethodBindVarArgBase<MethodBindVarArgTR<T, R>, T, R, true>::method)(p_args, p_arg_count, r_error);
}
+
#if defined(SANITIZERS_ENABLED) && defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
@@ -326,6 +333,14 @@ public:
return Variant();
}
+ virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
+#ifdef TYPED_METHOD_BIND
+ call_with_validated_object_instance_args(static_cast<T *>(p_object), method, p_args);
+#else
+ call_with_validated_object_instance_args(reinterpret_cast<MB_T *>(p_object), method, p_args);
+#endif
+ }
+
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
#ifdef TYPED_METHOD_BIND
call_with_ptr_args<T, P...>(static_cast<T *>(p_object), method, p_args);
@@ -393,6 +408,14 @@ public:
return Variant();
}
+ virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
+#ifdef TYPED_METHOD_BIND
+ call_with_validated_object_instance_argsc(static_cast<T *>(p_object), method, p_args);
+#else
+ call_with_validated_object_instance_argsc(reinterpret_cast<MB_T *>(p_object), method, p_args);
+#endif
+ }
+
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
#ifdef TYPED_METHOD_BIND
call_with_ptr_argsc<T, P...>(static_cast<T *>(p_object), method, p_args);
@@ -471,6 +494,14 @@ public:
return ret;
}
+ virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
+#ifdef TYPED_METHOD_BIND
+ call_with_validated_object_instance_args_ret(static_cast<T *>(p_object), method, p_args, r_ret);
+#else
+ call_with_validated_object_instance_args_ret(reinterpret_cast<MB_T *>(p_object), method, p_args, r_ret);
+#endif
+ }
+
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
#ifdef TYPED_METHOD_BIND
call_with_ptr_args_ret<T, R, P...>(static_cast<T *>(p_object), method, p_args, r_ret);
@@ -550,6 +581,14 @@ public:
return ret;
}
+ virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
+#ifdef TYPED_METHOD_BIND
+ call_with_validated_object_instance_args_retc(static_cast<T *>(p_object), method, p_args, r_ret);
+#else
+ call_with_validated_object_instance_args_retc(reinterpret_cast<MB_T *>(p_object), method, p_args, r_ret);
+#endif
+ }
+
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
#ifdef TYPED_METHOD_BIND
call_with_ptr_args_retc<T, R, P...>(static_cast<T *>(p_object), method, p_args, r_ret);
@@ -614,6 +653,10 @@ public:
return Variant();
}
+ virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
+ call_with_validated_variant_args_static_method(function, p_args);
+ }
+
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
(void)p_object;
(void)r_ret;
@@ -677,6 +720,10 @@ public:
return ret;
}
+ virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override {
+ call_with_validated_variant_args_static_method_ret(function, p_args, r_ret);
+ }
+
virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override {
(void)p_object;
call_with_ptr_args_static_method_ret(function, p_args, r_ret);
diff --git a/core/object/object.cpp b/core/object/object.cpp
index c324eab9bb..c76188a2cd 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -38,6 +38,7 @@
#include "core/os/os.h"
#include "core/string/print_string.h"
#include "core/string/translation.h"
+#include "core/templates/local_vector.h"
#include "core/variant/typed_array.h"
#ifdef DEBUG_ENABLED
@@ -195,14 +196,19 @@ bool Object::_predelete() {
_predelete_ok = 1;
notification(NOTIFICATION_PREDELETE, true);
if (_predelete_ok) {
- _class_ptr = nullptr; //must restore so destructors can access class ptr correctly
+ _class_name_ptr = nullptr; // Must restore, so constructors/destructors have proper class name access at each stage.
}
return _predelete_ok;
}
+void Object::cancel_free() {
+ _predelete_ok = false;
+}
+
void Object::_postinitialize() {
- _class_ptr = _get_class_namev();
+ _class_name_ptr = _get_class_namev(); // Set the direct pointer, which is much faster to obtain, but can only happen after postinitialize.
_initialize_classv();
+ _class_name_ptr = nullptr; // May have been called from a constructor.
notification(NOTIFICATION_POSTINITIALIZE);
}
@@ -882,8 +888,13 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) {
if (p_value.get_type() == Variant::NIL) {
if (metadata.has(p_name)) {
metadata.erase(p_name);
- metadata_properties.erase("metadata/" + p_name.operator String());
- notify_property_list_changed();
+
+ const String &sname = p_name;
+ metadata_properties.erase("metadata/" + sname);
+ if (!sname.begins_with("_")) {
+ // Metadata starting with _ don't show up in the inspector, so no need to update.
+ notify_property_list_changed();
+ }
}
return;
}
@@ -892,10 +903,14 @@ void Object::set_meta(const StringName &p_name, const Variant &p_value) {
if (E) {
E->value = p_value;
} else {
- ERR_FAIL_COND(!p_name.operator String().is_valid_identifier());
+ ERR_FAIL_COND_MSG(!p_name.operator String().is_valid_identifier(), "Invalid metadata identifier: '" + p_name + "'.");
Variant *V = &metadata.insert(p_name, p_value)->value;
- metadata_properties["metadata/" + p_name.operator String()] = V;
- notify_property_list_changed();
+
+ const String &sname = p_name;
+ metadata_properties["metadata/" + sname] = V;
+ if (!sname.begins_with("_")) {
+ notify_property_list_changed();
+ }
}
}
@@ -934,8 +949,8 @@ TypedArray<Dictionary> Object::_get_method_list_bind() const {
return ret;
}
-Vector<StringName> Object::_get_meta_list_bind() const {
- Vector<StringName> _metaret;
+TypedArray<StringName> Object::_get_meta_list_bind() const {
+ TypedArray<StringName> _metaret;
for (const KeyValue<StringName, Variant> &K : metadata) {
_metaret.push_back(K.key);
@@ -1012,22 +1027,29 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
return ERR_UNAVAILABLE;
}
- List<_ObjectSignalDisconnectData> disconnect_data;
+ // If this is a ref-counted object, prevent it from being destroyed during signal emission,
+ // which is needed in certain edge cases; e.g., https://github.com/godotengine/godot/issues/73889.
+ Ref<RefCounted> rc = Ref<RefCounted>(Object::cast_to<RefCounted>(this));
- //copy on write will ensure that disconnecting the signal or even deleting the object will not affect the signal calling.
- //this happens automatically and will not change the performance of calling.
- //awesome, isn't it?
- VMap<Callable, SignalData::Slot> slot_map = s->slot_map;
+ List<_ObjectSignalDisconnectData> disconnect_data;
- int ssize = slot_map.size();
+ // Ensure that disconnecting the signal or even deleting the object
+ // will not affect the signal calling.
+ LocalVector<Connection> slot_conns;
+ slot_conns.resize(s->slot_map.size());
+ {
+ uint32_t idx = 0;
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ slot_conns[idx++] = slot_kv.value.conn;
+ }
+ DEV_ASSERT(idx == s->slot_map.size());
+ }
OBJ_DEBUG_LOCK
Error err = OK;
- for (int i = 0; i < ssize; i++) {
- const Connection &c = slot_map.getv(i).conn;
-
+ for (const Connection &c : slot_conns) {
Object *target = c.callable.get_object();
if (!target) {
// Target might have been deleted during signal callback, this is expected and OK.
@@ -1190,8 +1212,8 @@ void Object::get_all_signal_connections(List<Connection> *p_connections) const {
for (const KeyValue<StringName, SignalData> &E : signal_map) {
const SignalData *s = &E.value;
- for (int i = 0; i < s->slot_map.size(); i++) {
- p_connections->push_back(s->slot_map.getv(i).conn);
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ p_connections->push_back(slot_kv.value.conn);
}
}
}
@@ -1202,8 +1224,8 @@ void Object::get_signal_connection_list(const StringName &p_signal, List<Connect
return; //nothing
}
- for (int i = 0; i < s->slot_map.size(); i++) {
- p_connections->push_back(s->slot_map.getv(i).conn);
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ p_connections->push_back(slot_kv.value.conn);
}
}
@@ -1213,8 +1235,8 @@ int Object::get_persistent_signal_connection_count() const {
for (const KeyValue<StringName, SignalData> &E : signal_map) {
const SignalData *s = &E.value;
- for (int i = 0; i < s->slot_map.size(); i++) {
- if (s->slot_map.getv(i).conn.flags & CONNECT_PERSIST) {
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ if (slot_kv.value.conn.flags & CONNECT_PERSIST) {
count += 1;
}
}
@@ -1314,28 +1336,28 @@ void Object::disconnect(const StringName &p_signal, const Callable &p_callable)
_disconnect(p_signal, p_callable);
}
-void Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) {
- ERR_FAIL_COND_MSG(p_callable.is_null(), "Cannot disconnect from '" + p_signal + "': the provided callable is null.");
+bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) {
+ ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot disconnect from '" + p_signal + "': the provided callable is null.");
Object *target_object = p_callable.get_object();
- ERR_FAIL_COND_MSG(!target_object, "Cannot disconnect '" + p_signal + "' from callable '" + p_callable + "': the callable object is null.");
+ ERR_FAIL_COND_V_MSG(!target_object, false, "Cannot disconnect '" + p_signal + "' from callable '" + p_callable + "': the callable object is null.");
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
bool signal_is_valid = ClassDB::has_signal(get_class_name(), p_signal) ||
(!script.is_null() && Ref<Script>(script)->has_script_signal(p_signal));
- ERR_FAIL_COND_MSG(signal_is_valid, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'.");
+ ERR_FAIL_COND_V_MSG(signal_is_valid, false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'.");
}
- ERR_FAIL_COND_MSG(!s, vformat("Disconnecting nonexistent signal '%s' in %s.", p_signal, to_string()));
+ ERR_FAIL_COND_V_MSG(!s, false, vformat("Disconnecting nonexistent signal '%s' in %s.", p_signal, to_string()));
- ERR_FAIL_COND_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), "Disconnecting nonexistent signal '" + p_signal + "', callable: " + p_callable + ".");
+ ERR_FAIL_COND_V_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'.");
SignalData::Slot *slot = &s->slot_map[*p_callable.get_base_comparator()];
if (!p_force) {
slot->reference_count--; // by default is zero, if it was not referenced it will go below it
if (slot->reference_count > 0) {
- return;
+ return false;
}
}
@@ -1346,6 +1368,8 @@ void Object::_disconnect(const StringName &p_signal, const Callable &p_callable,
//not user signal, delete
signal_map.erase(p_signal);
}
+
+ return true;
}
void Object::_set_bind(const StringName &p_set, const Variant &p_value) {
@@ -1550,6 +1574,7 @@ void Object::_bind_methods() {
ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(""));
ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion);
+ ClassDB::bind_method(D_METHOD("cancel_free"), &Object::cancel_free);
ClassDB::add_virtual_method("Object", MethodInfo("free"), false);
@@ -1694,6 +1719,30 @@ uint32_t Object::get_edited_version() const {
}
#endif
+StringName Object::get_class_name_for_extension(const GDExtension *p_library) const {
+ // Only return the class name per the extension if it matches the given p_library.
+ if (_extension && _extension->library == p_library) {
+ return _extension->class_name;
+ }
+
+ // Extensions only have wrapper classes for classes exposed in ClassDB.
+ const StringName *class_name = _get_class_namev();
+ if (ClassDB::is_class_exposed(*class_name)) {
+ return *class_name;
+ }
+
+ // Find the nearest parent class that's exposed.
+ StringName parent_class = ClassDB::get_parent_class(*class_name);
+ while (parent_class != StringName()) {
+ if (ClassDB::is_class_exposed(parent_class)) {
+ return parent_class;
+ }
+ parent_class = ClassDB::get_parent_class(parent_class);
+ }
+
+ return SNAME("Object");
+}
+
void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks) {
// This is only meant to be used on creation by the binder.
ERR_FAIL_COND(_instance_bindings != nullptr);
@@ -1714,7 +1763,7 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi
break;
}
}
- if (unlikely(!binding)) {
+ if (unlikely(!binding && p_callbacks)) {
uint32_t current_size = next_power_of_2(_instance_binding_count);
uint32_t new_size = next_power_of_2(_instance_binding_count + 1);
@@ -1793,26 +1842,30 @@ Object::~Object() {
ERR_PRINT("Object " + to_string() + " was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes.");
}
+ // Drop all connections to the signals of this object.
while (signal_map.size()) {
// Avoid regular iteration so erasing is safe.
KeyValue<StringName, SignalData> &E = *signal_map.begin();
SignalData *s = &E.value;
- //brute force disconnect for performance
- int slot_count = s->slot_map.size();
- const VMap<Callable, SignalData::Slot>::Pair *slot_list = s->slot_map.get_array();
-
- for (int i = 0; i < slot_count; i++) {
- slot_list[i].value.conn.callable.get_object()->connections.erase(slot_list[i].value.cE);
+ for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
+ Object *target = slot_kv.value.conn.callable.get_object();
+ if (likely(target)) {
+ target->connections.erase(slot_kv.value.cE);
+ }
}
signal_map.erase(E.key);
}
- //signals from nodes that connect to this node
+ // Disconnect signals that connect to this object.
while (connections.size()) {
Connection c = connections.front()->get();
- c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true);
+ bool disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true);
+ if (unlikely(!disconnected)) {
+ // If the disconnect has failed, abandon the connection to avoid getting trapped in an infinite loop here.
+ connections.pop_front();
+ }
}
if (_instance_id != ObjectID()) {
diff --git a/core/object/object.h b/core/object/object.h
index 5ec69a371b..a3e9d025ea 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -41,7 +41,6 @@
#include "core/templates/list.h"
#include "core/templates/rb_map.h"
#include "core/templates/safe_refcount.h"
-#include "core/templates/vmap.h"
#include "core/variant/callable_bind.h"
#include "core/variant/variant.h"
@@ -86,6 +85,7 @@ enum PropertyHint {
PROPERTY_HINT_NODE_TYPE, ///< a node object type
PROPERTY_HINT_HIDE_QUATERNION_EDIT, /// Only Node3D::transform should hide the quaternion editor.
PROPERTY_HINT_PASSWORD,
+ PROPERTY_HINT_LAYERS_AVOIDANCE,
PROPERTY_HINT_MAX,
};
@@ -119,6 +119,7 @@ enum PropertyUsageFlags {
PROPERTY_USAGE_EDITOR_INSTANTIATE_OBJECT = 1 << 26, // For Object properties, instantiate them when creating in editor.
PROPERTY_USAGE_EDITOR_BASIC_SETTING = 1 << 27, //for project or editor settings, show when basic settings are selected.
PROPERTY_USAGE_READ_ONLY = 1 << 28, // Mark a property as read-only in the inspector.
+ PROPERTY_USAGE_SECRET = 1 << 29, // Export preset credentials that should be stored separately from the rest of the export config.
PROPERTY_USAGE_DEFAULT = PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_EDITOR,
PROPERTY_USAGE_NO_EDITOR = PROPERTY_USAGE_STORAGE,
@@ -303,8 +304,10 @@ struct MethodInfo {
// API used to extend in GDExtension and other C compatible compiled languages.
class MethodBind;
+class GDExtension;
struct ObjectGDExtension {
+ GDExtension *library = nullptr;
ObjectGDExtension *parent = nullptr;
List<ObjectGDExtension *> children;
StringName parent_class_name;
@@ -376,7 +379,6 @@ private:
#define GDCLASS(m_class, m_inherits) \
private: \
void operator=(const m_class &p_rval) {} \
- mutable StringName _class_name; \
friend class ::ClassDB; \
\
public: \
@@ -388,13 +390,11 @@ public:
return String(#m_class); \
} \
virtual const StringName *_get_class_namev() const override { \
- if (_get_extension()) { \
- return &_get_extension()->class_name; \
- } \
- if (!_class_name) { \
- _class_name = get_class_static(); \
+ static StringName _class_name_static; \
+ if (unlikely(!_class_name_static)) { \
+ StringName::assign_static_unique_class_name(&_class_name_static, #m_class); \
} \
- return &_class_name; \
+ return &_class_name_static; \
} \
static _FORCE_INLINE_ void *get_class_ptr_static() { \
static int ptr; \
@@ -590,7 +590,7 @@ private:
};
MethodInfo user;
- VMap<Callable, Slot> slot_map;
+ HashMap<Callable, Slot, HashableHasher<Callable>> slot_map;
};
HashMap<StringName, SignalData> signal_map;
@@ -614,8 +614,7 @@ private:
Variant script; // Reference does not exist yet, store it in a Variant.
HashMap<StringName, Variant> metadata;
HashMap<StringName, Variant *> metadata_properties;
- mutable StringName _class_name;
- mutable const StringName *_class_ptr = nullptr;
+ mutable const StringName *_class_name_ptr = nullptr;
void _add_user_signal(const String &p_name, const Array &p_args = Array());
bool _has_user_signal(const StringName &p_name) const;
@@ -714,13 +713,14 @@ protected:
Variant _call_deferred_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
virtual const StringName *_get_class_namev() const {
- if (!_class_name) {
- _class_name = get_class_static();
+ static StringName _class_name_static;
+ if (unlikely(!_class_name_static)) {
+ StringName::assign_static_unique_class_name(&_class_name_static, "Object");
}
- return &_class_name;
+ return &_class_name_static;
}
- Vector<StringName> _get_meta_list_bind() const;
+ TypedArray<StringName> _get_meta_list_bind() const;
TypedArray<Dictionary> _get_property_list_bind() const;
TypedArray<Dictionary> _get_method_list_bind() const;
@@ -728,7 +728,7 @@ protected:
friend class ClassDB;
- void _disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force = false);
+ bool _disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force = false);
public: // Should be protected, but bug in clang++.
static void initialize_class();
@@ -788,15 +788,20 @@ public:
_FORCE_INLINE_ const StringName &get_class_name() const {
if (_extension) {
+ // Can't put inside the unlikely as constructor can run it
return _extension->class_name;
}
- if (!_class_ptr) {
+
+ if (unlikely(!_class_name_ptr)) {
+ // While class is initializing / deinitializing, constructors and destructurs
+ // need access to the proper class at the proper stage.
return *_get_class_namev();
- } else {
- return *_class_ptr;
}
+ return *_class_name_ptr;
}
+ StringName get_class_name_for_extension(const GDExtension *p_library) const;
+
/* IAPI */
void set(const StringName &p_name, const Variant &p_value, bool *r_valid = nullptr);
@@ -835,14 +840,21 @@ public:
/* SCRIPT */
- void set_script(const Variant &p_script);
- Variant get_script() const;
+// When in debug, some non-virtual functions can be overridden for multithreaded guards.
+#ifdef DEBUG_ENABLED
+#define MTVIRTUAL virtual
+#else
+#define MTVIRTUAL
+#endif
- bool has_meta(const StringName &p_name) const;
- void set_meta(const StringName &p_name, const Variant &p_value);
- void remove_meta(const StringName &p_name);
- Variant get_meta(const StringName &p_name, const Variant &p_default = Variant()) const;
- void get_meta_list(List<StringName> *p_list) const;
+ MTVIRTUAL void set_script(const Variant &p_script);
+ MTVIRTUAL Variant get_script() const;
+
+ MTVIRTUAL bool has_meta(const StringName &p_name) const;
+ MTVIRTUAL void set_meta(const StringName &p_name, const Variant &p_value);
+ MTVIRTUAL void remove_meta(const StringName &p_name);
+ MTVIRTUAL Variant get_meta(const StringName &p_name, const Variant &p_default = Variant()) const;
+ MTVIRTUAL void get_meta_list(List<StringName> *p_list) const;
#ifdef TOOLS_ENABLED
void set_edited(bool p_edited);
@@ -869,17 +881,17 @@ public:
return emit_signalp(p_name, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args));
}
- Error emit_signalp(const StringName &p_name, const Variant **p_args, int p_argcount);
- bool has_signal(const StringName &p_name) const;
- void get_signal_list(List<MethodInfo> *p_signals) const;
- void get_signal_connection_list(const StringName &p_signal, List<Connection> *p_connections) const;
- void get_all_signal_connections(List<Connection> *p_connections) const;
- int get_persistent_signal_connection_count() const;
- void get_signals_connected_to_this(List<Connection> *p_connections) const;
+ MTVIRTUAL Error emit_signalp(const StringName &p_name, const Variant **p_args, int p_argcount);
+ MTVIRTUAL bool has_signal(const StringName &p_name) const;
+ MTVIRTUAL void get_signal_list(List<MethodInfo> *p_signals) const;
+ MTVIRTUAL void get_signal_connection_list(const StringName &p_signal, List<Connection> *p_connections) const;
+ MTVIRTUAL void get_all_signal_connections(List<Connection> *p_connections) const;
+ MTVIRTUAL int get_persistent_signal_connection_count() const;
+ MTVIRTUAL void get_signals_connected_to_this(List<Connection> *p_connections) const;
- Error connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags = 0);
- void disconnect(const StringName &p_signal, const Callable &p_callable);
- bool is_connected(const StringName &p_signal, const Callable &p_callable) const;
+ MTVIRTUAL Error connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags = 0);
+ MTVIRTUAL void disconnect(const StringName &p_signal, const Callable &p_callable);
+ MTVIRTUAL bool is_connected(const StringName &p_signal, const Callable &p_callable) const;
template <typename... VarArgs>
void call_deferred(const StringName &p_name, VarArgs... p_args) {
@@ -924,6 +936,8 @@ public:
_ALWAYS_INLINE_ bool is_ref_counted() const { return type_is_reference; }
+ void cancel_free();
+
Object();
virtual ~Object();
};
diff --git a/core/object/ref_counted.h b/core/object/ref_counted.h
index ed48f4065c..3386514706 100644
--- a/core/object/ref_counted.h
+++ b/core/object/ref_counted.h
@@ -97,26 +97,15 @@ public:
return reference != p_r.reference;
}
- _FORCE_INLINE_ T *operator->() {
+ _FORCE_INLINE_ T *operator*() const {
return reference;
}
- _FORCE_INLINE_ T *operator*() {
+ _FORCE_INLINE_ T *operator->() const {
return reference;
}
- _FORCE_INLINE_ const T *operator->() const {
- return reference;
- }
-
- _FORCE_INLINE_ const T *ptr() const {
- return reference;
- }
- _FORCE_INLINE_ T *ptr() {
- return reference;
- }
-
- _FORCE_INLINE_ const T *operator*() const {
+ _FORCE_INLINE_ T *ptr() const {
return reference;
}
@@ -253,8 +242,11 @@ public:
template <class T>
struct PtrToArg<Ref<T>> {
_FORCE_INLINE_ static Ref<T> convert(const void *p_ptr) {
+ if (p_ptr == nullptr) {
+ return Ref<T>();
+ }
// p_ptr points to a RefCounted object
- return Ref<T>(const_cast<T *>(reinterpret_cast<const T *>(p_ptr)));
+ return Ref<T>(const_cast<T *>(*reinterpret_cast<T *const *>(p_ptr)));
}
typedef Ref<T> EncodeT;
@@ -270,8 +262,11 @@ struct PtrToArg<const Ref<T> &> {
typedef Ref<T> EncodeT;
_FORCE_INLINE_ static Ref<T> convert(const void *p_ptr) {
+ if (p_ptr == nullptr) {
+ return Ref<T>();
+ }
// p_ptr points to a RefCounted object
- return Ref<T>((T *)p_ptr);
+ return Ref<T>(*((T *const *)p_ptr));
}
};
@@ -295,4 +290,16 @@ struct GetTypeInfo<const Ref<T> &> {
}
};
+template <class T>
+struct VariantInternalAccessor<Ref<T>> {
+ static _FORCE_INLINE_ Ref<T> get(const Variant *v) { return Ref<T>(*VariantInternal::get_object(v)); }
+ static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::refcounted_object_assign(v, p_ref.ptr()); }
+};
+
+template <class T>
+struct VariantInternalAccessor<const Ref<T> &> {
+ static _FORCE_INLINE_ Ref<T> get(const Variant *v) { return Ref<T>(*VariantInternal::get_object(v)); }
+ static _FORCE_INLINE_ void set(Variant *v, const Ref<T> &p_ref) { VariantInternal::refcounted_object_assign(v, p_ref.ptr()); }
+};
+
#endif // REF_COUNTED_H
diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp
index 71f40660f4..6f047d80aa 100644
--- a/core/object/script_language.cpp
+++ b/core/object/script_language.cpp
@@ -34,7 +34,6 @@
#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/debugger/script_debugger.h"
-#include "core/variant/typed_array.h"
#include <stdint.h>
@@ -461,6 +460,52 @@ void ScriptLanguage::get_core_type_words(List<String> *p_core_type_words) const
void ScriptLanguage::frame() {
}
+TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_characteristics(const String &p_base) {
+ // Return characacteristics of the match found by order of importance.
+ // Matches will be ranked by a lexicographical order on the vector returned by this function.
+ // The lower values indicate better matches and that they should go before in the order of appearance.
+ if (last_matches == matches) {
+ return charac;
+ }
+ charac.clear();
+ // Ensure base is not empty and at the same time that matches is not empty too.
+ if (p_base.length() == 0) {
+ last_matches = matches;
+ charac.push_back(location);
+ return charac;
+ }
+ charac.push_back(matches.size());
+ charac.push_back((matches[0].first == 0) ? 0 : 1);
+ charac.push_back(location);
+ const char32_t *target_char = &p_base[0];
+ int bad_case = 0;
+ for (const Pair<int, int> &match_segment : matches) {
+ const char32_t *string_to_complete_char = &display[match_segment.first];
+ for (int j = 0; j < match_segment.second; j++, string_to_complete_char++, target_char++) {
+ if (*string_to_complete_char != *target_char) {
+ bad_case++;
+ }
+ }
+ }
+ charac.push_back(bad_case);
+ charac.push_back(matches[0].first);
+ last_matches = matches;
+ return charac;
+}
+
+void ScriptLanguage::CodeCompletionOption::clear_characteristics() {
+ charac = TypedArray<int>();
+}
+
+TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_cached_characteristics() const {
+ // Only returns the cached value and warns if it was not updated since the last change of matches.
+ if (last_matches != matches) {
+ WARN_PRINT("Characteristics are not up to date.");
+ }
+
+ return charac;
+}
+
bool PlaceHolderScriptInstance::set(const StringName &p_name, const Variant &p_value) {
if (script->is_placeholder_fallback_enabled()) {
return false;
diff --git a/core/object/script_language.h b/core/object/script_language.h
index 696c9a94a5..c22890e30a 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -35,6 +35,7 @@
#include "core/io/resource.h"
#include "core/templates/pair.h"
#include "core/templates/rb_map.h"
+#include "core/variant/typed_array.h"
class ScriptLanguage;
template <typename T>
@@ -305,8 +306,8 @@ public:
virtual Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { return ERR_UNAVAILABLE; }
virtual bool overrides_external_editor() { return false; }
- /* Keep enum in Sync with: */
- /* /scene/gui/code_edit.h - CodeEdit::CodeCompletionKind */
+ // Keep enums in sync with:
+ // scene/gui/code_edit.h - CodeEdit::CodeCompletionKind
enum CodeCompletionKind {
CODE_COMPLETION_KIND_CLASS,
CODE_COMPLETION_KIND_FUNCTION,
@@ -321,6 +322,7 @@ public:
CODE_COMPLETION_KIND_MAX
};
+ // scene/gui/code_edit.h - CodeEdit::CodeCompletionLocation
enum CodeCompletionLocation {
LOCATION_LOCAL = 0,
LOCATION_PARENT_MASK = 1 << 8,
@@ -336,6 +338,7 @@ public:
Ref<Resource> icon;
Variant default_value;
Vector<Pair<int, int>> matches;
+ Vector<Pair<int, int>> last_matches = { { -1, -1 } }; // This value correspond to an impossible match
int location = LOCATION_OTHER;
CodeCompletionOption() {}
@@ -346,6 +349,13 @@ public:
kind = p_kind;
location = p_location;
}
+
+ TypedArray<int> get_option_characteristics(const String &p_base);
+ void clear_characteristics();
+ TypedArray<int> get_option_cached_characteristics() const;
+
+ private:
+ TypedArray<int> charac;
};
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<CodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; }
diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h
index 79cf712119..1a0ec29479 100644
--- a/core/object/script_language_extension.h
+++ b/core/object/script_language_extension.h
@@ -651,7 +651,7 @@ public:
#ifdef TOOLS_ENABLED
Ref<Script> script = get_script();
- if (script->is_valid() && pcount > 0) {
+ if (script.is_valid() && pcount > 0) {
p_list->push_back(script->get_class_category());
}
#endif // TOOLS_ENABLED
diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp
index 077929351e..f04961c760 100644
--- a/core/object/undo_redo.cpp
+++ b/core/object/undo_redo.cpp
@@ -80,14 +80,14 @@ bool UndoRedo::_redo(bool p_execute) {
return true;
}
-void UndoRedo::create_action(const String &p_name, MergeMode p_mode) {
+void UndoRedo::create_action(const String &p_name, MergeMode p_mode, bool p_backward_undo_ops) {
uint64_t ticks = OS::get_singleton()->get_ticks_msec();
if (action_level == 0) {
_discard_redo();
// Check if the merge operation is valid
- if (p_mode != MERGE_DISABLE && actions.size() && actions[actions.size() - 1].name == p_name && actions[actions.size() - 1].last_tick + 800 > ticks) {
+ if (p_mode != MERGE_DISABLE && actions.size() && actions[actions.size() - 1].name == p_name && actions[actions.size() - 1].backward_undo_ops == p_backward_undo_ops && actions[actions.size() - 1].last_tick + 800 > ticks) {
current_action = actions.size() - 2;
if (p_mode == MERGE_ENDS) {
@@ -108,12 +108,18 @@ void UndoRedo::create_action(const String &p_name, MergeMode p_mode) {
actions.write[actions.size() - 1].last_tick = ticks;
+ // Revert reverse from previous commit.
+ if (actions[actions.size() - 1].backward_undo_ops) {
+ actions.write[actions.size() - 1].undo_ops.reverse();
+ }
+
merge_mode = p_mode;
merging = true;
} else {
Action new_action;
new_action.name = p_name;
new_action.last_tick = ticks;
+ new_action.backward_undo_ops = p_backward_undo_ops;
actions.push_back(new_action);
merge_mode = MERGE_DISABLE;
@@ -292,6 +298,10 @@ void UndoRedo::commit_action(bool p_execute) {
merging = false;
}
+ if (actions[actions.size() - 1].backward_undo_ops) {
+ actions.write[actions.size() - 1].undo_ops.reverse();
+ }
+
committing++;
_redo(p_execute); // perform action
committing--;
@@ -302,6 +312,11 @@ void UndoRedo::commit_action(bool p_execute) {
}
void UndoRedo::_process_operation_list(List<Operation>::Element *E) {
+ const int PREALLOCATE_ARGS_COUNT = 16;
+
+ LocalVector<const Variant *> args;
+ args.reserve(PREALLOCATE_ARGS_COUNT);
+
for (; E; E = E->next()) {
Operation &op = E->get();
@@ -337,12 +352,13 @@ void UndoRedo::_process_operation_list(List<Operation>::Element *E) {
if (binds.is_empty()) {
method_callback(method_callback_ud, obj, op.name, nullptr, 0);
} else {
- const Variant **args = (const Variant **)alloca(sizeof(const Variant **) * binds.size());
+ args.clear();
+
for (int i = 0; i < binds.size(); i++) {
- args[i] = (const Variant *)&binds[i];
+ args.push_back(&binds[i]);
}
- method_callback(method_callback_ud, obj, op.name, args, binds.size());
+ method_callback(method_callback_ud, obj, op.name, args.ptr(), binds.size());
}
}
} break;
@@ -458,7 +474,7 @@ UndoRedo::~UndoRedo() {
}
void UndoRedo::_bind_methods() {
- ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode"), &UndoRedo::create_action, DEFVAL(MERGE_DISABLE));
+ ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode", "backward_undo_ops"), &UndoRedo::create_action, DEFVAL(MERGE_DISABLE), DEFVAL(false));
ClassDB::bind_method(D_METHOD("commit_action", "execute"), &UndoRedo::commit_action, DEFVAL(true));
ClassDB::bind_method(D_METHOD("is_committing_action"), &UndoRedo::is_committing_action);
diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h
index 5dc9f2966c..2ee17867f2 100644
--- a/core/object/undo_redo.h
+++ b/core/object/undo_redo.h
@@ -72,7 +72,8 @@ private:
String name;
List<Operation> do_ops;
List<Operation> undo_ops;
- uint64_t last_tick;
+ uint64_t last_tick = 0;
+ bool backward_undo_ops = false;
};
Vector<Action> actions;
@@ -102,7 +103,7 @@ protected:
static void _bind_methods();
public:
- void create_action(const String &p_name = "", MergeMode p_mode = MERGE_DISABLE);
+ void create_action(const String &p_name = "", MergeMode p_mode = MERGE_DISABLE, bool p_backward_undo_ops = false);
void add_do_method(const Callable &p_callable);
void add_undo_method(const Callable &p_callable);
diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp
index 721c8d0a10..d285be3e70 100644
--- a/core/object/worker_thread_pool.cpp
+++ b/core/object/worker_thread_pool.cpp
@@ -31,6 +31,7 @@
#include "worker_thread_pool.h"
#include "core/os/os.h"
+#include "core/os/thread_safe.h"
void WorkerThreadPool::Task::free_template_userdata() {
ERR_FAIL_COND(!template_userdata);
@@ -51,6 +52,23 @@ void WorkerThreadPool::_process_task_queue() {
void WorkerThreadPool::_process_task(Task *p_task) {
bool low_priority = p_task->low_priority;
+ int pool_thread_index = -1;
+ Task *prev_low_prio_task = nullptr; // In case this is recursively called.
+
+ if (!use_native_low_priority_threads) {
+ pool_thread_index = thread_ids[Thread::get_caller_id()];
+ ThreadData &curr_thread = threads[pool_thread_index];
+ task_mutex.lock();
+ p_task->pool_thread_index = pool_thread_index;
+ if (low_priority) {
+ low_priority_tasks_running++;
+ prev_low_prio_task = curr_thread.current_low_prio_task;
+ curr_thread.current_low_prio_task = p_task;
+ } else {
+ curr_thread.current_low_prio_task = nullptr;
+ }
+ task_mutex.unlock();
+ }
if (p_task->group) {
// Handling a group
@@ -126,33 +144,51 @@ void WorkerThreadPool::_process_task(Task *p_task) {
p_task->callable.callp(nullptr, 0, ret, ce);
}
+ task_mutex.lock();
p_task->completed = true;
- p_task->done_semaphore.post();
+ for (uint8_t i = 0; i < p_task->waiting; i++) {
+ p_task->done_semaphore.post();
+ }
+ if (!use_native_low_priority_threads) {
+ p_task->pool_thread_index = -1;
+ }
+ task_mutex.unlock(); // Keep mutex down to here since on unlock the task may be freed.
}
- if (!use_native_low_priority_threads && low_priority) {
- // A low prioriry task was freed, so see if we can move a pending one to the high priority queue.
+ // Task may have been freed by now (all callers notified).
+ p_task = nullptr;
+
+ if (!use_native_low_priority_threads) {
bool post = false;
task_mutex.lock();
- if (low_priority_task_queue.first()) {
- Task *low_prio_task = low_priority_task_queue.first()->self();
- low_priority_task_queue.remove(low_priority_task_queue.first());
- task_queue.add_last(&low_prio_task->task_elem);
- post = true;
- } else {
- low_priority_threads_used.decrement();
+ ThreadData &curr_thread = threads[pool_thread_index];
+ curr_thread.current_low_prio_task = prev_low_prio_task;
+ if (low_priority) {
+ low_priority_threads_used--;
+ low_priority_tasks_running--;
+ // A low prioriry task was freed, so see if we can move a pending one to the high priority queue.
+ if (_try_promote_low_priority_task()) {
+ post = true;
+ }
+
+ if (low_priority_tasks_awaiting_others == low_priority_tasks_running) {
+ _prevent_low_prio_saturation_deadlock();
+ }
}
- task_mutex.lock();
+ task_mutex.unlock();
if (post) {
task_available_semaphore.post();
}
+
+ // Engine/user tasks can set-and-forget, so we must be sure it's back to normal by the end of the task.
+ set_current_thread_safe_for_nodes(false);
}
}
void WorkerThreadPool::_thread_function(void *p_user) {
while (true) {
singleton->task_available_semaphore.wait();
- if (singleton->exit_threads.is_set()) {
+ if (singleton->exit_threads) {
break;
}
singleton->_process_task_queue();
@@ -165,17 +201,29 @@ void WorkerThreadPool::_native_low_priority_thread_function(void *p_user) {
}
void WorkerThreadPool::_post_task(Task *p_task, bool p_high_priority) {
+ // Fall back to processing on the calling thread if there are no worker threads.
+ // Separated into its own variable to make it easier to extend this logic
+ // in custom builds.
+ bool process_on_calling_thread = threads.size() == 0;
+ if (process_on_calling_thread) {
+ _process_task(p_task);
+ return;
+ }
+
task_mutex.lock();
p_task->low_priority = !p_high_priority;
if (!p_high_priority && use_native_low_priority_threads) {
- task_mutex.unlock();
p_task->low_priority_thread = native_thread_allocator.alloc();
- p_task->low_priority_thread->start(_native_low_priority_thread_function, p_task); // Pask task directly to thread.
+ task_mutex.unlock();
- } else if (p_high_priority || low_priority_threads_used.get() < max_low_priority_threads) {
+ if (p_task->group) {
+ p_task->group->low_priority_native_tasks.push_back(p_task);
+ }
+ p_task->low_priority_thread->start(_native_low_priority_thread_function, p_task); // Pask task directly to thread.
+ } else if (p_high_priority || low_priority_threads_used < max_low_priority_threads) {
task_queue.add_last(&p_task->task_elem);
if (!p_high_priority) {
- low_priority_threads_used.increment();
+ low_priority_threads_used++;
}
task_mutex.unlock();
task_available_semaphore.post();
@@ -186,6 +234,35 @@ void WorkerThreadPool::_post_task(Task *p_task, bool p_high_priority) {
}
}
+bool WorkerThreadPool::_try_promote_low_priority_task() {
+ if (low_priority_task_queue.first()) {
+ Task *low_prio_task = low_priority_task_queue.first()->self();
+ low_priority_task_queue.remove(low_priority_task_queue.first());
+ task_queue.add_last(&low_prio_task->task_elem);
+ low_priority_threads_used++;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void WorkerThreadPool::_prevent_low_prio_saturation_deadlock() {
+ if (low_priority_tasks_awaiting_others == low_priority_tasks_running) {
+#ifdef DEV_ENABLED
+ print_verbose("WorkerThreadPool: Low-prio slots saturated with tasks all waiting for other low-prio tasks. Attempting to avoid deadlock by scheduling one extra task.");
+#endif
+ // In order not to create dependency cycles, we can only schedule the next one.
+ // We'll keep doing the same until the deadlock is broken,
+ SelfList<Task> *to_promote = low_priority_task_queue.first();
+ if (to_promote) {
+ low_priority_task_queue.remove(to_promote);
+ task_queue.add_last(to_promote);
+ low_priority_threads_used++;
+ task_available_semaphore.post();
+ }
+ }
+}
+
WorkerThreadPool::TaskID WorkerThreadPool::add_native_task(void (*p_func)(void *), void *p_userdata, bool p_high_priority, const String &p_description) {
return _add_task(Callable(), p_func, p_userdata, nullptr, p_high_priority, p_description);
}
@@ -226,58 +303,113 @@ bool WorkerThreadPool::is_task_completed(TaskID p_task_id) const {
return completed;
}
-void WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
+Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
task_mutex.lock();
Task **taskp = tasks.getptr(p_task_id);
if (!taskp) {
task_mutex.unlock();
- ERR_FAIL_MSG("Invalid Task ID"); // Invalid task
+ ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Invalid Task ID"); // Invalid task
}
Task *task = *taskp;
- if (task->waiting) {
- String description = task->description;
- task_mutex.unlock();
- if (description.is_empty()) {
- ERR_FAIL_MSG("Another thread is waiting on this task: " + itos(p_task_id)); // Invalid task
- } else {
- ERR_FAIL_MSG("Another thread is waiting on this task: " + description + " (" + itos(p_task_id) + ")"); // Invalid task
+ if (!task->completed) {
+ if (!use_native_low_priority_threads && task->pool_thread_index != -1) { // Otherwise, it's not running yet.
+ int caller_pool_th_index = thread_ids.has(Thread::get_caller_id()) ? thread_ids[Thread::get_caller_id()] : -1;
+ if (caller_pool_th_index == task->pool_thread_index) {
+ // Deadlock prevention.
+ // Waiting for a task run on this same thread? That means the task to be awaited started waiting as well
+ // and another task was run to make use of the thread in the meantime, with enough bad luck as to
+ // the need to wait for the original task arose in turn.
+ // In other words, the task we want to wait for is buried in the stack.
+ // Let's report the caller about the issue to it handles as it sees fit.
+ task_mutex.unlock();
+ return ERR_BUSY;
+ }
}
- }
- task->waiting = true;
+ task->waiting++;
+
+ bool is_low_prio_waiting_for_another = false;
+ if (!use_native_low_priority_threads) {
+ // Deadlock prevention:
+ // If all low-prio tasks are waiting for other low-prio tasks and there are no more free low-prio slots,
+ // we have a no progressable situation. We can apply a workaround, consisting in promoting an awaited queued
+ // low-prio task to the schedule queue so it can run and break the "impasse".
+ // NOTE: A similar reasoning could be made about high priority tasks, but there are usually much more
+ // than low-prio. Therefore, a deadlock there would only happen when dealing with a very complex task graph
+ // or when there are too few worker threads (limited platforms or exotic settings). If that turns out to be
+ // an issue in the real world, a further fix can be applied against that.
+ if (task->low_priority) {
+ bool awaiter_is_a_low_prio_task = thread_ids.has(Thread::get_caller_id()) && threads[thread_ids[Thread::get_caller_id()]].current_low_prio_task;
+ if (awaiter_is_a_low_prio_task) {
+ is_low_prio_waiting_for_another = true;
+ low_priority_tasks_awaiting_others++;
+ if (low_priority_tasks_awaiting_others == low_priority_tasks_running) {
+ _prevent_low_prio_saturation_deadlock();
+ }
+ }
+ }
+ }
- task_mutex.unlock();
+ task_mutex.unlock();
- if (use_native_low_priority_threads && task->low_priority) {
- task->low_priority_thread->wait_to_finish();
- native_thread_allocator.free(task->low_priority_thread);
- } else {
- int *index = thread_ids.getptr(Thread::get_caller_id());
-
- if (index) {
- // We are an actual process thread, we must not be blocked so continue processing stuff if available.
- while (true) {
- if (task->done_semaphore.try_wait()) {
- // If done, exit
- break;
- }
- if (task_available_semaphore.try_wait()) {
- // Solve tasks while they are around.
- _process_task_queue();
- continue;
+ if (use_native_low_priority_threads && task->low_priority) {
+ task->done_semaphore.wait();
+ } else {
+ bool current_is_pool_thread = thread_ids.has(Thread::get_caller_id());
+ if (current_is_pool_thread) {
+ // We are an actual process thread, we must not be blocked so continue processing stuff if available.
+ bool must_exit = false;
+ while (true) {
+ if (task->done_semaphore.try_wait()) {
+ // If done, exit
+ break;
+ }
+ if (!must_exit) {
+ if (task_available_semaphore.try_wait()) {
+ if (exit_threads) {
+ must_exit = true;
+ } else {
+ // Solve tasks while they are around.
+ _process_task_queue();
+ continue;
+ }
+ } else if (!use_native_low_priority_threads && task->low_priority) {
+ // A low prioriry task started waiting, so see if we can move a pending one to the high priority queue.
+ task_mutex.lock();
+ bool post = _try_promote_low_priority_task();
+ task_mutex.unlock();
+ if (post) {
+ task_available_semaphore.post();
+ }
+ }
+ }
+ OS::get_singleton()->delay_usec(1); // Microsleep, this could be converted to waiting for multiple objects in supported platforms for a bit more performance.
}
- OS::get_singleton()->delay_usec(1); // Microsleep, this could be converted to waiting for multiple objects in supported platforms for a bit more performance.
+ } else {
+ task->done_semaphore.wait();
}
- } else {
- task->done_semaphore.wait();
}
+
+ task_mutex.lock();
+ if (is_low_prio_waiting_for_another) {
+ low_priority_tasks_awaiting_others--;
+ }
+
+ task->waiting--;
+ }
+
+ if (task->waiting == 0) {
+ if (use_native_low_priority_threads && task->low_priority) {
+ task->low_priority_thread->wait_to_finish();
+ native_thread_allocator.free(task->low_priority_thread);
+ }
+ tasks.erase(p_task_id);
+ task_allocator.free(task);
}
- task_mutex.lock();
- tasks.erase(p_task_id);
- task_allocator.free(task);
task_mutex.unlock();
+ return OK;
}
WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
@@ -322,15 +454,8 @@ WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_ca
groups[id] = group;
task_mutex.unlock();
- if (!p_high_priority && use_native_low_priority_threads) {
- group->low_priority_native_tasks.resize(p_tasks);
- }
-
for (int i = 0; i < p_tasks; i++) {
_post_task(tasks_posted[i], p_high_priority);
- if (!p_high_priority && use_native_low_priority_threads) {
- group->low_priority_native_tasks[i] = tasks_posted[i];
- }
}
return id;
@@ -379,8 +504,8 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) {
if (group->low_priority_native_tasks.size() > 0) {
for (Task *task : group->low_priority_native_tasks) {
task->low_priority_thread->wait_to_finish();
- native_thread_allocator.free(task->low_priority_thread);
task_mutex.lock();
+ native_thread_allocator.free(task->low_priority_thread);
task_allocator.free(task);
task_mutex.unlock();
}
@@ -416,7 +541,7 @@ void WorkerThreadPool::init(int p_thread_count, bool p_use_native_threads_low_pr
if (p_use_native_threads_low_priority) {
max_low_priority_threads = 0;
} else {
- max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count);
+ max_low_priority_threads = CLAMP(p_thread_count * p_low_priority_task_ratio, 1, p_thread_count - 1);
}
use_native_low_priority_threads = p_use_native_threads_low_priority;
@@ -443,7 +568,7 @@ void WorkerThreadPool::finish() {
}
task_mutex.unlock();
- exit_threads.set_to(true);
+ exit_threads = true;
for (uint32_t i = 0; i < threads.size(); i++) {
task_available_semaphore.post();
diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h
index c62e05fc28..d4d9387765 100644
--- a/core/object/worker_thread_pool.h
+++ b/core/object/worker_thread_pool.h
@@ -81,10 +81,11 @@ private:
bool completed = false;
Group *group = nullptr;
SelfList<Task> task_elem;
- bool waiting = false; // Waiting for completion
+ uint32_t waiting = 0;
bool low_priority = false;
BaseTemplateUserdata *template_userdata = nullptr;
Thread *low_priority_thread = nullptr;
+ int pool_thread_index = -1;
void free_template_userdata();
Task() :
@@ -104,10 +105,11 @@ private:
struct ThreadData {
uint32_t index;
Thread thread;
+ Task *current_low_prio_task = nullptr;
};
TightLocalVector<ThreadData> threads;
- SafeFlag exit_threads;
+ bool exit_threads = false;
HashMap<Thread::ID, int> thread_ids;
HashMap<TaskID, Task *> tasks;
@@ -115,7 +117,9 @@ private:
bool use_native_low_priority_threads = false;
uint32_t max_low_priority_threads = 0;
- SafeNumeric<uint32_t> low_priority_threads_used;
+ uint32_t low_priority_threads_used = 0;
+ uint32_t low_priority_tasks_running = 0;
+ uint32_t low_priority_tasks_awaiting_others = 0;
uint64_t last_task = 1;
@@ -127,6 +131,9 @@ private:
void _post_task(Task *p_task, bool p_high_priority);
+ bool _try_promote_low_priority_task();
+ void _prevent_low_prio_saturation_deadlock();
+
static WorkerThreadPool *singleton;
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);
@@ -169,7 +176,7 @@ public:
TaskID add_task(const Callable &p_action, bool p_high_priority = false, const String &p_description = String());
bool is_task_completed(TaskID p_task_id) const;
- void wait_for_task_completion(TaskID p_task_id);
+ Error wait_for_task_completion(TaskID p_task_id);
template <class C, class M, class U>
GroupID add_template_group_task(C *p_instance, M p_method, U p_userdata, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String()) {
diff --git a/core/os/keyboard.cpp b/core/os/keyboard.cpp
index 25a4b320cd..1a51624030 100644
--- a/core/os/keyboard.cpp
+++ b/core/os/keyboard.cpp
@@ -367,11 +367,11 @@ String keycode_get_string(Key p_code) {
codestr += "+";
}
if ((p_code & KeyModifierMask::CMD_OR_CTRL) != Key::NONE) {
-#ifdef MACOS_ENABLED
- codestr += find_keycode_name(Key::META);
-#else
- codestr += find_keycode_name(Key::CTRL);
-#endif
+ if (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) {
+ codestr += find_keycode_name(Key::META);
+ } else {
+ codestr += find_keycode_name(Key::CTRL);
+ }
codestr += "+";
}
if ((p_code & KeyModifierMask::CTRL) != Key::NONE) {
@@ -400,17 +400,38 @@ String keycode_get_string(Key p_code) {
return codestr;
}
-Key find_keycode(const String &p_code) {
+Key find_keycode(const String &p_codestr) {
+ Key keycode = Key::NONE;
+ Vector<String> code_parts = p_codestr.split("+");
+ if (code_parts.size() < 1) {
+ return keycode;
+ }
+
+ String last_part = code_parts[code_parts.size() - 1];
const _KeyCodeText *kct = &_keycodes[0];
while (kct->text) {
- if (p_code.nocasecmp_to(kct->text) == 0) {
- return kct->code;
+ if (last_part.nocasecmp_to(kct->text) == 0) {
+ keycode = kct->code;
+ break;
}
kct++;
}
- return Key::NONE;
+ for (int part = 0; part < code_parts.size() - 1; part++) {
+ String code_part = code_parts[part];
+ if (code_part.nocasecmp_to(find_keycode_name(Key::SHIFT)) == 0) {
+ keycode |= KeyModifierMask::SHIFT;
+ } else if (code_part.nocasecmp_to(find_keycode_name(Key::CTRL)) == 0) {
+ keycode |= KeyModifierMask::CTRL;
+ } else if (code_part.nocasecmp_to(find_keycode_name(Key::META)) == 0) {
+ keycode |= KeyModifierMask::META;
+ } else if (code_part.nocasecmp_to(find_keycode_name(Key::ALT)) == 0) {
+ keycode |= KeyModifierMask::ALT;
+ }
+ }
+
+ return keycode;
}
const char *find_keycode_name(Key p_keycode) {
diff --git a/core/os/keyboard.h b/core/os/keyboard.h
index 84017e89a6..cf276dc49f 100644
--- a/core/os/keyboard.h
+++ b/core/os/keyboard.h
@@ -330,7 +330,7 @@ constexpr KeyModifierMask operator|(KeyModifierMask a, KeyModifierMask b) {
String keycode_get_string(Key p_code);
bool keycode_has_unicode(Key p_keycode);
-Key find_keycode(const String &p_code);
+Key find_keycode(const String &p_codestr);
const char *find_keycode_name(Key p_keycode);
int keycode_get_count();
int keycode_get_value_by_index(int p_index);
diff --git a/core/os/mutex.h b/core/os/mutex.h
index 90cc1632e8..cee0f8af74 100644
--- a/core/os/mutex.h
+++ b/core/os/mutex.h
@@ -119,8 +119,25 @@ class MutexLock {
public:
_ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex) :
+ lock(p_mutex.mutex){};
+};
+
+// This specialization is needed so manual locking and MutexLock can be used
+// at the same time on a SafeBinaryMutex.
+template <int Tag>
+class MutexLock<SafeBinaryMutex<Tag>> {
+ friend class ConditionVariable;
+
+ std::unique_lock<std::mutex> lock;
+
+public:
+ _ALWAYS_INLINE_ explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) :
lock(p_mutex.mutex) {
- }
+ SafeBinaryMutex<Tag>::count++;
+ };
+ _ALWAYS_INLINE_ ~MutexLock() {
+ SafeBinaryMutex<Tag>::count--;
+ };
};
using Mutex = MutexImpl<std::recursive_mutex>; // Recursive, for general use
diff --git a/core/os/os.cpp b/core/os/os.cpp
index ef7d860d19..5704ef7a40 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -34,6 +34,7 @@
#include "core/input/input.h"
#include "core/io/dir_access.h"
#include "core/io/file_access.h"
+#include "core/io/json.h"
#include "core/os/midi_driver.h"
#include "core/version_generated.gen.h"
@@ -72,6 +73,10 @@ void OS::add_logger(Logger *p_logger) {
}
}
+String OS::get_identifier() const {
+ return get_name().to_lower();
+}
+
void OS::print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, Logger::ErrorType p_type) {
if (!_stderr_enabled) {
return;
@@ -147,6 +152,14 @@ int OS::get_low_processor_usage_mode_sleep_usec() const {
return low_processor_usage_mode_sleep_usec;
}
+void OS::set_delta_smoothing(bool p_enabled) {
+ _delta_smoothing_enabled = p_enabled;
+}
+
+bool OS::is_delta_smoothing_enabled() const {
+ return _delta_smoothing_enabled;
+}
+
String OS::get_executable_path() const {
return _execpath;
}
@@ -281,6 +294,15 @@ Error OS::shell_open(String p_uri) {
return ERR_UNAVAILABLE;
}
+Error OS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
+ if (!p_path.begins_with("file://")) {
+ p_path = String("file://") + p_path;
+ }
+ if (!p_path.ends_with("/")) {
+ p_path = p_path.get_base_dir();
+ }
+ return shell_open(p_path);
+}
// implement these with the canvas?
uint64_t OS::get_static_memory_usage() const {
@@ -295,8 +317,15 @@ Error OS::set_cwd(const String &p_cwd) {
return ERR_CANT_OPEN;
}
-uint64_t OS::get_free_static_memory() const {
- return Memory::get_mem_available();
+Dictionary OS::get_memory_info() const {
+ Dictionary meminfo;
+
+ meminfo["physical"] = -1;
+ meminfo["free"] = -1;
+ meminfo["available"] = -1;
+ meminfo["stack"] = -1;
+
+ return meminfo;
}
void OS::yield() {
@@ -341,13 +370,7 @@ void OS::set_has_server_feature_callback(HasServerFeatureCallback p_callback) {
bool OS::has_feature(const String &p_feature) {
// Feature tags are always lowercase for consistency.
- if (p_feature == get_name().to_lower()) {
- return true;
- }
-
- // Catch-all `linuxbsd` feature tag that matches on both Linux and BSD.
- // This is the one exposed in the project settings dialog.
- if (p_feature == "linuxbsd" && (get_name() == "Linux" || get_name() == "FreeBSD" || get_name() == "NetBSD" || get_name() == "OpenBSD" || get_name() == "BSD")) {
+ if (p_feature == get_identifier()) {
return true;
}
@@ -553,6 +576,10 @@ void OS::add_frame_delay(bool p_can_draw) {
}
}
+Error OS::setup_remote_filesystem(const String &p_server_host, int p_port, const String &p_password, String &r_project_path) {
+ return default_rfs.synchronize_with_server(p_server_host, p_port, p_password, r_project_path);
+}
+
OS::PreferredTextureFormat OS::get_preferred_texture_format() const {
#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64)
return PREFERRED_TEXTURE_FORMAT_ETC2_ASTC; // By rule, ARM hardware uses ETC texture compression.
@@ -563,6 +590,59 @@ OS::PreferredTextureFormat OS::get_preferred_texture_format() const {
#endif
}
+void OS::set_use_benchmark(bool p_use_benchmark) {
+ use_benchmark = p_use_benchmark;
+}
+
+bool OS::is_use_benchmark_set() {
+ return use_benchmark;
+}
+
+void OS::set_benchmark_file(const String &p_benchmark_file) {
+ benchmark_file = p_benchmark_file;
+}
+
+String OS::get_benchmark_file() {
+ return benchmark_file;
+}
+
+void OS::benchmark_begin_measure(const String &p_what) {
+#ifdef TOOLS_ENABLED
+ start_benchmark_from[p_what] = OS::get_singleton()->get_ticks_usec();
+#endif
+}
+void OS::benchmark_end_measure(const String &p_what) {
+#ifdef TOOLS_ENABLED
+ uint64_t total = OS::get_singleton()->get_ticks_usec() - start_benchmark_from[p_what];
+ double total_f = double(total) / double(1000000);
+
+ startup_benchmark_json[p_what] = total_f;
+#endif
+}
+
+void OS::benchmark_dump() {
+#ifdef TOOLS_ENABLED
+ if (!use_benchmark) {
+ return;
+ }
+ if (!benchmark_file.is_empty()) {
+ Ref<FileAccess> f = FileAccess::open(benchmark_file, FileAccess::WRITE);
+ if (f.is_valid()) {
+ Ref<JSON> json;
+ json.instantiate();
+ f->store_string(json->stringify(startup_benchmark_json, "\t", false, true));
+ }
+ } else {
+ List<Variant> keys;
+ startup_benchmark_json.get_key_list(&keys);
+ print_line("BENCHMARK:");
+ for (const Variant &K : keys) {
+ print_line("\t-", K, ": ", startup_benchmark_json[K], +" sec.");
+ }
+ }
+#endif
+}
+
OS::OS() {
singleton = this;
diff --git a/core/os/os.h b/core/os/os.h
index d77890d89d..f2787d6381 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -34,6 +34,7 @@
#include "core/config/engine.h"
#include "core/io/image.h"
#include "core/io/logger.h"
+#include "core/io/remote_filesystem_client.h"
#include "core/os/time_enums.h"
#include "core/string/ustring.h"
#include "core/templates/list.h"
@@ -51,6 +52,7 @@ class OS {
bool _keep_screen_on = true; // set default value to true, because this had been true before godot 2.0.
bool low_processor_usage_mode = false;
int low_processor_usage_mode_sleep_usec = 10000;
+ bool _delta_smoothing_enabled = false;
bool _verbose_stdout = false;
bool _debug_stdout = false;
String _local_clipboard;
@@ -72,6 +74,14 @@ class OS {
String _current_rendering_driver_name;
String _current_rendering_method;
+ RemoteFilesystemClient default_rfs;
+
+ // For tracking benchmark data
+ bool use_benchmark = false;
+ String benchmark_file;
+ HashMap<String, uint64_t> start_benchmark_from;
+ Dictionary startup_benchmark_json;
+
protected:
void _set_logger(CompositeLogger *p_logger);
@@ -134,6 +144,7 @@ public:
virtual String get_stdin_string() = 0;
virtual Error get_entropy(uint8_t *r_buffer, int p_bytes) = 0; // Should return cryptographically-safe random bytes.
+ virtual String get_system_ca_certificates() { return ""; } // Concatenated certificates in PEM format.
virtual PackedStringArray get_connected_midi_inputs();
virtual void open_midi_inputs();
@@ -150,6 +161,9 @@ public:
virtual void set_low_processor_usage_mode_sleep_usec(int p_usec);
virtual int get_low_processor_usage_mode_sleep_usec() const;
+ void set_delta_smoothing(bool p_enabled);
+ bool is_delta_smoothing_enabled() const;
+
virtual Vector<String> get_system_fonts() const { return Vector<String>(); };
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return String(); };
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); };
@@ -163,6 +177,7 @@ public:
virtual void vibrate_handheld(int p_duration_ms = 500) {}
virtual Error shell_open(String p_uri);
+ virtual Error shell_show_in_file_manager(String p_path, bool p_open_folder = true);
virtual Error set_cwd(const String &p_cwd);
virtual bool has_environment(const String &p_var) const = 0;
@@ -171,6 +186,7 @@ public:
virtual void unset_environment(const String &p_var) const = 0;
virtual String get_name() const = 0;
+ virtual String get_identifier() const;
virtual String get_distribution_name() const = 0;
virtual String get_version() const = 0;
virtual List<String> get_cmdline_args() const { return _cmdline; }
@@ -229,7 +245,7 @@ public:
virtual uint64_t get_static_memory_usage() const;
virtual uint64_t get_static_memory_peak_usage() const;
- virtual uint64_t get_free_static_memory() const;
+ virtual Dictionary get_memory_info() const;
RenderThreadMode get_render_thread_mode() const { return _render_thread_mode; }
@@ -289,8 +305,19 @@ public:
virtual bool request_permissions() { return true; }
virtual Vector<String> get_granted_permissions() const { return Vector<String>(); }
+ // For recording / measuring benchmark data. Only enabled with tools
+ void set_use_benchmark(bool p_use_benchmark);
+ bool is_use_benchmark_set();
+ void set_benchmark_file(const String &p_benchmark_file);
+ String get_benchmark_file();
+ virtual void benchmark_begin_measure(const String &p_what);
+ virtual void benchmark_end_measure(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 {
PREFERRED_TEXTURE_FORMAT_S3TC_BPTC,
PREFERRED_TEXTURE_FORMAT_ETC2_ASTC
diff --git a/core/os/semaphore.h b/core/os/semaphore.h
index a992a4587d..66dfb3ee02 100644
--- a/core/os/semaphore.h
+++ b/core/os/semaphore.h
@@ -33,6 +33,9 @@
#include "core/error/error_list.h"
#include "core/typedefs.h"
+#ifdef DEBUG_ENABLED
+#include "core/error/error_macros.h"
+#endif
#include <condition_variable>
#include <mutex>
@@ -42,6 +45,9 @@ private:
mutable std::mutex mutex;
mutable std::condition_variable condition;
mutable uint32_t count = 0; // Initialized as locked.
+#ifdef DEBUG_ENABLED
+ mutable uint32_t awaiters = 0;
+#endif
public:
_ALWAYS_INLINE_ void post() const {
@@ -52,10 +58,16 @@ public:
_ALWAYS_INLINE_ void wait() const {
std::unique_lock lock(mutex);
+#ifdef DEBUG_ENABLED
+ ++awaiters;
+#endif
while (!count) { // Handle spurious wake-ups.
condition.wait(lock);
}
- count--;
+ --count;
+#ifdef DEBUG_ENABLED
+ --awaiters;
+#endif
}
_ALWAYS_INLINE_ bool try_wait() const {
@@ -67,6 +79,47 @@ public:
return false;
}
}
+
+#ifdef DEBUG_ENABLED
+ ~Semaphore() {
+ // Destroying an std::condition_variable when not all threads waiting on it have been notified
+ // invokes undefined behavior (e.g., it may be nicely destroyed or it may be awaited forever.)
+ // That means other threads could still be running the body of std::condition_variable::wait()
+ // but already past the safety checkpoint. That's the case for instance if that function is already
+ // waiting to lock again.
+ //
+ // We will make the rule a bit more restrictive and simpler to understand at the same time: there
+ // should not be any threads at any stage of the waiting by the time the semaphore is destroyed.
+ //
+ // We do so because of the following reasons:
+ // - We have the guideline that threads must be awaited (i.e., completed), so the waiting thread
+ // must be completely done by the time the thread controlling it finally destroys the semaphore.
+ // Therefore, only a coding mistake could make the program run into such a attempt at premature
+ // destruction of the semaphore.
+ // - In scripting, given that Semaphores are wrapped by RefCounted classes, in general it can't
+ // happen that a thread is trying to destroy a Semaphore while another is still doing whatever with
+ // it, so the simplification is mostly transparent to script writers.
+ // - The redefined rule can be checked for failure to meet it, which is what this implementation does.
+ // This is useful to detect a few cases of potential misuse; namely:
+ // a) In scripting:
+ // * The coder is naughtily dealing with the reference count causing a semaphore to die prematurely.
+ // * The coder is letting the project reach its termination without having cleanly finished threads
+ // that await on semaphores (or at least, let the usual semaphore-controlled loop exit).
+ // b) In the native side, where Semaphore is not a ref-counted beast and certain coding mistakes can
+ // lead to its premature destruction as well.
+ //
+ // Let's let users know they are doing it wrong, but apply a, somewhat hacky, countermeasure against UB
+ // in debug builds.
+ std::lock_guard lock(mutex);
+ if (awaiters) {
+ WARN_PRINT(
+ "A Semaphore object is being destroyed while one or more threads are still waiting on it.\n"
+ "Please call post() on it as necessary to prevent such a situation and so ensure correct cleanup.");
+ // And now, the hacky countermeasure (i.e., leak the condition variable).
+ new (&condition) std::condition_variable();
+ }
+ }
+#endif
};
#endif // SEMAPHORE_H
diff --git a/core/os/spin_lock.h b/core/os/spin_lock.h
index 409154dbb5..93ea782b60 100644
--- a/core/os/spin_lock.h
+++ b/core/os/spin_lock.h
@@ -36,15 +36,15 @@
#include <atomic>
class SpinLock {
- std::atomic_flag locked = ATOMIC_FLAG_INIT;
+ mutable std::atomic_flag locked = ATOMIC_FLAG_INIT;
public:
- _ALWAYS_INLINE_ void lock() {
+ _ALWAYS_INLINE_ void lock() const {
while (locked.test_and_set(std::memory_order_acquire)) {
// Continue.
}
}
- _ALWAYS_INLINE_ void unlock() {
+ _ALWAYS_INLINE_ void unlock() const {
locked.clear(std::memory_order_release);
}
};
diff --git a/core/os/thread.cpp b/core/os/thread.cpp
index 92865576f3..03e2c5409d 100644
--- a/core/os/thread.cpp
+++ b/core/os/thread.cpp
@@ -38,13 +38,9 @@
Thread::PlatformFunctions Thread::platform_functions;
-uint64_t Thread::_thread_id_hash(const std::thread::id &p_t) {
- static std::hash<std::thread::id> hasher;
- return hasher(p_t);
-}
+SafeNumeric<uint64_t> Thread::id_counter(1); // The first value after .increment() is 2, hence by default the main thread ID should be 1.
-Thread::ID Thread::main_thread_id = _thread_id_hash(std::this_thread::get_id());
-thread_local Thread::ID Thread::caller_id = 0;
+thread_local Thread::ID Thread::caller_id = Thread::UNASSIGNED_ID;
void Thread::_set_platform_functions(const PlatformFunctions &p_functions) {
platform_functions = p_functions;
@@ -70,32 +66,25 @@ void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_cal
}
}
-void Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_settings) {
- if (id != _thread_id_hash(std::thread::id())) {
-#ifdef DEBUG_ENABLED
- WARN_PRINT("A Thread object has been re-started without wait_to_finish() having been called on it. Please do so to ensure correct cleanup of the thread.");
-#endif
- thread.detach();
- std::thread empty_thread;
- thread.swap(empty_thread);
- }
- std::thread new_thread(&Thread::callback, _thread_id_hash(thread.get_id()), p_settings, p_callback, p_user);
+Thread::ID Thread::start(Thread::Callback p_callback, void *p_user, const Settings &p_settings) {
+ ERR_FAIL_COND_V_MSG(id != UNASSIGNED_ID, UNASSIGNED_ID, "A Thread object has been re-started without wait_to_finish() having been called on it.");
+ id = id_counter.increment();
+ std::thread new_thread(&Thread::callback, id, p_settings, p_callback, p_user);
thread.swap(new_thread);
- id = _thread_id_hash(thread.get_id());
+ return id;
}
bool Thread::is_started() const {
- return id != _thread_id_hash(std::thread::id());
+ return id != UNASSIGNED_ID;
}
void Thread::wait_to_finish() {
- if (id != _thread_id_hash(std::thread::id())) {
- ERR_FAIL_COND_MSG(id == get_caller_id(), "A Thread can't wait for itself to finish.");
- thread.join();
- std::thread empty_thread;
- thread.swap(empty_thread);
- id = _thread_id_hash(std::thread::id());
- }
+ ERR_FAIL_COND_MSG(id == UNASSIGNED_ID, "Attempt of waiting to finish on a thread that was never started.");
+ ERR_FAIL_COND_MSG(id == get_caller_id(), "Threads can't wait to finish on themselves, another thread must wait.");
+ thread.join();
+ std::thread empty_thread;
+ thread.swap(empty_thread);
+ id = UNASSIGNED_ID;
}
Error Thread::set_name(const String &p_name) {
@@ -107,13 +96,14 @@ Error Thread::set_name(const String &p_name) {
}
Thread::Thread() {
- caller_id = _thread_id_hash(std::this_thread::get_id());
}
Thread::~Thread() {
- if (id != _thread_id_hash(std::thread::id())) {
+ if (id != UNASSIGNED_ID) {
#ifdef DEBUG_ENABLED
- WARN_PRINT("A Thread object has been destroyed without wait_to_finish() having been called on it. Please do so to ensure correct cleanup of the thread.");
+ WARN_PRINT(
+ "A Thread object is being destroyed without its completion having been realized.\n"
+ "Please call wait_to_finish() on it to ensure correct cleanup.");
#endif
thread.detach();
}
diff --git a/core/os/thread.h b/core/os/thread.h
index 6eb21fba65..3e307adfff 100644
--- a/core/os/thread.h
+++ b/core/os/thread.h
@@ -52,6 +52,11 @@ public:
typedef uint64_t ID;
+ enum : ID {
+ UNASSIGNED_ID = 0,
+ MAIN_ID = 1
+ };
+
enum Priority {
PRIORITY_LOW,
PRIORITY_NORMAL,
@@ -74,11 +79,8 @@ public:
private:
friend class Main;
- static ID main_thread_id;
-
- static uint64_t _thread_id_hash(const std::thread::id &p_t);
-
- ID id = _thread_id_hash(std::thread::id());
+ ID id = UNASSIGNED_ID;
+ static SafeNumeric<uint64_t> id_counter;
static thread_local ID caller_id;
std::thread thread;
@@ -86,18 +88,28 @@ private:
static PlatformFunctions platform_functions;
+ static void make_main_thread() { caller_id = MAIN_ID; }
+ static void release_main_thread() { caller_id = UNASSIGNED_ID; }
+
public:
static void _set_platform_functions(const PlatformFunctions &p_functions);
_FORCE_INLINE_ ID get_id() const { return id; }
// get the ID of the caller thread
- _FORCE_INLINE_ static ID get_caller_id() { return caller_id; }
+ _FORCE_INLINE_ static ID get_caller_id() {
+ if (unlikely(caller_id == UNASSIGNED_ID)) {
+ caller_id = id_counter.increment();
+ }
+ return caller_id;
+ }
// get the ID of the main thread
- _FORCE_INLINE_ static ID get_main_id() { return main_thread_id; }
+ _FORCE_INLINE_ static ID get_main_id() { return MAIN_ID; }
+
+ _FORCE_INLINE_ static bool is_main_thread() { return caller_id == MAIN_ID; } // Gain a tiny bit of perf here because there is no need to validate caller_id here, because only main thread will be set as 1.
static Error set_name(const String &p_name);
- void start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings());
+ ID start(Thread::Callback p_callback, void *p_user, const Settings &p_settings = Settings());
bool is_started() const;
///< waits until thread is finished, and deallocates it.
void wait_to_finish();
diff --git a/core/os/thread_safe.cpp b/core/os/thread_safe.cpp
new file mode 100644
index 0000000000..96b7de8ed2
--- /dev/null
+++ b/core/os/thread_safe.cpp
@@ -0,0 +1,46 @@
+/**************************************************************************/
+/* thread_safe.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef THREAD_SAFE_CPP
+#define THREAD_SAFE_CPP
+
+#include "thread_safe.h"
+
+static thread_local bool current_thread_safe_for_nodes = false;
+
+bool is_current_thread_safe_for_nodes() {
+ return current_thread_safe_for_nodes;
+}
+
+void set_current_thread_safe_for_nodes(bool p_safe) {
+ current_thread_safe_for_nodes = p_safe;
+}
+
+#endif // THREAD_SAFE_CPP
diff --git a/core/os/thread_safe.h b/core/os/thread_safe.h
index ac8734b6c1..042a0b7d98 100644
--- a/core/os/thread_safe.h
+++ b/core/os/thread_safe.h
@@ -38,4 +38,7 @@
#define _THREAD_SAFE_LOCK_ _thread_safe_.lock();
#define _THREAD_SAFE_UNLOCK_ _thread_safe_.unlock();
+bool is_current_thread_safe_for_nodes();
+void set_current_thread_safe_for_nodes(bool p_safe);
+
#endif // THREAD_SAFE_H
diff --git a/core/os/time.cpp b/core/os/time.cpp
index 12e6f08525..bad5cc2e4f 100644
--- a/core/os/time.cpp
+++ b/core/os/time.cpp
@@ -52,9 +52,6 @@ static const uint8_t MONTH_DAYS_TABLE[2][12] = {
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
-VARIANT_ENUM_CAST(Month);
-VARIANT_ENUM_CAST(Weekday);
-
#define UNIX_TIME_TO_HMS \
uint8_t hour, minute, second; \
{ \
@@ -382,10 +379,10 @@ String Time::get_time_string_from_system(bool p_utc) const {
Dictionary Time::get_time_zone_from_system() const {
OS::TimeZoneInfo info = OS::get_singleton()->get_time_zone_info();
- Dictionary timezone;
- timezone["bias"] = info.bias;
- timezone["name"] = info.name;
- return timezone;
+ Dictionary ret_timezone;
+ ret_timezone["bias"] = info.bias;
+ ret_timezone["name"] = info.name;
+ return ret_timezone;
}
double Time::get_unix_time_from_system() const {
diff --git a/core/os/time.h b/core/os/time.h
index 19bc900aee..ccd2d92b8b 100644
--- a/core/os/time.h
+++ b/core/os/time.h
@@ -81,4 +81,7 @@ public:
virtual ~Time();
};
+VARIANT_ENUM_CAST(Month);
+VARIANT_ENUM_CAST(Weekday);
+
#endif // TIME_H
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index a374e7c009..b4ac533779 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -120,6 +120,7 @@ static ResourceUID *resource_uid = nullptr;
static bool _is_core_extensions_registered = false;
void register_core_types() {
+ OS::get_singleton()->benchmark_begin_measure("register_core_types");
//consistency check
static_assert(sizeof(Callable) <= 16);
@@ -294,6 +295,8 @@ void register_core_types() {
GDREGISTER_NATIVE_STRUCT(ScriptLanguageExtensionProfilingInfo, "StringName signature;uint64_t call_count;uint64_t total_time;uint64_t self_time");
worker_thread_pool = memnew(WorkerThreadPool);
+
+ OS::get_singleton()->benchmark_end_measure("register_core_types");
}
void register_core_settings() {
@@ -302,15 +305,9 @@ void register_core_settings() {
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "network/limits/packet_peer_stream/max_buffer_po2", PROPERTY_HINT_RANGE, "0,64,1,or_greater"), (16));
GLOBAL_DEF(PropertyInfo(Variant::STRING, "network/tls/certificate_bundle_override", PROPERTY_HINT_FILE, "*.crt"), "");
- int worker_threads = GLOBAL_DEF("threading/worker_pool/max_threads", -1);
- bool low_priority_use_system_threads = GLOBAL_DEF("threading/worker_pool/use_system_threads_for_low_priority_tasks", true);
- float low_property_ratio = GLOBAL_DEF("threading/worker_pool/low_priority_thread_ratio", 0.3);
-
- if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
- worker_thread_pool->init();
- } else {
- worker_thread_pool->init(worker_threads, low_priority_use_system_threads, low_property_ratio);
- }
+ GLOBAL_DEF("threading/worker_pool/max_threads", -1);
+ GLOBAL_DEF("threading/worker_pool/use_system_threads_for_low_priority_tasks", true);
+ GLOBAL_DEF("threading/worker_pool/low_priority_thread_ratio", 0.3);
}
void register_core_singletons() {
@@ -366,21 +363,30 @@ void unregister_core_extensions() {
}
void unregister_core_types() {
- memdelete(gdextension_manager);
+ OS::get_singleton()->benchmark_begin_measure("unregister_core_types");
+
+ // Destroy singletons in reverse order to ensure dependencies are not broken.
+
+ memdelete(worker_thread_pool);
- memdelete(resource_uid);
- memdelete(_resource_loader);
- memdelete(_resource_saver);
- memdelete(_os);
- memdelete(_engine);
- memdelete(_classdb);
- memdelete(_marshalls);
memdelete(_engine_debugger);
+ memdelete(_marshalls);
+ memdelete(_classdb);
+ memdelete(_engine);
+ memdelete(_os);
+ memdelete(_resource_saver);
+ memdelete(_resource_loader);
- memdelete(_geometry_2d);
memdelete(_geometry_3d);
+ memdelete(_geometry_2d);
- memdelete(worker_thread_pool);
+ memdelete(gdextension_manager);
+
+ memdelete(resource_uid);
+
+ if (ip) {
+ memdelete(ip);
+ }
ResourceLoader::remove_resource_format_loader(resource_format_image);
resource_format_image.unref();
@@ -411,10 +417,6 @@ void unregister_core_types() {
ResourceLoader::remove_resource_format_loader(resource_loader_json);
resource_loader_json.unref();
- if (ip) {
- memdelete(ip);
- }
-
ResourceLoader::remove_resource_format_loader(resource_loader_gdextension);
resource_loader_gdextension.unref();
@@ -431,4 +433,6 @@ void unregister_core_types() {
ResourceCache::clear();
CoreStringNames::free();
StringName::cleanup();
+
+ OS::get_singleton()->benchmark_end_measure("unregister_core_types");
}
diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp
index 7b894d83bf..7b90710308 100644
--- a/core/string/print_string.cpp
+++ b/core/string/print_string.cpp
@@ -193,10 +193,8 @@ void print_error(String p_string) {
_global_unlock();
}
-void print_verbose(String p_string) {
- if (OS::get_singleton()->is_stdout_verbose()) {
- print_line(p_string);
- }
+bool is_print_verbose_enabled() {
+ return OS::get_singleton()->is_stdout_verbose();
}
String stringify_variants(Variant p_var) {
diff --git a/core/string/print_string.h b/core/string/print_string.h
index 6496384b3f..7656e9bfa1 100644
--- a/core/string/print_string.h
+++ b/core/string/print_string.h
@@ -59,7 +59,15 @@ void remove_print_handler(const PrintHandlerList *p_handler);
extern void __print_line(String p_string);
extern void __print_line_rich(String p_string);
extern void print_error(String p_string);
-extern void print_verbose(String p_string);
+extern bool is_print_verbose_enabled();
+
+// This version avoids processing the text to be printed until it actually has to be printed, saving some CPU usage.
+#define print_verbose(m_text) \
+ { \
+ if (is_print_verbose_enabled()) { \
+ print_line(m_text); \
+ } \
+ }
inline void print_line(Variant v) {
__print_line(stringify_variants(v));
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index df9b6b3f1a..6099fea13f 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -201,6 +201,14 @@ StringName::StringName(const StringName &p_name) {
}
}
+void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) {
+ mutex.lock();
+ if (*ptr == StringName()) {
+ *ptr = StringName(p_name, true);
+ }
+ mutex.unlock();
+}
+
StringName::StringName(const char *p_name, bool p_static) {
_data = nullptr;
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 177e82896d..4ed58d8286 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -117,6 +117,15 @@ public:
_FORCE_INLINE_ bool operator<(const StringName &p_name) const {
return _data < p_name._data;
}
+ _FORCE_INLINE_ bool operator<=(const StringName &p_name) const {
+ return _data <= p_name._data;
+ }
+ _FORCE_INLINE_ bool operator>(const StringName &p_name) const {
+ return _data > p_name._data;
+ }
+ _FORCE_INLINE_ bool operator>=(const StringName &p_name) const {
+ return _data >= p_name._data;
+ }
_FORCE_INLINE_ bool operator==(const StringName &p_name) const {
// the real magic of all this mess happens here.
// this is why path comparisons are very fast
@@ -177,6 +186,8 @@ public:
StringName(const String &p_name, bool p_static = false);
StringName(const StaticCString &p_static_string, bool p_static = false);
StringName() {}
+
+ static void assign_static_unique_class_name(StringName *ptr, const char *p_name);
_FORCE_INLINE_ ~StringName() {
if (likely(configured) && _data) { //only free if configured
unref();
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 160bad14ab..3ca2e5ccdf 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -941,18 +941,11 @@ String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const
}
String TranslationServer::add_padding(const String &p_message, int p_length) const {
- String res;
- String prefix = pseudolocalization_prefix;
- String suffix;
- for (int i = 0; i < p_length * expansion_ratio / 2; i++) {
- prefix += "_";
- suffix += "_";
- }
- suffix += pseudolocalization_suffix;
- res += prefix;
- res += p_message;
- res += suffix;
- return res;
+ String underscores = String("_").repeat(p_length * expansion_ratio / 2);
+ String prefix = pseudolocalization_prefix + underscores;
+ String suffix = underscores + pseudolocalization_suffix;
+
+ return prefix + p_message + suffix;
}
const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 6a59942a56..c276f20f99 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -812,15 +812,15 @@ signed char String::nocasecmp_to(const String &p_str) const {
const char32_t *this_str = get_data();
while (true) {
- if (*that_str == 0 && *this_str == 0) {
- return 0; //we're equal
- } else if (*this_str == 0) {
- return -1; //if this is empty, and the other one is not, then we're less.. I think?
- } else if (*that_str == 0) {
- return 1; //otherwise the other one is smaller..
- } else if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than
+ if (*that_str == 0 && *this_str == 0) { // If both strings are at the end, they are equal.
+ return 0;
+ } else if (*this_str == 0) { // If at the end of this, and not of other, we are less.
+ return -1;
+ } else if (*that_str == 0) { // If at end of other, and not of this, we are greater.
+ return 1;
+ } else if (_find_upper(*this_str) < _find_upper(*that_str)) { // If current character in this is less, we are less.
return -1;
- } else if (_find_upper(*this_str) > _find_upper(*that_str)) { //less than
+ } else if (_find_upper(*this_str) > _find_upper(*that_str)) { // If current character in this is greater, we are greater.
return 1;
}
@@ -844,15 +844,15 @@ signed char String::casecmp_to(const String &p_str) const {
const char32_t *this_str = get_data();
while (true) {
- if (*that_str == 0 && *this_str == 0) {
- return 0; //we're equal
- } else if (*this_str == 0) {
- return -1; //if this is empty, and the other one is not, then we're less.. I think?
- } else if (*that_str == 0) {
- return 1; //otherwise the other one is smaller..
- } else if (*this_str < *that_str) { //more than
+ if (*that_str == 0 && *this_str == 0) { // If both strings are at the end, they are equal.
+ return 0;
+ } else if (*this_str == 0) { // If at the end of this, and not of other, we are less.
return -1;
- } else if (*this_str > *that_str) { //less than
+ } else if (*that_str == 0) { // If at end of other, and not of this, we are greater.
+ return 1;
+ } else if (*this_str < *that_str) { // If current character in this is less, we are less.
+ return -1;
+ } else if (*this_str > *that_str) { // If current character in this is greater, we are greater.
return 1;
}
@@ -861,7 +861,48 @@ signed char String::casecmp_to(const String &p_str) const {
}
}
-signed char String::naturalnocasecmp_to(const String &p_str) const {
+static _FORCE_INLINE_ signed char natural_cmp_common(const char32_t *&r_this_str, const char32_t *&r_that_str) {
+ // Keep ptrs to start of numerical sequences.
+ const char32_t *this_substr = r_this_str;
+ const char32_t *that_substr = r_that_str;
+
+ // Compare lengths of both numerical sequences, ignoring leading zeros.
+ while (is_digit(*r_this_str)) {
+ r_this_str++;
+ }
+ while (is_digit(*r_that_str)) {
+ r_that_str++;
+ }
+ while (*this_substr == '0') {
+ this_substr++;
+ }
+ while (*that_substr == '0') {
+ that_substr++;
+ }
+ int this_len = r_this_str - this_substr;
+ int that_len = r_that_str - that_substr;
+
+ if (this_len < that_len) {
+ return -1;
+ } else if (this_len > that_len) {
+ return 1;
+ }
+
+ // If lengths equal, compare lexicographically.
+ while (this_substr != r_this_str && that_substr != r_that_str) {
+ if (*this_substr < *that_substr) {
+ return -1;
+ } else if (*this_substr > *that_substr) {
+ return 1;
+ }
+ this_substr++;
+ that_substr++;
+ }
+
+ return 0;
+}
+
+signed char String::naturalcasecmp_to(const String &p_str) const {
const char32_t *this_str = get_data();
const char32_t *that_str = p_str.get_data();
@@ -889,48 +930,69 @@ signed char String::naturalnocasecmp_to(const String &p_str) const {
return -1;
}
- // Keep ptrs to start of numerical sequences
- const char32_t *this_substr = this_str;
- const char32_t *that_substr = that_str;
-
- // Compare lengths of both numerical sequences, ignoring leading zeros
- while (is_digit(*this_str)) {
- this_str++;
- }
- while (is_digit(*that_str)) {
- that_str++;
- }
- while (*this_substr == '0') {
- this_substr++;
+ signed char ret = natural_cmp_common(this_str, that_str);
+ if (ret) {
+ return ret;
}
- while (*that_substr == '0') {
- that_substr++;
+ } else if (is_digit(*that_str)) {
+ return 1;
+ } else {
+ if (*this_str < *that_str) { // If current character in this is less, we are less.
+ return -1;
+ } else if (*this_str > *that_str) { // If current character in this is greater, we are greater.
+ return 1;
}
- int this_len = this_str - this_substr;
- int that_len = that_str - that_substr;
- if (this_len < that_len) {
+ this_str++;
+ that_str++;
+ }
+ }
+ if (*that_str) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+signed char String::naturalnocasecmp_to(const String &p_str) const {
+ const char32_t *this_str = get_data();
+ const char32_t *that_str = p_str.get_data();
+
+ if (this_str && that_str) {
+ while (*this_str == '.' || *that_str == '.') {
+ if (*this_str++ != '.') {
+ return 1;
+ }
+ if (*that_str++ != '.') {
+ return -1;
+ }
+ if (!*that_str) {
+ return 1;
+ }
+ if (!*this_str) {
+ return -1;
+ }
+ }
+
+ while (*this_str) {
+ if (!*that_str) {
+ return 1;
+ } else if (is_digit(*this_str)) {
+ if (!is_digit(*that_str)) {
return -1;
- } else if (this_len > that_len) {
- return 1;
}
- // If lengths equal, compare lexicographically
- while (this_substr != this_str && that_substr != that_str) {
- if (*this_substr < *that_substr) {
- return -1;
- } else if (*this_substr > *that_substr) {
- return 1;
- }
- this_substr++;
- that_substr++;
+ signed char ret = natural_cmp_common(this_str, that_str);
+ if (ret) {
+ return ret;
}
} else if (is_digit(*that_str)) {
return 1;
} else {
- if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than
+ if (_find_upper(*this_str) < _find_upper(*that_str)) { // If current character in this is less, we are less.
return -1;
- } else if (_find_upper(*this_str) > _find_upper(*that_str)) { //less than
+ } else if (_find_upper(*this_str) > _find_upper(*that_str)) { // If current character in this is greater, we are greater.
return 1;
}
@@ -1644,6 +1706,35 @@ String String::hex_encode_buffer(const uint8_t *p_buffer, int p_len) {
return ret;
}
+Vector<uint8_t> String::hex_decode() const {
+ ERR_FAIL_COND_V_MSG(length() % 2 != 0, Vector<uint8_t>(), "Hexadecimal string of uneven length.");
+
+#define HEX_TO_BYTE(m_output, m_index) \
+ uint8_t m_output; \
+ c = operator[](m_index); \
+ if (is_digit(c)) { \
+ m_output = c - '0'; \
+ } else if (c >= 'a' && c <= 'f') { \
+ m_output = c - 'a' + 10; \
+ } else if (c >= 'A' && c <= 'F') { \
+ m_output = c - 'A' + 10; \
+ } else { \
+ ERR_FAIL_V_MSG(Vector<uint8_t>(), "Invalid hexadecimal character \"" + chr(c) + "\" at index " + m_index + "."); \
+ }
+
+ Vector<uint8_t> out;
+ int len = length() / 2;
+ out.resize(len);
+ for (int i = 0; i < len; i++) {
+ char32_t c;
+ HEX_TO_BYTE(first, i * 2);
+ HEX_TO_BYTE(second, i * 2 + 1);
+ out.write[i] = first * 16 + second;
+ }
+ return out;
+#undef HEX_TO_BYTE
+}
+
void String::print_unicode_error(const String &p_message, bool p_critical) const {
if (p_critical) {
print_error(vformat("Unicode parsing error, some characters were replaced with spaces: %s", p_message));
@@ -2164,7 +2255,7 @@ int64_t String::hex_to_int() const {
} else if (c >= 'a' && c <= 'f') {
n = (c - 'a') + 10;
} else {
- ERR_FAIL_COND_V_MSG(true, 0, "Invalid hexadecimal notation character \"" + chr(*s) + "\" in string \"" + *this + "\".");
+ ERR_FAIL_V_MSG(0, vformat(R"(Invalid hexadecimal notation character "%c" (U+%04X) in string "%s".)", *s, static_cast<int32_t>(*s), *this));
}
// Check for overflow/underflow, with special case to ensure INT64_MIN does not result in error
bool overflow = ((hex > INT64_MAX / 16) && (sign == 1 || (sign == -1 && hex != (INT64_MAX >> 4) + 1))) || (sign == -1 && hex == (INT64_MAX >> 4) + 1 && c > '0');
@@ -2564,6 +2655,23 @@ double String::to_float(const wchar_t *p_str, const wchar_t **r_end) {
return built_in_strtod<wchar_t>(p_str, (wchar_t **)r_end);
}
+uint32_t String::num_characters(int64_t p_int) {
+ int r = 1;
+ if (p_int < 0) {
+ r += 1;
+ if (p_int == INT64_MIN) {
+ p_int = INT64_MAX;
+ } else {
+ p_int = -p_int;
+ }
+ }
+ while (p_int >= 10) {
+ p_int /= 10;
+ r++;
+ }
+ return r;
+}
+
int64_t String::to_int(const char32_t *p_str, int p_len, bool p_clamp) {
if (p_len == 0 || !p_str[0]) {
return 0;
@@ -2811,6 +2919,12 @@ String String::insert(int p_at_pos, const String &p_string) const {
return pre + p_string + post;
}
+String String::erase(int p_pos, int p_chars) const {
+ ERR_FAIL_COND_V_MSG(p_pos < 0, "", vformat("Invalid starting position for `String.erase()`: %d. Starting position must be positive or zero.", p_pos));
+ ERR_FAIL_COND_V_MSG(p_chars < 0, "", vformat("Invalid character count for `String.erase()`: %d. Character count must be positive or zero.", p_chars));
+ return left(p_pos) + substr(p_pos + p_chars);
+}
+
String String::substr(int p_from, int p_chars) const {
if (p_chars == -1) {
p_chars = length() - p_from;
@@ -3478,6 +3592,14 @@ String String::replacen(const String &p_key, const String &p_with) const {
String String::repeat(int p_count) const {
ERR_FAIL_COND_V_MSG(p_count < 0, "", "Parameter count should be a positive number.");
+ if (p_count == 0) {
+ return "";
+ }
+
+ if (p_count == 1) {
+ return *this;
+ }
+
int len = length();
String new_string = *this;
new_string.resize(p_count * len + 1);
@@ -4115,13 +4237,11 @@ String String::pad_decimals(int p_digits) const {
}
if (s.length() - (c + 1) > p_digits) {
- s = s.substr(0, c + p_digits + 1);
+ return s.substr(0, c + p_digits + 1);
} else {
- while (s.length() - (c + 1) < p_digits) {
- s += "0";
- }
+ int zeros_to_add = p_digits - s.length() + (c + 1);
+ return s + String("0").repeat(zeros_to_add);
}
- return s;
}
String String::pad_zeros(int p_digits) const {
@@ -4146,12 +4266,8 @@ String String::pad_zeros(int p_digits) const {
return s;
}
- while (end - begin < p_digits) {
- s = s.insert(begin, "0");
- end++;
- }
-
- return s;
+ int zeros_to_add = p_digits - (end - begin);
+ return s.insert(begin, String("0").repeat(zeros_to_add));
}
String String::trim_prefix(const String &p_prefix) const {
@@ -4330,11 +4446,8 @@ String String::path_to(const String &p_path) const {
common_parent--;
- String dir;
-
- for (int i = src_dirs.size() - 1; i > common_parent; i--) {
- dir += "../";
- }
+ int dirs_to_backtrack = (src_dirs.size() - 1) - common_parent;
+ String dir = String("../").repeat(dirs_to_backtrack);
for (int i = common_parent + 1; i < dst_dirs.size(); i++) {
dir += dst_dirs[i] + "/";
@@ -4532,15 +4645,65 @@ String String::property_name_encode() const {
}
// Changes made to the set of invalid characters must also be reflected in the String documentation.
-const String String::invalid_node_name_characters = ". : @ / \" " UNIQUE_NODE_PREFIX;
+
+static const char32_t invalid_node_name_characters[] = { '.', ':', '@', '/', '\"', UNIQUE_NODE_PREFIX[0], 0 };
+
+String String::get_invalid_node_name_characters() {
+ // Do not use this function for critical validation.
+ String r;
+ const char32_t *c = invalid_node_name_characters;
+ while (*c) {
+ if (c != invalid_node_name_characters) {
+ r += " ";
+ }
+ r += String::chr(*c);
+ c++;
+ }
+ return r;
+}
String String::validate_node_name() const {
- Vector<String> chars = String::invalid_node_name_characters.split(" ");
- String name = this->replace(chars[0], "");
- for (int i = 1; i < chars.size(); i++) {
- name = name.replace(chars[i], "");
+ // This is a critical validation in node addition, so it must be optimized.
+ const char32_t *cn = ptr();
+ if (cn == nullptr) {
+ return String();
}
- return name;
+ bool valid = true;
+ uint32_t idx = 0;
+ while (cn[idx]) {
+ const char32_t *c = invalid_node_name_characters;
+ while (*c) {
+ if (cn[idx] == *c) {
+ valid = false;
+ break;
+ }
+ c++;
+ }
+ if (!valid) {
+ break;
+ }
+ idx++;
+ }
+
+ if (valid) {
+ return *this;
+ }
+
+ String validated = *this;
+ char32_t *nn = validated.ptrw();
+ while (nn[idx]) {
+ const char32_t *c = invalid_node_name_characters;
+ while (*c) {
+ if (nn[idx] == *c) {
+ nn[idx] = '_';
+ break;
+ }
+ c++;
+ }
+ idx++;
+ }
+
+ return validated;
}
String String::get_basename() const {
@@ -4573,11 +4736,8 @@ String String::rpad(int min_length, const String &character) const {
String s = *this;
int padding = min_length - s.length();
if (padding > 0) {
- for (int i = 0; i < padding; i++) {
- s = s + character;
- }
+ s += character.repeat(padding);
}
-
return s;
}
@@ -4586,11 +4746,8 @@ String String::lpad(int min_length, const String &character) const {
String s = *this;
int padding = min_length - s.length();
if (padding > 0) {
- for (int i = 0; i < padding; i++) {
- s = character + s;
- }
+ s = character.repeat(padding) + s;
}
-
return s;
}
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 28e3af92c5..782ca47507 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -262,6 +262,7 @@ public:
signed char casecmp_to(const String &p_str) const;
signed char nocasecmp_to(const String &p_str) const;
+ signed char naturalcasecmp_to(const String &p_str) const;
signed char naturalnocasecmp_to(const String &p_str) const;
const char32_t *get_data() const;
@@ -304,6 +305,7 @@ public:
String replacen(const String &p_key, const String &p_with) const;
String repeat(int p_count) const;
String insert(int p_at_pos, const String &p_string) const;
+ String erase(int p_pos, int p_chars = 1) const;
String pad_decimals(int p_digits) const;
String pad_zeros(int p_digits) const;
String trim_prefix(const String &p_prefix) const;
@@ -321,6 +323,8 @@ public:
static String chr(char32_t p_char);
static String md5(const uint8_t *p_md5);
static String hex_encode_buffer(const uint8_t *p_buffer, int p_len);
+ Vector<uint8_t> hex_decode() const;
+
bool is_numeric() const;
double to_float() const;
@@ -335,6 +339,7 @@ public:
static double to_float(const char *p_str);
static double to_float(const wchar_t *p_str, const wchar_t **r_end = nullptr);
static double to_float(const char32_t *p_str, const char32_t **r_end = nullptr);
+ static uint32_t num_characters(int64_t p_int);
String capitalize() const;
String to_camel_case() const;
@@ -430,7 +435,7 @@ public:
String property_name_encode() const;
// node functions
- static const String invalid_node_name_characters;
+ static String get_invalid_node_name_characters();
String validate_node_name() const;
String validate_identifier() const;
String validate_filename() const;
diff --git a/core/templates/hash_map.h b/core/templates/hash_map.h
index f3944fcd0d..4da73f1cfb 100644
--- a/core/templates/hash_map.h
+++ b/core/templates/hash_map.h
@@ -67,9 +67,9 @@ template <class TKey, class TValue,
class Allocator = DefaultTypedAllocator<HashMapElement<TKey, TValue>>>
class HashMap {
public:
- const uint32_t MIN_CAPACITY_INDEX = 2; // Use a prime.
- const float MAX_OCCUPANCY = 0.75;
- const uint32_t EMPTY_HASH = 0;
+ static constexpr uint32_t MIN_CAPACITY_INDEX = 2; // Use a prime.
+ static constexpr float MAX_OCCUPANCY = 0.75;
+ static constexpr uint32_t EMPTY_HASH = 0;
private:
Allocator element_alloc;
@@ -97,7 +97,7 @@ private:
}
bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const {
- if (elements == nullptr) {
+ if (elements == nullptr || num_elements == 0) {
return false; // Failed lookups, no elements
}
@@ -252,7 +252,7 @@ public:
}
void clear() {
- if (elements == nullptr) {
+ if (elements == nullptr || num_elements == 0) {
return;
}
uint32_t capacity = hash_table_size_primes[capacity_index];
diff --git a/core/templates/hash_set.h b/core/templates/hash_set.h
index 97f1b460aa..00f4acbc9c 100644
--- a/core/templates/hash_set.h
+++ b/core/templates/hash_set.h
@@ -80,7 +80,7 @@ private:
}
bool _lookup_pos(const TKey &p_key, uint32_t &r_pos) const {
- if (keys == nullptr) {
+ if (keys == nullptr || num_elements == 0) {
return false; // Failed lookups, no elements
}
@@ -237,7 +237,7 @@ public:
}
void clear() {
- if (keys == nullptr) {
+ if (keys == nullptr || num_elements == 0) {
return;
}
uint32_t capacity = hash_table_size_primes[capacity_index];
diff --git a/core/templates/hashfuncs.h b/core/templates/hashfuncs.h
index 95e6bad2f2..2a212f3dcb 100644
--- a/core/templates/hashfuncs.h
+++ b/core/templates/hashfuncs.h
@@ -386,6 +386,12 @@ struct HashMapHasherDefault {
}
};
+// TODO: Fold this into HashMapHasherDefault once C++20 concepts are allowed
+template <class T>
+struct HashableHasher {
+ static _FORCE_INLINE_ uint32_t hash(const T &hashable) { return hashable.hash(); }
+};
+
template <typename T>
struct HashMapComparatorDefault {
static bool compare(const T &p_lhs, const T &p_rhs) {
diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h
index 5311a94987..b454821a8f 100644
--- a/core/templates/local_vector.h
+++ b/core/templates/local_vector.h
@@ -59,11 +59,7 @@ public:
_FORCE_INLINE_ void push_back(T p_elem) {
if (unlikely(count == capacity)) {
- if (capacity == 0) {
- capacity = 1;
- } else {
- capacity <<= 1;
- }
+ capacity = tight ? (capacity + 1) : MAX((U)1, capacity << 1);
data = (T *)memrealloc(data, capacity * sizeof(T));
CRASH_COND_MSG(!data, "Out of memory");
}
@@ -87,7 +83,7 @@ public:
}
/// Removes the item copying the last value into the position of the one to
- /// remove. It's generally faster than `remove`.
+ /// remove. It's generally faster than `remove_at`.
void remove_at_unordered(U p_index) {
ERR_FAIL_INDEX(p_index, count);
count--;
@@ -99,11 +95,13 @@ public:
}
}
- void erase(const T &p_val) {
+ _FORCE_INLINE_ bool erase(const T &p_val) {
int64_t idx = find(p_val);
if (idx >= 0) {
remove_at(idx);
+ return true;
}
+ return false;
}
void invert() {
@@ -143,12 +141,7 @@ public:
count = p_size;
} else if (p_size > count) {
if (unlikely(p_size > capacity)) {
- if (capacity == 0) {
- capacity = 1;
- }
- while (capacity < p_size) {
- capacity <<= 1;
- }
+ capacity = tight ? p_size : nearest_power_of_2_templated(p_size);
data = (T *)memrealloc(data, capacity * sizeof(T));
CRASH_COND_MSG(!data, "Out of memory");
}
diff --git a/core/templates/paged_allocator.h b/core/templates/paged_allocator.h
index 1cd71ec16c..deb2937771 100644
--- a/core/templates/paged_allocator.h
+++ b/core/templates/paged_allocator.h
@@ -99,7 +99,8 @@ public:
}
}
- void reset(bool p_allow_unfreed = false) {
+private:
+ void _reset(bool p_allow_unfreed) {
if (!p_allow_unfreed || !std::is_trivially_destructible<T>::value) {
ERR_FAIL_COND(allocs_available < pages_allocated * page_size);
}
@@ -116,16 +117,41 @@ public:
allocs_available = 0;
}
}
+
+public:
+ void reset(bool p_allow_unfreed = false) {
+ if (thread_safe) {
+ spin_lock.lock();
+ }
+ _reset(p_allow_unfreed);
+ if (thread_safe) {
+ spin_lock.unlock();
+ }
+ }
+
bool is_configured() const {
- return page_size > 0;
+ if (thread_safe) {
+ spin_lock.lock();
+ }
+ bool result = page_size > 0;
+ if (thread_safe) {
+ spin_lock.unlock();
+ }
+ return result;
}
void configure(uint32_t p_page_size) {
+ if (thread_safe) {
+ spin_lock.lock();
+ }
ERR_FAIL_COND(page_pool != nullptr); //sanity check
ERR_FAIL_COND(p_page_size == 0);
page_size = nearest_power_of_2_templated(p_page_size);
page_mask = page_size - 1;
page_shift = get_shift_from_power_of_2(page_size);
+ if (thread_safe) {
+ spin_lock.unlock();
+ }
}
// Power of 2 recommended because of alignment with OS page sizes.
@@ -135,13 +161,20 @@ public:
}
~PagedAllocator() {
- if (allocs_available < pages_allocated * page_size) {
+ if (thread_safe) {
+ spin_lock.lock();
+ }
+ bool leaked = allocs_available < pages_allocated * page_size;
+ if (leaked) {
if (CoreGlobals::leak_reporting_enabled) {
- ERR_FAIL_COND_MSG(allocs_available < pages_allocated * page_size, String("Pages in use exist at exit in PagedAllocator: ") + String(typeid(T).name()));
+ ERR_PRINT(String("Pages in use exist at exit in PagedAllocator: ") + String(typeid(T).name()));
}
- return;
+ } else {
+ _reset(false);
+ }
+ if (thread_safe) {
+ spin_lock.unlock();
}
- reset();
}
};
diff --git a/core/templates/rid_owner.h b/core/templates/rid_owner.h
index aa858b4796..e2ffabdaf0 100644
--- a/core/templates/rid_owner.h
+++ b/core/templates/rid_owner.h
@@ -117,6 +117,7 @@ class RID_Alloc : public RID_AllocBase {
uint32_t free_element = free_index % elements_in_chunk;
uint32_t validator = (uint32_t)(_gen_id() & 0x7FFFFFFF);
+ CRASH_COND_MSG(validator == 0x7FFFFFFF, "Overflow in RID validator");
uint64_t id = validator;
id <<= 32;
id |= free_index;
@@ -186,7 +187,6 @@ public:
spin_lock.unlock();
}
ERR_FAIL_V_MSG(nullptr, "Attempting to initialize the wrong RID");
- return nullptr;
}
validator_chunks[idx_chunk][idx_element] &= 0x7FFFFFFF; //initialized
@@ -239,7 +239,7 @@ public:
uint32_t validator = uint32_t(id >> 32);
- bool owned = (validator_chunks[idx_chunk][idx_element] & 0x7FFFFFFF) == validator;
+ bool owned = (validator != 0x7FFFFFFF) && (validator_chunks[idx_chunk][idx_element] & 0x7FFFFFFF) == validator;
if (THREAD_SAFE) {
spin_lock.unlock();
diff --git a/core/templates/safe_refcount.h b/core/templates/safe_refcount.h
index 58ed019287..bfc9f6fc9a 100644
--- a/core/templates/safe_refcount.h
+++ b/core/templates/safe_refcount.h
@@ -50,11 +50,14 @@
// value and, as an important benefit, you can be sure the value is properly synchronized
// even with threads that are already running.
-// This is used in very specific areas of the engine where it's critical that these guarantees are held
+// These are used in very specific areas of the engine where it's critical that these guarantees are held
#define SAFE_NUMERIC_TYPE_PUN_GUARANTEES(m_type) \
static_assert(sizeof(SafeNumeric<m_type>) == sizeof(m_type)); \
static_assert(alignof(SafeNumeric<m_type>) == alignof(m_type)); \
static_assert(std::is_trivially_destructible<std::atomic<m_type>>::value);
+#define SAFE_FLAG_TYPE_PUN_GUARANTEES \
+ static_assert(sizeof(SafeFlag) == sizeof(bool)); \
+ static_assert(alignof(SafeFlag) == alignof(bool));
template <class T>
class SafeNumeric {
@@ -102,6 +105,17 @@ public:
return value.fetch_sub(p_value, std::memory_order_acq_rel) - p_value;
}
+ _ALWAYS_INLINE_ T bit_or(T p_value) {
+ return value.fetch_or(p_value, std::memory_order_acq_rel);
+ }
+ _ALWAYS_INLINE_ T bit_and(T p_value) {
+ return value.fetch_and(p_value, std::memory_order_acq_rel);
+ }
+
+ _ALWAYS_INLINE_ T bit_xor(T p_value) {
+ return value.fetch_xor(p_value, std::memory_order_acq_rel);
+ }
+
// Returns the original value instead of the new one
_ALWAYS_INLINE_ T postsub(T p_value) {
return value.fetch_sub(p_value, std::memory_order_acq_rel);
diff --git a/core/templates/self_list.h b/core/templates/self_list.h
index c3d7391d6c..ff6fa953ae 100644
--- a/core/templates/self_list.h
+++ b/core/templates/self_list.h
@@ -99,11 +99,20 @@ public:
p_elem->_root = nullptr;
}
+ void clear() {
+ while (_first) {
+ remove(_first);
+ }
+ }
+
_FORCE_INLINE_ SelfList<T> *first() { return _first; }
_FORCE_INLINE_ const SelfList<T> *first() const { return _first; }
_FORCE_INLINE_ List() {}
- _FORCE_INLINE_ ~List() { ERR_FAIL_COND(_first != nullptr); }
+ _FORCE_INLINE_ ~List() {
+ // A self list must be empty on destruction.
+ DEV_ASSERT(_first == nullptr);
+ }
};
private:
diff --git a/core/templates/vector.h b/core/templates/vector.h
index ae58eb8b16..d8bac0870f 100644
--- a/core/templates/vector.h
+++ b/core/templates/vector.h
@@ -71,12 +71,15 @@ public:
void fill(T p_elem);
void remove_at(int p_index) { _cowdata.remove_at(p_index); }
- void erase(const T &p_val) {
+ _FORCE_INLINE_ bool erase(const T &p_val) {
int idx = find(p_val);
if (idx >= 0) {
remove_at(idx);
+ return true;
}
+ return false;
}
+
void reverse();
_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index 81ac5adba7..9f8fb7e95e 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -83,50 +83,60 @@ struct VariantCaster<const T &> {
}
};
-#define VARIANT_ENUM_CAST(m_enum) \
- MAKE_ENUM_TYPE_INFO(m_enum) \
- template <> \
- struct VariantCaster<m_enum> { \
- static _FORCE_INLINE_ m_enum cast(const Variant &p_variant) { \
- return (m_enum)p_variant.operator int64_t(); \
- } \
- }; \
- template <> \
- struct PtrToArg<m_enum> { \
- _FORCE_INLINE_ static m_enum convert(const void *p_ptr) { \
- return m_enum(*reinterpret_cast<const int64_t *>(p_ptr)); \
- } \
- typedef int64_t EncodeT; \
- _FORCE_INLINE_ static void encode(m_enum p_val, const void *p_ptr) { \
- *(int64_t *)p_ptr = (int64_t)p_val; \
- } \
- }; \
- template <> \
- struct ZeroInitializer<m_enum> { \
- static void initialize(m_enum &value) { value = (m_enum)0; } \
+#define VARIANT_ENUM_CAST(m_enum) \
+ MAKE_ENUM_TYPE_INFO(m_enum) \
+ template <> \
+ struct VariantCaster<m_enum> { \
+ static _FORCE_INLINE_ m_enum cast(const Variant &p_variant) { \
+ return (m_enum)p_variant.operator int64_t(); \
+ } \
+ }; \
+ template <> \
+ struct PtrToArg<m_enum> { \
+ _FORCE_INLINE_ static m_enum convert(const void *p_ptr) { \
+ return m_enum(*reinterpret_cast<const int64_t *>(p_ptr)); \
+ } \
+ typedef int64_t EncodeT; \
+ _FORCE_INLINE_ static void encode(m_enum p_val, const void *p_ptr) { \
+ *(int64_t *)p_ptr = (int64_t)p_val; \
+ } \
+ }; \
+ template <> \
+ struct ZeroInitializer<m_enum> { \
+ static void initialize(m_enum &value) { value = (m_enum)0; } \
+ }; \
+ template <> \
+ struct VariantInternalAccessor<m_enum> { \
+ static _FORCE_INLINE_ m_enum get(const Variant *v) { return m_enum(*VariantInternal::get_int(v)); } \
+ static _FORCE_INLINE_ void set(Variant *v, m_enum p_value) { *VariantInternal::get_int(v) = (int64_t)p_value; } \
};
-#define VARIANT_BITFIELD_CAST(m_enum) \
- MAKE_BITFIELD_TYPE_INFO(m_enum) \
- template <> \
- struct VariantCaster<BitField<m_enum>> { \
- static _FORCE_INLINE_ BitField<m_enum> cast(const Variant &p_variant) { \
- return BitField<m_enum>(p_variant.operator int64_t()); \
- } \
- }; \
- template <> \
- struct PtrToArg<BitField<m_enum>> { \
- _FORCE_INLINE_ static BitField<m_enum> convert(const void *p_ptr) { \
- return BitField<m_enum>(*reinterpret_cast<const int64_t *>(p_ptr)); \
- } \
- typedef int64_t EncodeT; \
- _FORCE_INLINE_ static void encode(BitField<m_enum> p_val, const void *p_ptr) { \
- *(int64_t *)p_ptr = p_val; \
- } \
- }; \
- template <> \
- struct ZeroInitializer<BitField<m_enum>> { \
- static void initialize(BitField<m_enum> &value) { value = 0; } \
+#define VARIANT_BITFIELD_CAST(m_enum) \
+ MAKE_BITFIELD_TYPE_INFO(m_enum) \
+ template <> \
+ struct VariantCaster<BitField<m_enum>> { \
+ static _FORCE_INLINE_ BitField<m_enum> cast(const Variant &p_variant) { \
+ return BitField<m_enum>(p_variant.operator int64_t()); \
+ } \
+ }; \
+ template <> \
+ struct PtrToArg<BitField<m_enum>> { \
+ _FORCE_INLINE_ static BitField<m_enum> convert(const void *p_ptr) { \
+ return BitField<m_enum>(*reinterpret_cast<const int64_t *>(p_ptr)); \
+ } \
+ typedef int64_t EncodeT; \
+ _FORCE_INLINE_ static void encode(BitField<m_enum> p_val, const void *p_ptr) { \
+ *(int64_t *)p_ptr = p_val; \
+ } \
+ }; \
+ template <> \
+ struct ZeroInitializer<BitField<m_enum>> { \
+ static void initialize(BitField<m_enum> &value) { value = 0; } \
+ }; \
+ template <> \
+ struct VariantInternalAccessor<BitField<m_enum>> { \
+ static _FORCE_INLINE_ BitField<m_enum> get(const Variant *v) { return BitField<m_enum>(*VariantInternal::get_int(v)); } \
+ static _FORCE_INLINE_ void set(Variant *v, BitField<m_enum> p_value) { *VariantInternal::get_int(v) = p_value.operator int64_t(); } \
};
// Object enum casts must go here
@@ -597,6 +607,8 @@ void call_with_ptr_args_static_method(void (*p_method)(P...), const void **p_arg
call_with_ptr_args_static_method_helper<P...>(p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
}
+// Validated
+
template <class T, class... P>
void call_with_validated_variant_args(Variant *base, void (T::*p_method)(P...), const Variant **p_args) {
call_with_validated_variant_args_helper<T, P...>(VariantGetInternalPtr<T>::get_ptr(base), p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
@@ -632,6 +644,38 @@ void call_with_validated_variant_args_static_method_ret(R (*p_method)(P...), con
call_with_validated_variant_args_static_method_ret_helper<R, P...>(p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
}
+// Validated Object
+
+template <class T, class... P>
+void call_with_validated_object_instance_args(T *base, void (T::*p_method)(P...), const Variant **p_args) {
+ call_with_validated_variant_args_helper<T, P...>(base, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
+}
+
+template <class T, class... P>
+void call_with_validated_object_instance_argsc(T *base, void (T::*p_method)(P...) const, const Variant **p_args) {
+ call_with_validated_variant_argsc_helper<T, P...>(base, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
+}
+
+template <class T, class R, class... P>
+void call_with_validated_object_instance_args_ret(T *base, R (T::*p_method)(P...), const Variant **p_args, Variant *r_ret) {
+ call_with_validated_variant_args_ret_helper<T, R, P...>(base, p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
+}
+
+template <class T, class R, class... P>
+void call_with_validated_object_instance_args_retc(T *base, R (T::*p_method)(P...) const, const Variant **p_args, Variant *r_ret) {
+ call_with_validated_variant_args_retc_helper<T, R, P...>(base, p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
+}
+
+template <class T, class... P>
+void call_with_validated_object_instance_args_static(T *base, void (*p_method)(T *, P...), const Variant **p_args) {
+ call_with_validated_variant_args_static_helper<T, P...>(base, p_method, p_args, BuildIndexSequence<sizeof...(P)>{});
+}
+
+template <class T, class R, class... P>
+void call_with_validated_object_instance_args_static_retc(T *base, R (*p_method)(T *, P...), const Variant **p_args, Variant *r_ret) {
+ call_with_validated_variant_args_static_retc_helper<T, R, P...>(base, p_method, p_args, r_ret, BuildIndexSequence<sizeof...(P)>{});
+}
+
// GCC raises "parameter 'p_args' set but not used" when P = {},
// it's not clever enough to treat other P values as making this branch valid.
#if defined(__GNUC__) && !defined(__clang__)
diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp
index 2f2acc55a6..630873ec2e 100644
--- a/core/variant/callable.cpp
+++ b/core/variant/callable.cpp
@@ -122,7 +122,11 @@ Callable Callable::unbind(int p_argcount) const {
}
bool Callable::is_valid() const {
- return get_object() && (is_custom() || get_object()->has_method(get_method()));
+ if (is_custom()) {
+ return get_custom()->is_valid();
+ } else {
+ return get_object() && get_object()->has_method(get_method());
+ }
}
Object *Callable::get_object() const {
@@ -373,6 +377,11 @@ Callable::~Callable() {
}
}
+bool CallableCustom::is_valid() const {
+ // Sensible default implementation so most custom callables don't need their own.
+ return ObjectDB::get_instance(get_object());
+}
+
StringName CallableCustom::get_method() const {
ERR_FAIL_V_MSG(StringName(), vformat("Can't get method on CallableCustom \"%s\".", get_as_text()));
}
diff --git a/core/variant/callable.h b/core/variant/callable.h
index 0abbb64c0b..086e5d2a00 100644
--- a/core/variant/callable.h
+++ b/core/variant/callable.h
@@ -145,8 +145,9 @@ public:
virtual String get_as_text() const = 0;
virtual CompareEqualFunc get_compare_equal_func() const = 0;
virtual CompareLessFunc get_compare_less_func() const = 0;
+ virtual bool is_valid() const;
virtual StringName get_method() const;
- virtual ObjectID get_object() const = 0; //must always be able to provide an object
+ virtual ObjectID get_object() const = 0;
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const = 0;
virtual Error rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const;
virtual const Callable *get_base_comparator() const;
diff --git a/core/variant/callable_bind.cpp b/core/variant/callable_bind.cpp
index 5be91c6e11..378d1ff618 100644
--- a/core/variant/callable_bind.cpp
+++ b/core/variant/callable_bind.cpp
@@ -75,6 +75,10 @@ CallableCustom::CompareLessFunc CallableCustomBind::get_compare_less_func() cons
return _less_func;
}
+bool CallableCustomBind::is_valid() const {
+ return callable.is_valid();
+}
+
StringName CallableCustomBind::get_method() const {
return callable.get_method();
}
@@ -193,6 +197,10 @@ CallableCustom::CompareLessFunc CallableCustomUnbind::get_compare_less_func() co
return _less_func;
}
+bool CallableCustomUnbind::is_valid() const {
+ return callable.is_valid();
+}
+
StringName CallableCustomUnbind::get_method() const {
return callable.get_method();
}
diff --git a/core/variant/callable_bind.h b/core/variant/callable_bind.h
index 278ed335d0..b51076ad0f 100644
--- a/core/variant/callable_bind.h
+++ b/core/variant/callable_bind.h
@@ -47,8 +47,9 @@ public:
virtual String get_as_text() const override;
virtual CompareEqualFunc get_compare_equal_func() const override;
virtual CompareLessFunc get_compare_less_func() const override;
+ virtual bool is_valid() const override;
virtual StringName get_method() const override;
- virtual ObjectID get_object() const override; //must always be able to provide an object
+ virtual ObjectID get_object() const override;
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
virtual const Callable *get_base_comparator() const override;
virtual int get_bound_arguments_count() const override;
@@ -73,8 +74,9 @@ public:
virtual String get_as_text() const override;
virtual CompareEqualFunc get_compare_equal_func() const override;
virtual CompareLessFunc get_compare_less_func() const override;
+ virtual bool is_valid() const override;
virtual StringName get_method() const override;
- virtual ObjectID get_object() const override; //must always be able to provide an object
+ virtual ObjectID get_object() const override;
virtual void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override;
virtual const Callable *get_base_comparator() const override;
virtual int get_bound_arguments_count() const override;
diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp
index 0429508cc5..f019273735 100644
--- a/core/variant/dictionary.cpp
+++ b/core/variant/dictionary.cpp
@@ -83,9 +83,16 @@ Variant &Dictionary::operator[](const Variant &p_key) {
if (unlikely(_p->read_only)) {
if (p_key.get_type() == Variant::STRING_NAME) {
const StringName *sn = VariantInternal::get_string_name(&p_key);
- *_p->read_only = _p->variant_map[sn->operator String()];
- } else {
+ const String &key = sn->operator String();
+ if (likely(_p->variant_map.has(key))) {
+ *_p->read_only = _p->variant_map[key];
+ } else {
+ *_p->read_only = Variant();
+ }
+ } else if (likely(_p->variant_map.has(p_key))) {
*_p->read_only = _p->variant_map[p_key];
+ } else {
+ *_p->read_only = Variant();
}
return *_p->read_only;
diff --git a/core/variant/method_ptrcall.h b/core/variant/method_ptrcall.h
index df1e524494..cbfb9cc257 100644
--- a/core/variant/method_ptrcall.h
+++ b/core/variant/method_ptrcall.h
@@ -159,7 +159,10 @@ MAKE_PTRARG_BY_REFERENCE(Variant);
template <class T>
struct PtrToArg<T *> {
_FORCE_INLINE_ static T *convert(const void *p_ptr) {
- return const_cast<T *>(reinterpret_cast<const T *>(p_ptr));
+ if (p_ptr == nullptr) {
+ return nullptr;
+ }
+ return const_cast<T *>(*reinterpret_cast<T *const *>(p_ptr));
}
typedef Object *EncodeT;
_FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) {
@@ -170,7 +173,10 @@ struct PtrToArg<T *> {
template <class T>
struct PtrToArg<const T *> {
_FORCE_INLINE_ static const T *convert(const void *p_ptr) {
- return reinterpret_cast<const T *>(p_ptr);
+ if (p_ptr == nullptr) {
+ return nullptr;
+ }
+ return *reinterpret_cast<T *const *>(p_ptr);
}
typedef const Object *EncodeT;
_FORCE_INLINE_ static void encode(T *p_var, void *p_ptr) {
diff --git a/core/variant/typed_array.h b/core/variant/typed_array.h
index 03e557819b..98afc7e717 100644
--- a/core/variant/typed_array.h
+++ b/core/variant/typed_array.h
@@ -33,6 +33,7 @@
#include "core/object/object.h"
#include "core/variant/array.h"
+#include "core/variant/binder_common.h"
#include "core/variant/method_ptrcall.h"
#include "core/variant/type_info.h"
#include "core/variant/variant.h"
@@ -55,6 +56,17 @@ public:
}
};
+template <class T>
+struct VariantInternalAccessor<TypedArray<T>> {
+ static _FORCE_INLINE_ TypedArray<T> get(const Variant *v) { return *VariantInternal::get_array(v); }
+ static _FORCE_INLINE_ void set(Variant *v, const TypedArray<T> &p_array) { *VariantInternal::get_array(v) = p_array; }
+};
+template <class T>
+struct VariantInternalAccessor<const TypedArray<T> &> {
+ static _FORCE_INLINE_ TypedArray<T> get(const Variant *v) { return *VariantInternal::get_array(v); }
+ static _FORCE_INLINE_ void set(Variant *v, const TypedArray<T> &p_array) { *VariantInternal::get_array(v) = p_array; }
+};
+
//specialization for the rest of variant types
#define MAKE_TYPED_ARRAY(m_type, m_variant_type) \
@@ -117,6 +129,7 @@ MAKE_TYPED_ARRAY(Vector<String>, Variant::PACKED_STRING_ARRAY)
MAKE_TYPED_ARRAY(Vector<Vector2>, Variant::PACKED_VECTOR2_ARRAY)
MAKE_TYPED_ARRAY(Vector<Vector3>, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TYPED_ARRAY(Vector<Color>, Variant::PACKED_COLOR_ARRAY)
+MAKE_TYPED_ARRAY(IPAddress, Variant::STRING)
template <class T>
struct PtrToArg<TypedArray<T>> {
@@ -215,5 +228,6 @@ MAKE_TYPED_ARRAY_INFO(Vector<String>, Variant::PACKED_STRING_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<Vector2>, Variant::PACKED_VECTOR2_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<Vector3>, Variant::PACKED_VECTOR3_ARRAY)
MAKE_TYPED_ARRAY_INFO(Vector<Color>, Variant::PACKED_COLOR_ARRAY)
+MAKE_TYPED_ARRAY_INFO(IPAddress, Variant::STRING)
#endif // TYPED_ARRAY_H
diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp
index fa3bb78913..10a267e5a9 100644
--- a/core/variant/variant.cpp
+++ b/core/variant/variant.cpp
@@ -3659,9 +3659,9 @@ String Variant::get_call_error_text(Object *p_base, const StringName &p_method,
err_text = "Cannot convert argument " + itos(errorarg + 1) + " from [missing argptr, type unknown] to " + Variant::get_type_name(Variant::Type(ce.expected));
}
} else if (ce.error == Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS) {
- err_text = "Method expected " + itos(ce.argument) + " arguments, but called with " + itos(p_argcount);
+ err_text = "Method expected " + itos(ce.expected) + " arguments, but called with " + itos(p_argcount);
} else if (ce.error == Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS) {
- err_text = "Method expected " + itos(ce.argument) + " arguments, but called with " + itos(p_argcount);
+ err_text = "Method expected " + itos(ce.expected) + " arguments, but called with " + itos(p_argcount);
} else if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD) {
err_text = "Method not found";
} else if (ce.error == Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL) {
diff --git a/core/variant/variant.h b/core/variant/variant.h
index f694e59051..04c2fe2012 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -488,7 +488,7 @@ public:
Variant(const IPAddress &p_address);
#define VARIANT_ENUM_CLASS_CONSTRUCTOR(m_enum) \
- Variant(const m_enum &p_value) { \
+ Variant(m_enum p_value) { \
type = INT; \
_data._int = (int64_t)p_value; \
}
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index ae15158836..dad9183216 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1633,6 +1633,7 @@ static void _register_variant_builtin_methods() {
bind_string_method(casecmp_to, sarray("to"), varray());
bind_string_method(nocasecmp_to, sarray("to"), varray());
+ bind_string_method(naturalcasecmp_to, sarray("to"), varray());
bind_string_method(naturalnocasecmp_to, sarray("to"), varray());
bind_string_method(length, sarray(), varray());
bind_string_method(substr, sarray("from", "len"), varray(-1));
@@ -1659,6 +1660,7 @@ static void _register_variant_builtin_methods() {
bind_string_method(replacen, sarray("what", "forwhat"), varray());
bind_string_method(repeat, sarray("count"), varray());
bind_string_method(insert, sarray("position", "what"), varray());
+ bind_string_method(erase, sarray("position", "chars"), varray(1));
bind_string_method(capitalize, sarray(), varray());
bind_string_method(to_camel_case, sarray(), varray());
bind_string_method(to_pascal_case, sarray(), varray());
@@ -1734,6 +1736,7 @@ static void _register_variant_builtin_methods() {
bind_string_method(to_utf8_buffer, sarray(), varray());
bind_string_method(to_utf16_buffer, sarray(), varray());
bind_string_method(to_utf32_buffer, sarray(), varray());
+ bind_string_method(hex_decode, sarray(), varray());
bind_string_method(to_wchar_buffer, sarray(), varray());
bind_static_method(String, num_scientific, sarray("number"), varray());
@@ -1920,7 +1923,7 @@ static void _register_variant_builtin_methods() {
bind_method(Vector4, distance_squared_to, sarray("to"), varray());
bind_method(Vector4, dot, sarray("with"), varray());
bind_method(Vector4, inverse, sarray(), varray());
- bind_method(Vector4, is_equal_approx, sarray("with"), varray());
+ bind_method(Vector4, is_equal_approx, sarray("to"), varray());
bind_method(Vector4, is_zero_approx, sarray(), varray());
bind_method(Vector4, is_finite, sarray(), varray());
@@ -2072,6 +2075,7 @@ static void _register_variant_builtin_methods() {
bind_method(Transform2D, scaled_local, sarray("scale"), varray());
bind_method(Transform2D, translated, sarray("offset"), varray());
bind_method(Transform2D, translated_local, sarray("offset"), varray());
+ bind_method(Transform2D, determinant, sarray(), varray());
bind_method(Transform2D, basis_xform, sarray("v"), varray());
bind_method(Transform2D, basis_xform_inv, sarray("v"), varray());
bind_method(Transform2D, interpolate_with, sarray("xform", "weight"), varray());
@@ -2097,7 +2101,7 @@ static void _register_variant_builtin_methods() {
bind_method(Basis, is_equal_approx, sarray("b"), varray());
bind_method(Basis, is_finite, sarray(), varray());
bind_method(Basis, get_rotation_quaternion, sarray(), varray());
- bind_static_method(Basis, looking_at, sarray("target", "up"), varray(Vector3(0, 1, 0)));
+ bind_static_method(Basis, looking_at, sarray("target", "up", "use_model_front"), varray(Vector3(0, 1, 0), false));
bind_static_method(Basis, from_scale, sarray("scale"), varray());
bind_static_method(Basis, from_euler, sarray("euler", "order"), varray((int64_t)EulerOrder::YXZ));
@@ -2140,7 +2144,7 @@ static void _register_variant_builtin_methods() {
bind_method(Transform3D, scaled_local, sarray("scale"), varray());
bind_method(Transform3D, translated, sarray("offset"), varray());
bind_method(Transform3D, translated_local, sarray("offset"), varray());
- bind_method(Transform3D, looking_at, sarray("target", "up"), varray(Vector3(0, 1, 0)));
+ bind_method(Transform3D, looking_at, sarray("target", "up", "use_model_front"), varray(Vector3(0, 1, 0), false));
bind_method(Transform3D, interpolate_with, sarray("xform", "weight"), varray());
bind_method(Transform3D, is_equal_approx, sarray("xform"), varray());
bind_method(Transform3D, is_finite, sarray(), varray());
@@ -2528,6 +2532,13 @@ static void _register_variant_builtin_methods() {
_VariantCall::add_variant_constant(Variant::VECTOR3, "FORWARD", Vector3(0, 0, -1));
_VariantCall::add_variant_constant(Variant::VECTOR3, "BACK", Vector3(0, 0, 1));
+ _VariantCall::add_variant_constant(Variant::VECTOR3, "MODEL_LEFT", Vector3(1, 0, 0));
+ _VariantCall::add_variant_constant(Variant::VECTOR3, "MODEL_RIGHT", Vector3(-1, 0, 0));
+ _VariantCall::add_variant_constant(Variant::VECTOR3, "MODEL_TOP", Vector3(0, 1, 0));
+ _VariantCall::add_variant_constant(Variant::VECTOR3, "MODEL_BOTTOM", Vector3(0, -1, 0));
+ _VariantCall::add_variant_constant(Variant::VECTOR3, "MODEL_FRONT", Vector3(0, 0, 1));
+ _VariantCall::add_variant_constant(Variant::VECTOR3, "MODEL_REAR", Vector3(0, 0, -1));
+
_VariantCall::add_constant(Variant::VECTOR4, "AXIS_X", Vector4::AXIS_X);
_VariantCall::add_constant(Variant::VECTOR4, "AXIS_Y", Vector4::AXIS_Y);
_VariantCall::add_constant(Variant::VECTOR4, "AXIS_Z", Vector4::AXIS_Z);
diff --git a/core/variant/variant_construct.cpp b/core/variant/variant_construct.cpp
index 950f4a62d8..3427950224 100644
--- a/core/variant/variant_construct.cpp
+++ b/core/variant/variant_construct.cpp
@@ -317,6 +317,17 @@ String Variant::get_constructor_argument_name(Variant::Type p_type, int p_constr
return construct_data[p_type][p_constructor].arg_names[p_argument];
}
+void VariantInternal::refcounted_object_assign(Variant *v, const RefCounted *rc) {
+ if (!rc || !const_cast<RefCounted *>(rc)->init_ref()) {
+ v->_get_obj().obj = nullptr;
+ v->_get_obj().id = ObjectID();
+ return;
+ }
+
+ v->_get_obj().obj = const_cast<RefCounted *>(rc);
+ v->_get_obj().id = rc->get_instance_id();
+}
+
void VariantInternal::object_assign(Variant *v, const Object *o) {
if (o) {
if (o->is_ref_counted()) {
diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h
index 0d55ee4ae2..782053b613 100644
--- a/core/variant/variant_internal.h
+++ b/core/variant/variant_internal.h
@@ -35,6 +35,9 @@
// For use when you want to access the internal pointer of a Variant directly.
// Use with caution. You need to be sure that the type is correct.
+
+class RefCounted;
+
class VariantInternal {
friend class Variant;
@@ -320,6 +323,7 @@ public:
}
static void object_assign(Variant *v, const Object *o); // Needs RefCounted, so it's implemented elsewhere.
+ static void refcounted_object_assign(Variant *v, const RefCounted *rc);
_FORCE_INLINE_ static void object_assign(Variant *v, const Variant *o) {
object_assign(v, o->_get_obj().obj);
@@ -498,7 +502,7 @@ public:
case Variant::PACKED_COLOR_ARRAY:
return get_color_array(v);
case Variant::OBJECT:
- return v->_get_obj().obj;
+ return get_object(v);
case Variant::VARIANT_MAX:
ERR_FAIL_V(nullptr);
}
@@ -820,28 +824,28 @@ VARIANT_ACCESSOR_NUMBER(int64_t)
VARIANT_ACCESSOR_NUMBER(uint64_t)
VARIANT_ACCESSOR_NUMBER(char32_t)
-// Bind enums to allow using them as return types.
-VARIANT_ACCESSOR_NUMBER(Error)
-VARIANT_ACCESSOR_NUMBER(Side)
-VARIANT_ACCESSOR_NUMBER(Vector2::Axis)
-VARIANT_ACCESSOR_NUMBER(Vector2i::Axis)
-VARIANT_ACCESSOR_NUMBER(Vector3::Axis)
-VARIANT_ACCESSOR_NUMBER(Vector3i::Axis)
-VARIANT_ACCESSOR_NUMBER(Vector4::Axis)
-VARIANT_ACCESSOR_NUMBER(Vector4i::Axis)
+template <>
+struct VariantInternalAccessor<ObjectID> {
+ static _FORCE_INLINE_ ObjectID get(const Variant *v) { return ObjectID(*VariantInternal::get_int(v)); }
+ static _FORCE_INLINE_ void set(Variant *v, ObjectID p_value) { *VariantInternal::get_int(v) = p_value; }
+};
-VARIANT_ACCESSOR_NUMBER(Projection::Planes)
+template <class T>
+struct VariantInternalAccessor<T *> {
+ static _FORCE_INLINE_ T *get(const Variant *v) { return const_cast<T *>(static_cast<const T *>(*VariantInternal::get_object(v))); }
+ static _FORCE_INLINE_ void set(Variant *v, const T *p_value) { VariantInternal::object_assign(v, p_value); }
+};
-template <>
-struct VariantInternalAccessor<EulerOrder> {
- static _FORCE_INLINE_ EulerOrder get(const Variant *v) { return EulerOrder(*VariantInternal::get_int(v)); }
- static _FORCE_INLINE_ void set(Variant *v, EulerOrder p_value) { *VariantInternal::get_int(v) = (int64_t)p_value; }
+template <class T>
+struct VariantInternalAccessor<const T *> {
+ static _FORCE_INLINE_ const T *get(const Variant *v) { return static_cast<const T *>(*VariantInternal::get_object(v)); }
+ static _FORCE_INLINE_ void set(Variant *v, const T *p_value) { VariantInternal::object_assign(v, p_value); }
};
template <>
-struct VariantInternalAccessor<ObjectID> {
- static _FORCE_INLINE_ ObjectID get(const Variant *v) { return ObjectID(*VariantInternal::get_int(v)); }
- static _FORCE_INLINE_ void set(Variant *v, ObjectID p_value) { *VariantInternal::get_int(v) = p_value; }
+struct VariantInternalAccessor<IPAddress> {
+ static _FORCE_INLINE_ IPAddress get(const Variant *v) { return IPAddress(*VariantInternal::get_string(v)); }
+ static _FORCE_INLINE_ void set(Variant *v, IPAddress p_value) { *VariantInternal::get_string(v) = p_value; }
};
template <>
@@ -1530,14 +1534,14 @@ struct VariantTypeAdjust<Object *> {
template <class T>
struct VariantTypeConstructor {
- _FORCE_INLINE_ static void variant_from_type(void *p_variant, void *p_value) {
- Variant *variant = reinterpret_cast<Variant *>(p_variant);
- VariantInitializer<T>::init(variant);
- VariantInternalAccessor<T>::set(variant, *((T *)p_value));
+ _FORCE_INLINE_ static void variant_from_type(void *r_variant, void *p_value) {
+ // r_variant is provided by caller as uninitialized memory
+ memnew_placement(r_variant, Variant(*((T *)p_value)));
}
- _FORCE_INLINE_ static void type_from_variant(void *p_value, void *p_variant) {
- *((T *)p_value) = VariantInternalAccessor<T>::get(reinterpret_cast<Variant *>(p_variant));
+ _FORCE_INLINE_ static void type_from_variant(void *r_value, void *p_variant) {
+ // r_value is provided by caller as uninitialized memory
+ memnew_placement(r_value, T(*reinterpret_cast<Variant *>(p_variant)));
}
};
diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp
index 33c285dc6d..aed83ac010 100644
--- a/core/variant/variant_op.cpp
+++ b/core/variant/variant_op.cpp
@@ -900,6 +900,39 @@ void Variant::_register_variant_operators() {
register_op<OperatorEvaluatorNotInt>(Variant::OP_NOT, Variant::INT, Variant::NIL);
register_op<OperatorEvaluatorNotFloat>(Variant::OP_NOT, Variant::FLOAT, Variant::NIL);
register_op<OperatorEvaluatorNotObject>(Variant::OP_NOT, Variant::OBJECT, Variant::NIL);
+ register_op<OperatorEvaluatorNot<String>>(Variant::OP_NOT, Variant::STRING, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Vector2>>(Variant::OP_NOT, Variant::VECTOR2, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Vector2i>>(Variant::OP_NOT, Variant::VECTOR2I, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Rect2>>(Variant::OP_NOT, Variant::RECT2, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Rect2i>>(Variant::OP_NOT, Variant::RECT2I, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Vector3>>(Variant::OP_NOT, Variant::VECTOR3, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Vector3i>>(Variant::OP_NOT, Variant::VECTOR3I, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Transform2D>>(Variant::OP_NOT, Variant::TRANSFORM2D, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Vector4>>(Variant::OP_NOT, Variant::VECTOR4, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Vector4i>>(Variant::OP_NOT, Variant::VECTOR4I, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Plane>>(Variant::OP_NOT, Variant::PLANE, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Quaternion>>(Variant::OP_NOT, Variant::QUATERNION, Variant::NIL);
+ register_op<OperatorEvaluatorNot<::AABB>>(Variant::OP_NOT, Variant::AABB, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Basis>>(Variant::OP_NOT, Variant::BASIS, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Transform3D>>(Variant::OP_NOT, Variant::TRANSFORM3D, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Projection>>(Variant::OP_NOT, Variant::PROJECTION, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Color>>(Variant::OP_NOT, Variant::COLOR, Variant::NIL);
+ register_op<OperatorEvaluatorNot<StringName>>(Variant::OP_NOT, Variant::STRING_NAME, Variant::NIL);
+ register_op<OperatorEvaluatorNot<NodePath>>(Variant::OP_NOT, Variant::NODE_PATH, Variant::NIL);
+ register_op<OperatorEvaluatorNot<::RID>>(Variant::OP_NOT, Variant::RID, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Callable>>(Variant::OP_NOT, Variant::CALLABLE, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Signal>>(Variant::OP_NOT, Variant::SIGNAL, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Dictionary>>(Variant::OP_NOT, Variant::DICTIONARY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<Array>>(Variant::OP_NOT, Variant::ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedByteArray>>(Variant::OP_NOT, Variant::PACKED_BYTE_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedInt32Array>>(Variant::OP_NOT, Variant::PACKED_INT32_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedInt64Array>>(Variant::OP_NOT, Variant::PACKED_INT64_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedFloat32Array>>(Variant::OP_NOT, Variant::PACKED_FLOAT32_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedFloat64Array>>(Variant::OP_NOT, Variant::PACKED_FLOAT64_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedStringArray>>(Variant::OP_NOT, Variant::PACKED_STRING_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedVector2Array>>(Variant::OP_NOT, Variant::PACKED_VECTOR2_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedVector3Array>>(Variant::OP_NOT, Variant::PACKED_VECTOR3_ARRAY, Variant::NIL);
+ register_op<OperatorEvaluatorNot<PackedColorArray>>(Variant::OP_NOT, Variant::PACKED_COLOR_ARRAY, Variant::NIL);
register_string_op(OperatorEvaluatorInStringFind, Variant::OP_IN);
diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h
index d2163cf92d..c11f726402 100644
--- a/core/variant/variant_op.h
+++ b/core/variant/variant_op.h
@@ -805,14 +805,14 @@ class OperatorEvaluatorNot {
public:
static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) {
const A &a = *VariantGetInternalPtr<A>::get_ptr(&p_left);
- *r_ret = !a;
+ *r_ret = a == A();
r_valid = true;
}
static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) {
- *VariantGetInternalPtr<bool>::get_ptr(r_ret) = !*VariantGetInternalPtr<A>::get_ptr(left);
+ *VariantGetInternalPtr<bool>::get_ptr(r_ret) = *VariantGetInternalPtr<A>::get_ptr(left) == A();
}
static void ptr_evaluate(const void *left, const void *right, void *r_ret) {
- PtrToArg<bool>::encode(!PtrToArg<A>::convert(left));
+ PtrToArg<bool>::encode(PtrToArg<A>::convert(left) == A(), r_ret);
}
static Variant::Type get_return_type() { return Variant::BOOL; }
};
@@ -824,6 +824,11 @@ public:
_FORCE_INLINE_ static void _add_arrays(Array &sum, const Array &array_a, const Array &array_b) {
int asize = array_a.size();
int bsize = array_b.size();
+
+ if (array_a.is_typed() && array_a.is_same_typed(array_b)) {
+ sum.set_typed(array_a.get_typed_builtin(), array_a.get_typed_class_name(), array_a.get_typed_script());
+ }
+
sum.resize(asize + bsize);
for (int i = 0; i < asize; i++) {
sum[i] = array_a[i];
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index a6363039ba..545825011a 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -374,6 +374,7 @@ struct VariantUtilityFunctions {
r_error.error = Callable::CallError::CALL_OK;
if (from.get_type() != to.get_type()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT;
+ r_error.expected = from.get_type();
r_error.argument = 1;
return Variant();
}
@@ -803,6 +804,8 @@ struct VariantUtilityFunctions {
r_error.error = Callable::CallError::CALL_OK;
}
+#undef print_verbose
+
static inline void print_verbose(const Variant **p_args, int p_arg_count, Callable::CallError &r_error) {
if (OS::get_singleton()->is_stdout_verbose()) {
String s;
diff --git a/core/version.h b/core/version.h
index 8ed7fe4af3..5ddb09284e 100644
--- a/core/version.h
+++ b/core/version.h
@@ -40,13 +40,13 @@
// Defines the main "branch" version. Patch versions in this branch should be
// forward-compatible.
// Example: "3.1"
-#define VERSION_BRANCH "" _MKSTR(VERSION_MAJOR) "." _MKSTR(VERSION_MINOR)
+#define VERSION_BRANCH _MKSTR(VERSION_MAJOR) "." _MKSTR(VERSION_MINOR)
#if VERSION_PATCH
// Example: "3.1.4"
-#define VERSION_NUMBER "" VERSION_BRANCH "." _MKSTR(VERSION_PATCH)
+#define VERSION_NUMBER VERSION_BRANCH "." _MKSTR(VERSION_PATCH)
#else // patch is 0, we don't include it in the "pretty" version number.
// Example: "3.1" instead of "3.1.0"
-#define VERSION_NUMBER "" VERSION_BRANCH
+#define VERSION_NUMBER VERSION_BRANCH
#endif // VERSION_PATCH
// Version number encoded as hexadecimal int with one byte for each number,
@@ -57,16 +57,16 @@
// Describes the full configuration of that Godot version, including the version number,
// the status (beta, stable, etc.) and potential module-specific features (e.g. mono).
// Example: "3.1.4.stable.mono"
-#define VERSION_FULL_CONFIG "" VERSION_NUMBER "." VERSION_STATUS VERSION_MODULE_CONFIG
+#define VERSION_FULL_CONFIG VERSION_NUMBER "." VERSION_STATUS VERSION_MODULE_CONFIG
// Similar to VERSION_FULL_CONFIG, but also includes the (potentially custom) VERSION_BUILD
// description (e.g. official, custom_build, etc.).
// Example: "3.1.4.stable.mono.official"
-#define VERSION_FULL_BUILD "" VERSION_FULL_CONFIG "." VERSION_BUILD
+#define VERSION_FULL_BUILD VERSION_FULL_CONFIG "." VERSION_BUILD
// Same as above, but prepended with Godot's name and a cosmetic "v" for "version".
// Example: "Godot v3.1.4.stable.official.mono"
-#define VERSION_FULL_NAME "" VERSION_NAME " v" VERSION_FULL_BUILD
+#define VERSION_FULL_NAME VERSION_NAME " v" VERSION_FULL_BUILD
// Git commit hash, generated at build time in `core/version_hash.gen.cpp`.
extern const char *const VERSION_HASH;