diff options
Diffstat (limited to 'core')
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; |