diff options
Diffstat (limited to 'core')
99 files changed, 1770 insertions, 792 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 0e27d556ec..24080c056a 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -176,14 +176,14 @@ TypedArray<Dictionary> Engine::get_copyright_info() const { Dictionary Engine::get_donor_info() const { Dictionary donors; - donors["platinum_sponsors"] = array_from_info(DONORS_SPONSOR_PLATINUM); - donors["gold_sponsors"] = array_from_info(DONORS_SPONSOR_GOLD); - donors["silver_sponsors"] = array_from_info(DONORS_SPONSOR_SILVER); - donors["bronze_sponsors"] = array_from_info(DONORS_SPONSOR_BRONZE); - donors["mini_sponsors"] = array_from_info(DONORS_SPONSOR_MINI); - donors["gold_donors"] = array_from_info(DONORS_GOLD); - donors["silver_donors"] = array_from_info(DONORS_SILVER); - donors["bronze_donors"] = array_from_info(DONORS_BRONZE); + donors["patrons"] = array_from_info(DONORS_PATRONS); + donors["platinum_sponsors"] = array_from_info(DONORS_SPONSORS_PLATINUM); + donors["gold_sponsors"] = array_from_info(DONORS_SPONSORS_GOLD); + donors["silver_sponsors"] = array_from_info(DONORS_SPONSORS_SILVER); + donors["diamond_members"] = array_from_info(DONORS_MEMBERS_DIAMOND); + donors["titanium_members"] = array_from_info(DONORS_MEMBERS_TITANIUM); + donors["platinum_members"] = array_from_info(DONORS_MEMBERS_PLATINUM); + donors["gold_members"] = array_from_info(DONORS_MEMBERS_GOLD); return donors; } diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 3049994240..90e2e27320 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -95,7 +95,7 @@ const PackedStringArray ProjectSettings::_get_supported_features() { features.append(VERSION_FULL_CONFIG); features.append(VERSION_FULL_BUILD); -#ifdef VULKAN_ENABLED +#ifdef RD_ENABLED features.append("Forward Plus"); features.append("Mobile"); #endif @@ -281,6 +281,11 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { if (autoloads.has(node_name)) { remove_autoload(node_name); } + } else if (p_name.operator String().begins_with("global_group/")) { + String group_name = p_name.operator String().get_slice("/", 1); + if (global_groups.has(group_name)) { + remove_global_group(group_name); + } } } else { if (p_name == CoreStringNames::get_singleton()->_custom_features) { @@ -327,6 +332,9 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) { autoload.path = path; } add_autoload(autoload); + } else if (p_name.operator String().begins_with("global_group/")) { + String group_name = p_name.operator String().get_slice("/", 1); + add_global_group(group_name, p_value); } } @@ -674,6 +682,8 @@ Error ProjectSettings::setup(const String &p_path, const String &p_main_pack, bo Compression::gzip_level = GLOBAL_GET("compression/formats/gzip/compression_level"); + load_scene_groups_cache(); + project_loaded = err == OK; return err; } @@ -852,8 +862,8 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S } if (!p_custom_features.is_empty()) { + // Store how many properties are saved, add one for custom features, which must always go first. file->store_32(count + 1); - //store how many properties are saved, add one for custom featuers, which must always go first String key = CoreStringNames::get_singleton()->_custom_features; file->store_pascal_string(key); @@ -870,7 +880,8 @@ Error ProjectSettings::_save_settings_binary(const String &p_file, const RBMap<S file->store_buffer(buff.ptr(), buff.size()); } else { - file->store_32(count); //store how many properties are saved + // Store how many properties are saved. + file->store_32(count); } for (const KeyValue<String, List<String>> &E : p_props) { @@ -1240,6 +1251,73 @@ ProjectSettings::AutoloadInfo ProjectSettings::get_autoload(const StringName &p_ return autoloads[p_name]; } +const HashMap<StringName, String> &ProjectSettings::get_global_groups_list() const { + return global_groups; +} + +void ProjectSettings::add_global_group(const StringName &p_name, const String &p_description) { + ERR_FAIL_COND_MSG(p_name == StringName(), "Trying to add global group with no name."); + global_groups[p_name] = p_description; +} + +void ProjectSettings::remove_global_group(const StringName &p_name) { + ERR_FAIL_COND_MSG(!global_groups.has(p_name), "Trying to remove non-existent global group."); + global_groups.erase(p_name); +} + +bool ProjectSettings::has_global_group(const StringName &p_name) const { + return global_groups.has(p_name); +} + +void ProjectSettings::remove_scene_groups_cache(const StringName &p_path) { + scene_groups_cache.erase(p_path); +} + +void ProjectSettings::add_scene_groups_cache(const StringName &p_path, const HashSet<StringName> &p_cache) { + scene_groups_cache[p_path] = p_cache; +} + +void ProjectSettings::save_scene_groups_cache() { + Ref<ConfigFile> cf; + cf.instantiate(); + for (const KeyValue<StringName, HashSet<StringName>> &E : scene_groups_cache) { + if (E.value.is_empty()) { + continue; + } + Array list; + for (const StringName &group : E.value) { + list.push_back(group); + } + cf->set_value(E.key, "groups", list); + } + cf->save(get_scene_groups_cache_path()); +} + +String ProjectSettings::get_scene_groups_cache_path() const { + return get_project_data_path().path_join("scene_groups_cache.cfg"); +} + +void ProjectSettings::load_scene_groups_cache() { + Ref<ConfigFile> cf; + cf.instantiate(); + if (cf->load(get_scene_groups_cache_path()) == OK) { + List<String> scene_paths; + cf->get_sections(&scene_paths); + for (const String &E : scene_paths) { + Array scene_groups = cf->get_value(E, "groups", Array()); + HashSet<StringName> cache; + for (int i = 0; i < scene_groups.size(); ++i) { + cache.insert(scene_groups[i]); + } + add_scene_groups_cache(E, cache); + } + } +} + +const HashMap<StringName, HashSet<StringName>> &ProjectSettings::get_scene_groups_cache() const { + return scene_groups_cache; +} + void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("has_setting", "name"), &ProjectSettings::has_setting); ClassDB::bind_method(D_METHOD("set_setting", "name", "value"), &ProjectSettings::set_setting); @@ -1315,8 +1393,8 @@ ProjectSettings::ProjectSettings() { // - Have a 16:9 aspect ratio, // - Have both dimensions divisible by 8 to better play along with video recording, // - Be displayable correctly in windowed mode on a 1366×768 display (tested on Windows 10 with default settings). - GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/viewport_width", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"), 1152); // 8K resolution - GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/viewport_height", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 648); // 8K resolution + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/viewport_width", PROPERTY_HINT_RANGE, "1,7680,1,or_greater"), 1152); // 8K resolution + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/viewport_height", PROPERTY_HINT_RANGE, "1,4320,1,or_greater"), 648); // 8K resolution GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "display/window/size/mode", PROPERTY_HINT_ENUM, "Windowed,Minimized,Maximized,Fullscreen,Exclusive Fullscreen"), 0); @@ -1332,8 +1410,8 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("display/window/size/extend_to_title", false); GLOBAL_DEF("display/window/size/no_focus", false); - GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"), 0); // 8K resolution - GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"), 0); // 8K resolution + GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_width_override", PROPERTY_HINT_RANGE, "1,7680,1,or_greater"), 0); // 8K resolution + GLOBAL_DEF(PropertyInfo(Variant::INT, "display/window/size/window_height_override", PROPERTY_HINT_RANGE, "1,4320,1,or_greater"), 0); // 8K resolution GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true); GLOBAL_DEF("display/window/energy_saving/keep_screen_on.editor", false); @@ -1343,6 +1421,9 @@ ProjectSettings::ProjectSettings() { 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); + GLOBAL_DEF(PropertyInfo(Variant::INT, "audio/general/ios/session_category", PROPERTY_HINT_ENUM, "Ambient,Multi Route,Play and Record,Playback,Record,Solo Ambient"), 0); + GLOBAL_DEF("audio/general/ios/mix_with_others", false); + PackedStringArray extensions; extensions.push_back("gd"); if (Engine::get_singleton()->has_singleton("GodotSharp")) { @@ -1381,20 +1462,28 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("debug/settings/crash_handler/message.editor", String("Please include this when reporting the bug on: https://github.com/godotengine/godot/issues")); GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/occlusion_culling/bvh_build_quality", PROPERTY_HINT_ENUM, "Low,Medium,High"), 2); - GLOBAL_DEF(PropertyInfo(Variant::INT, "memory/limits/multithreaded_server/rid_pool_prealloc", PROPERTY_HINT_RANGE, "0,500,1"), 60); // No negative and limit to 500 due to crashes. GLOBAL_DEF_RST("internationalization/rendering/force_right_to_left_layout_direction", false); - GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_ENUM, "Based on Locale,Left-to-Right,Right-to-Left"), 0); + GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "internationalization/rendering/root_node_layout_direction", PROPERTY_HINT_ENUM, "Based on Application Locale,Left-to-Right,Right-to-Left,Based on System Locale"), 0); GLOBAL_DEF(PropertyInfo(Variant::INT, "gui/timers/incremental_search_max_interval_msec", PROPERTY_HINT_RANGE, "0,10000,1,or_greater"), 2000); GLOBAL_DEF_BASIC("gui/common/snap_controls_to_pixels", true); GLOBAL_DEF_BASIC("gui/fonts/dynamic_fonts/use_oversampling", true); - 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(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/block_size_kb", PROPERTY_HINT_RANGE, "4,2048,1,or_greater"), 256); + GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/max_size_mb", PROPERTY_HINT_RANGE, "1,1024,1,or_greater"), 128); + GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/staging_buffer/texture_upload_region_size_px", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64); + GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "rendering/rendering_device/pipeline_cache/save_chunk_size_mb", PROPERTY_HINT_RANGE, "0.000001,64.0,0.001,or_greater"), 3.0); + GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/rendering_device/vulkan/max_descriptors_per_pool", PROPERTY_HINT_RANGE, "1,256,1,or_greater"), 64); + + GLOBAL_DEF_RST("rendering/rendering_device/d3d12/max_resource_descriptors_per_frame", 16384); + custom_prop_info["rendering/rendering_device/d3d12/max_resource_descriptors_per_frame"] = PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/max_resource_descriptors_per_frame", PROPERTY_HINT_RANGE, "512,262144"); + GLOBAL_DEF_RST("rendering/rendering_device/d3d12/max_sampler_descriptors_per_frame", 1024); + custom_prop_info["rendering/rendering_device/d3d12/max_sampler_descriptors_per_frame"] = PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/max_sampler_descriptors_per_frame", PROPERTY_HINT_RANGE, "256,2048"); + GLOBAL_DEF_RST("rendering/rendering_device/d3d12/max_misc_descriptors_per_frame", 512); + custom_prop_info["rendering/rendering_device/d3d12/max_misc_descriptors_per_frame"] = PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/max_misc_descriptors_per_frame", PROPERTY_HINT_RANGE, "32,4096"); + + GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/rendering_device/d3d12/agility_sdk_version"), 610); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), 1); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/textures/canvas_textures/default_texture_repeat", PROPERTY_HINT_ENUM, "Disable,Enable,Mirror"), 0); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index 302df7e8d0..55d5957ad1 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -106,6 +106,8 @@ protected: LocalVector<String> hidden_prefixes; HashMap<StringName, AutoloadInfo> autoloads; + HashMap<StringName, String> global_groups; + HashMap<StringName, HashSet<StringName>> scene_groups_cache; Array global_class_list; bool is_global_class_list_loaded = false; @@ -208,6 +210,18 @@ public: bool has_autoload(const StringName &p_autoload) const; AutoloadInfo get_autoload(const StringName &p_name) const; + const HashMap<StringName, String> &get_global_groups_list() const; + void add_global_group(const StringName &p_name, const String &p_description); + void remove_global_group(const StringName &p_name); + bool has_global_group(const StringName &p_name) const; + + const HashMap<StringName, HashSet<StringName>> &get_scene_groups_cache() const; + void add_scene_groups_cache(const StringName &p_path, const HashSet<StringName> &p_cache); + void remove_scene_groups_cache(const StringName &p_path); + void save_scene_groups_cache(); + String get_scene_groups_cache_path() const; + void load_scene_groups_cache(); + ProjectSettings(); ~ProjectSettings(); }; diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 05fe393a2f..d91c659d1e 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -662,6 +662,7 @@ void OS::_bind_methods() { BIND_ENUM_CONSTANT(RENDERING_DRIVER_VULKAN); BIND_ENUM_CONSTANT(RENDERING_DRIVER_OPENGL3); + BIND_ENUM_CONSTANT(RENDERING_DRIVER_D3D12); BIND_ENUM_CONSTANT(SYSTEM_DIR_DESKTOP); BIND_ENUM_CONSTANT(SYSTEM_DIR_DCIM); @@ -1211,8 +1212,7 @@ void Thread::_start_func(void *ud) { Ref<Thread> t = *tud; memdelete(tud); - Object *target_instance = t->target_callable.get_object(); - if (!target_instance) { + if (!t->target_callable.is_valid()) { t->running.clear(); 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())); } diff --git a/core/core_bind.h b/core/core_bind.h index 5f51b64eb7..715e26cf23 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -129,6 +129,7 @@ public: enum RenderingDriver { RENDERING_DRIVER_VULKAN, RENDERING_DRIVER_OPENGL3, + RENDERING_DRIVER_D3D12, }; virtual PackedStringArray get_connected_midi_inputs(); diff --git a/core/core_builders.py b/core/core_builders.py index e40ebbb14d..8b6b87ad83 100644 --- a/core/core_builders.py +++ b/core/core_builders.py @@ -117,24 +117,24 @@ def make_authors_header(target, source, env): def make_donors_header(target, source, env): sections = [ + "Patrons", "Platinum sponsors", "Gold sponsors", "Silver sponsors", - "Bronze sponsors", - "Mini sponsors", - "Gold donors", - "Silver donors", - "Bronze donors", + "Diamond members", + "Titanium members", + "Platinum members", + "Gold members", ] sections_id = [ - "DONORS_SPONSOR_PLATINUM", - "DONORS_SPONSOR_GOLD", - "DONORS_SPONSOR_SILVER", - "DONORS_SPONSOR_BRONZE", - "DONORS_SPONSOR_MINI", - "DONORS_GOLD", - "DONORS_SILVER", - "DONORS_BRONZE", + "DONORS_PATRONS", + "DONORS_SPONSORS_PLATINUM", + "DONORS_SPONSORS_GOLD", + "DONORS_SPONSORS_SILVER", + "DONORS_MEMBERS_DIAMOND", + "DONORS_MEMBERS_TITANIUM", + "DONORS_MEMBERS_PLATINUM", + "DONORS_MEMBERS_GOLD", ] src = source[0] diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 33b3271495..2f70fdf219 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -794,6 +794,8 @@ void register_global_constants() { void unregister_global_constants() { _global_constants.clear(); + _global_constants_map.clear(); + _global_enums.clear(); } int CoreConstants::get_global_constant_count() { diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index 58cb51245a..543dabfb16 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -502,7 +502,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::ConstantDoc &constant_doc : global_scope_doc->constants) { if (constant_doc.name == name) { - d["documentation"] = fix_doc_description(constant_doc.description); + d["description"] = fix_doc_description(constant_doc.description); break; } } @@ -521,7 +521,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { const DocData::EnumDoc *enum_doc = global_scope_doc->enums.getptr(E.key); if (enum_doc) { - d1["documentation"] = fix_doc_description(enum_doc->description); + d1["description"] = fix_doc_description(enum_doc->description); } } Array values; @@ -532,7 +532,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::ConstantDoc &constant_doc : global_scope_doc->constants) { if (constant_doc.name == F.first) { - d2["documentation"] = fix_doc_description(constant_doc.description); + d2["description"] = fix_doc_description(constant_doc.description); break; } } @@ -596,7 +596,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::MethodDoc &method_doc : global_scope_doc->methods) { if (method_doc.name == name) { - func["documentation"] = fix_doc_description(method_doc.description); + func["description"] = fix_doc_description(method_doc.description); break; } } @@ -647,7 +647,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::PropertyDoc &property_doc : builtin_doc->properties) { if (property_doc.name == member_name) { - d2["documentation"] = fix_doc_description(property_doc.description); + d2["description"] = fix_doc_description(property_doc.description); break; } } @@ -673,7 +673,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::ConstantDoc &constant_doc : builtin_doc->constants) { if (constant_doc.name == constant_name) { - d2["documentation"] = fix_doc_description(constant_doc.description); + d2["description"] = fix_doc_description(constant_doc.description); break; } } @@ -706,7 +706,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::ConstantDoc &constant_doc : builtin_doc->constants) { if (constant_doc.name == enumeration) { - values_dict["documentation"] = fix_doc_description(constant_doc.description); + values_dict["description"] = fix_doc_description(constant_doc.description); break; } } @@ -717,7 +717,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { const DocData::EnumDoc *enum_doc = builtin_doc->enums.getptr(enum_name); if (enum_doc) { - enum_dict["documentation"] = fix_doc_description(enum_doc->description); + enum_dict["description"] = fix_doc_description(enum_doc->description); } } @@ -742,15 +742,20 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { Dictionary d2; String operator_name = Variant::get_operator_name(Variant::Operator(k)); d2["name"] = operator_name; - if (k != Variant::OP_NEGATE && k != Variant::OP_POSITIVE && k != Variant::OP_NOT && k != Variant::OP_BIT_NEGATE) { - d2["right_type"] = get_builtin_or_variant_type_name(Variant::Type(j)); + + String right_type_name = get_builtin_or_variant_type_name(Variant::Type(j)); + bool is_unary = k == Variant::OP_NEGATE || k == Variant::OP_POSITIVE || k == Variant::OP_NOT || k == Variant::OP_BIT_NEGATE; + if (!is_unary) { + d2["right_type"] = right_type_name; } + d2["return_type"] = get_builtin_or_variant_type_name(Variant::get_operator_return_type(Variant::Operator(k), type, Variant::Type(j))); if (p_include_docs && builtin_doc != nullptr) { for (const DocData::MethodDoc &operator_doc : builtin_doc->operators) { - if (operator_doc.name == "operator " + operator_name) { - d2["documentation"] = fix_doc_description(operator_doc.description); + if (operator_doc.name == "operator " + operator_name && + (is_unary || operator_doc.arguments[0].type == right_type_name)) { + d2["description"] = fix_doc_description(operator_doc.description); break; } } @@ -805,7 +810,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::MethodDoc &method_doc : builtin_doc->methods) { if (method_doc.name == method_name) { - d2["documentation"] = fix_doc_description(method_doc.description); + d2["description"] = fix_doc_description(method_doc.description); break; } } @@ -853,7 +858,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { } } if (constructor_found) { - d2["documentation"] = fix_doc_description(constructor_doc.description); + d2["description"] = fix_doc_description(constructor_doc.description); } } } @@ -871,7 +876,8 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { } if (p_include_docs && builtin_doc != nullptr) { - d["documentation"] = fix_doc_description(builtin_doc->description); + d["brief_description"] = fix_doc_description(builtin_doc->brief_description); + d["description"] = fix_doc_description(builtin_doc->description); } builtins.push_back(d); @@ -933,7 +939,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::ConstantDoc &constant_doc : class_doc->constants) { if (constant_doc.name == F) { - d2["documentation"] = fix_doc_description(constant_doc.description); + d2["description"] = fix_doc_description(constant_doc.description); break; } } @@ -967,7 +973,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::ConstantDoc &constant_doc : class_doc->constants) { if (constant_doc.name == G->get()) { - d3["documentation"] = fix_doc_description(constant_doc.description); + d3["description"] = fix_doc_description(constant_doc.description); break; } } @@ -981,7 +987,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { const DocData::EnumDoc *enum_doc = class_doc->enums.getptr(F); if (enum_doc) { - d2["documentation"] = fix_doc_description(enum_doc->description); + d2["description"] = fix_doc_description(enum_doc->description); } } @@ -1039,7 +1045,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::MethodDoc &method_doc : class_doc->methods) { if (method_doc.name == method_name) { - d2["documentation"] = fix_doc_description(method_doc.description); + d2["description"] = fix_doc_description(method_doc.description); break; } } @@ -1116,7 +1122,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::MethodDoc &method_doc : class_doc->methods) { if (method_doc.name == method_name) { - d2["documentation"] = fix_doc_description(method_doc.description); + d2["description"] = fix_doc_description(method_doc.description); break; } } @@ -1159,7 +1165,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::MethodDoc &signal_doc : class_doc->signals) { if (signal_doc.name == signal_name) { - d2["documentation"] = fix_doc_description(signal_doc.description); + d2["description"] = fix_doc_description(signal_doc.description); break; } } @@ -1208,7 +1214,7 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { if (p_include_docs) { for (const DocData::PropertyDoc &property_doc : class_doc->properties) { if (property_doc.name == property_name) { - d2["documentation"] = fix_doc_description(property_doc.description); + d2["description"] = fix_doc_description(property_doc.description); break; } } @@ -1223,7 +1229,8 @@ Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { } if (p_include_docs && class_doc != nullptr) { - d["documentation"] = fix_doc_description(class_doc->description); + d["brief_description"] = fix_doc_description(class_doc->brief_description); + d["description"] = fix_doc_description(class_doc->description); } classes.push_back(d); diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 7e280466a8..19ffe96a09 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -35,6 +35,7 @@ #include "core/object/method_bind.h" #include "core/os/os.h" #include "core/version.h" +#include "gdextension_manager.h" extern void gdextension_setup_interface(); extern GDExtensionInterfaceFunctionPtr gdextension_get_proc_address(const char *p_name); @@ -218,36 +219,30 @@ public: #ifdef TOOLS_ENABLED ERR_FAIL_COND_MSG(!valid, vformat("Cannot call invalid GDExtension method bind '%s'. It's probably cached - you may need to restart Godot.", name)); #endif - ERR_FAIL_COND_MSG(vararg, "Validated methods don't have ptrcall support. This is most likely an engine bug."); + ERR_FAIL_COND_MSG(vararg, "Vararg methods don't have validated call 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. + void *ret_opaque = nullptr; + if (r_ret) { + VariantInternal::initialize(r_ret, return_value_info.type); + ret_opaque = r_ret->get_type() == Variant::NIL ? r_ret : VariantInternal::get_opaque_pointer(r_ret); } ptrcall(p_object, argptrs, ret_opaque); -#endif + + if (r_ret && r_ret->get_type() == Variant::OBJECT) { + VariantInternal::update_object_id(r_ret); + } } } @@ -669,7 +664,7 @@ void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExte memnew_placement(r_path, String(self->library_path)); } -HashMap<StringName, GDExtensionInterfaceFunctionPtr> gdextension_interface_functions; +HashMap<StringName, GDExtensionInterfaceFunctionPtr> GDExtension::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."); @@ -683,12 +678,11 @@ GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(StringName p } Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol) { - library_path = p_path; - String abs_path = ProjectSettings::get_singleton()->globalize_path(p_path); #if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) // If running on the editor on Windows, we copy the library and open the copy. // This is so the original file isn't locked and can be updated by a compiler. + bool library_copied = false; if (Engine::get_singleton()->is_editor_hint()) { if (!FileAccess::exists(abs_path)) { ERR_PRINT("GDExtension library not found: " + library_path); @@ -710,6 +704,7 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb return ERR_CANT_CREATE; } FileAccess::set_hidden_attribute(copy_path, true); + library_copied = true; // Save the copied path so it can be deleted later. temp_lib_path = copy_path; @@ -719,11 +714,17 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb } #endif - Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, true); - if (err != OK) { - ERR_PRINT("GDExtension dynamic library not found: " + abs_path); - return err; + Error err = OS::get_singleton()->open_dynamic_library(abs_path, library, true, &library_path); + ERR_FAIL_COND_V_MSG(err == ERR_FILE_NOT_FOUND, err, "GDExtension dynamic library not found: " + abs_path); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't open GDExtension dynamic library: " + abs_path); + +#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) + // If we copied the file, let's change the library path to point at the original, + // because that's what we want to check to see if it's changed. + if (library_copied) { + library_path = library_path.get_base_dir() + "\\" + p_path.get_file(); } +#endif void *entry_funcptr = nullptr; @@ -743,6 +744,7 @@ Error GDExtension::open_library(const String &p_path, const String &p_entry_symb return OK; } else { ERR_PRINT("GDExtension initialization function '" + p_entry_symbol + "' returned an error."); + OS::get_singleton()->close_dynamic_library(library); return FAILED; } } @@ -781,7 +783,7 @@ void GDExtension::initialize_library(InitializationLevel p_level) { level_initialized = int32_t(p_level); - ERR_FAIL_COND(initialization.initialize == nullptr); + ERR_FAIL_NULL(initialization.initialize); initialization.initialize(initialization.userdata, GDExtensionInitializationLevel(p_level)); } @@ -840,6 +842,10 @@ void GDExtension::initialize_gdextensions() { register_interface_function("get_library_path", (GDExtensionInterfaceFunctionPtr)&GDExtension::_get_library_path); } +void GDExtension::finalize_gdextensions() { + gdextension_interface_functions.clear(); +} + Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, Ref<GDExtension> &p_extension) { ERR_FAIL_COND_V_MSG(p_extension.is_valid() && p_extension->is_library_open(), ERR_ALREADY_IN_USE, "Cannot load GDExtension resource into already opened library."); @@ -904,6 +910,8 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, return ERR_FILE_NOT_FOUND; } + bool is_static_library = library_path.ends_with(".a") || library_path.ends_with(".xcframework"); + if (!library_path.is_resource_file() && !library_path.is_absolute_path()) { library_path = p_path.get_base_dir().path_join(library_path); } @@ -915,12 +923,12 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, #ifdef TOOLS_ENABLED p_extension->set_reloadable(config->get_value("configuration", "reloadable", false) && Engine::get_singleton()->is_extension_reloading_enabled()); - p_extension->update_last_modified_time(MAX( - FileAccess::get_modified_time(library_path), - FileAccess::get_modified_time(p_path))); + p_extension->update_last_modified_time( + FileAccess::get_modified_time(p_path), + FileAccess::get_modified_time(library_path)); #endif - err = p_extension->open_library(library_path, entry_symbol); + err = p_extension->open_library(is_static_library ? String() : library_path, entry_symbol); if (err != OK) { #if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) // If the DLL fails to load, make sure that temporary DLL copies are cleaned up. @@ -928,6 +936,10 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, DirAccess::remove_absolute(p_extension->get_temp_library_path()); } #endif + + // Unreference the extension so that this loading can be considered a failure. + p_extension.unref(); + // Errors already logged in open_library() return err; } @@ -937,7 +949,12 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, List<String> keys; config->get_section_keys("icons", &keys); for (const String &key : keys) { - p_extension->class_icon_paths[key] = config->get_value("icons", key); + String icon_path = config->get_value("icons", key); + if (icon_path.is_relative_path()) { + icon_path = p_path.get_base_dir().path_join(icon_path); + } + + p_extension->class_icon_paths[key] = icon_path; } } @@ -945,6 +962,15 @@ Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_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) { + // We can't have two GDExtension resource object representing the same library, because + // loading (or unloading) a GDExtension affects global data. So, we need reuse the same + // object if one has already been loaded (even if caching is disabled at the resource + // loader level). + GDExtensionManager *manager = GDExtensionManager::get_singleton(); + if (manager->is_extension_loaded(p_path)) { + return manager->get_extension(p_path); + } + Ref<GDExtension> lib; Error err = load_gdextension_resource(p_path, lib); if (err != OK && r_error) { @@ -972,10 +998,13 @@ String GDExtensionResourceLoader::get_resource_type(const String &p_path) const #ifdef TOOLS_ENABLED bool GDExtension::has_library_changed() const { - if (FileAccess::get_modified_time(get_path()) > last_modified_time) { + // Check only that the last modified time is different (rather than checking + // that it's newer) since some OS's (namely Windows) will preserve the modified + // time by default when copying files. + if (FileAccess::get_modified_time(get_path()) != resource_last_modified_time) { return true; } - if (FileAccess::get_modified_time(library_path) > last_modified_time) { + if (FileAccess::get_modified_time(library_path) != library_last_modified_time) { return true; } return false; diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 2996100c9a..0d20b8e50c 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -90,7 +90,8 @@ class GDExtension : public Resource { int32_t level_initialized = -1; #ifdef TOOLS_ENABLED - uint64_t last_modified_time = 0; + uint64_t resource_last_modified_time = 0; + uint64_t library_last_modified_time = 0; bool is_reloading = false; Vector<GDExtensionMethodBind *> invalid_methods; Vector<ObjectID> instance_bindings; @@ -106,12 +107,16 @@ class GDExtension : public Resource { void clear_instance_bindings(); #endif + static HashMap<StringName, GDExtensionInterfaceFunctionPtr> gdextension_interface_functions; + protected: static void _bind_methods(); public: HashMap<String, String> class_icon_paths; + virtual bool editor_can_reload_from_file() override { return false; } // Reloading is handled in a special way. + 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); @@ -136,8 +141,9 @@ public: void set_reloadable(bool p_reloadable) { reloadable = p_reloadable; } bool has_library_changed() const; - void update_last_modified_time(uint64_t p_last_modified_time) { - last_modified_time = MAX(last_modified_time, p_last_modified_time); + void update_last_modified_time(uint64_t p_resource_last_modified_time, uint64_t p_library_last_modified_time) { + resource_last_modified_time = p_resource_last_modified_time; + library_last_modified_time = p_library_last_modified_time; } void track_instance_binding(Object *p_object); @@ -151,6 +157,7 @@ public: 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(); + static void finalize_gdextensions(); GDExtension(); ~GDExtension(); diff --git a/core/extension/gdextension_compat_hashes.cpp b/core/extension/gdextension_compat_hashes.cpp index 9c8d6b3e7f..dd4cd20d09 100644 --- a/core/extension/gdextension_compat_hashes.cpp +++ b/core/extension/gdextension_compat_hashes.cpp @@ -32,6 +32,7 @@ #ifndef DISABLE_DEPRECATED +#include "core/object/class_db.h" #include "core/variant/variant.h" HashMap<StringName, LocalVector<GDExtensionCompatHashes::Mapping>> GDExtensionCompatHashes::mappings; @@ -52,7 +53,7 @@ bool GDExtensionCompatHashes::lookup_current_hash(const StringName &p_class, con return false; } -bool GDExtensionCompatHashes::get_legacy_hashes(const StringName &p_class, const StringName &p_method, Array &r_hashes) { +bool GDExtensionCompatHashes::get_legacy_hashes(const StringName &p_class, const StringName &p_method, Array &r_hashes, bool p_check_valid) { LocalVector<Mapping> *methods = mappings.getptr(p_class); if (!methods) { return false; @@ -61,6 +62,13 @@ bool GDExtensionCompatHashes::get_legacy_hashes(const StringName &p_class, const bool found = false; for (const Mapping &mapping : *methods) { if (mapping.method == p_method) { + if (p_check_valid) { + MethodBind *mb = ClassDB::get_method_with_compatibility(p_class, p_method, mapping.current_hash); + if (!mb) { + WARN_PRINT(vformat("Compatibility hash %d mapped to non-existent hash %d. Please update gdextension_compat_hashes.cpp.", mapping.legacy_hash, mapping.current_hash)); + continue; + } + } r_hashes.push_back(mapping.legacy_hash); found = true; } @@ -840,4 +848,8 @@ void GDExtensionCompatHashes::initialize() { // clang-format on } +void GDExtensionCompatHashes::finalize() { + mappings.clear(); +} + #endif // DISABLE_DEPRECATED diff --git a/core/extension/gdextension_compat_hashes.h b/core/extension/gdextension_compat_hashes.h index 3a66ef0b97..813859d9e6 100644 --- a/core/extension/gdextension_compat_hashes.h +++ b/core/extension/gdextension_compat_hashes.h @@ -48,8 +48,9 @@ class GDExtensionCompatHashes { public: static void initialize(); + static void finalize(); static bool lookup_current_hash(const StringName &p_class, const StringName &p_method, uint32_t p_legacy_hash, uint32_t *r_current_hash); - static bool get_legacy_hashes(const StringName &p_class, const StringName &p_method, Array &r_hashes); + static bool get_legacy_hashes(const StringName &p_class, const StringName &p_method, Array &r_hashes, bool p_check_valid = true); }; #endif // DISABLE_DEPRECATED diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp index 2b4a37b1e0..e02e7aa701 100644 --- a/core/extension/gdextension_interface.cpp +++ b/core/extension/gdextension_interface.cpp @@ -159,9 +159,7 @@ public: userdata = p_info->callable_userdata; token = p_info->token; - if (p_info->object != nullptr) { - object = ((Object *)p_info->object)->get_instance_id(); - } + object = p_info->object_id; call_func = p_info->call_func; is_valid_func = p_info->is_valid_func; @@ -400,7 +398,7 @@ static void gdextension_variant_iter_get(GDExtensionConstVariantPtr p_self, GDEx Variant *iter = (Variant *)r_iter; bool valid; - memnew_placement(r_ret, Variant(self->iter_next(*iter, valid))); + memnew_placement(r_ret, Variant(self->iter_get(*iter, valid))); *r_valid = valid; } @@ -1152,6 +1150,11 @@ static void gdextension_object_set_instance_binding(GDExtensionObjectPtr p_objec o->set_instance_binding(p_token, p_binding, p_callbacks); } +static void gdextension_object_free_instance_binding(GDExtensionObjectPtr p_object, void *p_token) { + Object *o = (Object *)p_object; + o->free_instance_binding(p_token); +} + static void gdextension_object_set_instance(GDExtensionObjectPtr p_object, GDExtensionConstStringNamePtr p_classname, GDExtensionClassInstancePtr p_instance) { const StringName classname = *reinterpret_cast<const StringName *>(p_classname); Object *o = (Object *)p_object; @@ -1491,6 +1494,7 @@ void gdextension_setup_interface() { REGISTER_INTERFACE_FUNC(global_get_singleton); REGISTER_INTERFACE_FUNC(object_get_instance_binding); REGISTER_INTERFACE_FUNC(object_set_instance_binding); + REGISTER_INTERFACE_FUNC(object_free_instance_binding); REGISTER_INTERFACE_FUNC(object_set_instance); REGISTER_INTERFACE_FUNC(object_get_class_name); REGISTER_INTERFACE_FUNC(object_cast_to); diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index fbd1480e69..d58f0226d8 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -392,7 +392,7 @@ typedef GDExtensionBool (*GDExtensionCallableCustomLessThan)(void *callable_user typedef void (*GDExtensionCallableCustomToString)(void *callable_userdata, GDExtensionBool *r_is_valid, GDExtensionStringPtr r_out); typedef struct { - /* Only `call_func` and `token` are strictly required, however, `object` should be passed if its not a static method. + /* Only `call_func` and `token` are strictly required, however, `object_id` should be passed if its not a static method. * * `token` should point to an address that uniquely identifies the GDExtension (for example, the * `GDExtensionClassLibraryPtr` passed to the entry symbol function. @@ -409,7 +409,7 @@ typedef struct { void *callable_userdata; void *token; - GDExtensionObjectPtr object; + GDObjectInstanceID object_id; GDExtensionCallableCustomCall call_func; GDExtensionCallableCustomIsValid is_valid_func; @@ -590,7 +590,10 @@ typedef GDExtensionInterfaceFunctionPtr (*GDExtensionInterfaceGetProcAddress)(co * * For example: * - * GDExtensionInterfaceGetGodotVersion *get_godot_version = (GDExtensionInterfaceGetGodotVersion)p_get_proc_address("get_godot_version"); + * GDExtensionInterfaceGetGodotVersion get_godot_version = (GDExtensionInterfaceGetGodotVersion)p_get_proc_address("get_godot_version"); + * + * (Note that snippet may cause "cast between incompatible function types" on some compilers, you can + * silence this by adding an intermediary `void*` cast.) * * You can then call it like a normal function: * @@ -2192,6 +2195,17 @@ typedef void *(*GDExtensionInterfaceObjectGetInstanceBinding)(GDExtensionObjectP typedef void (*GDExtensionInterfaceObjectSetInstanceBinding)(GDExtensionObjectPtr p_o, void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks); /** + * @name object_free_instance_binding + * @since 4.2 + * + * Free 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. + */ +typedef void (*GDExtensionInterfaceObjectFreeInstanceBinding)(GDExtensionObjectPtr p_o, void *p_token); + +/** * @name object_set_instance * @since 4.1 * diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index 0dc84f685f..a4d032f22f 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -293,3 +293,9 @@ GDExtensionManager::GDExtensionManager() { GDExtensionCompatHashes::initialize(); #endif } + +GDExtensionManager::~GDExtensionManager() { +#ifndef DISABLE_DEPRECATED + GDExtensionCompatHashes::finalize(); +#endif +} diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h index 8cd6d5a3e2..9386e356bb 100644 --- a/core/extension/gdextension_manager.h +++ b/core/extension/gdextension_manager.h @@ -86,6 +86,7 @@ public: void reload_extensions(); GDExtensionManager(); + ~GDExtensionManager(); }; VARIANT_ENUM_CAST(GDExtensionManager::LoadStatus) diff --git a/core/input/gamecontrollerdb.txt b/core/input/gamecontrollerdb.txt index 9a43c4e35d..77655e9b6a 100644 --- a/core/input/gamecontrollerdb.txt +++ b/core/input/gamecontrollerdb.txt @@ -414,6 +414,8 @@ 03000000ad1b000023f0000000000000,MLG,a:b0,b:b1,back:b7,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a6,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Windows, 03000000ad1b00003ef0000000000000,MLG Fightstick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b8,rightshoulder:b5,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows, 03000000380700006382000000000000,MLG PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, +030000004523000015e0000000000000,Mobapad Chitu HD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, +03000000491900000904000000000000,Mobapad Chitu HD,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,misc1:b15,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, 03000000ffff00000000000000000000,Mocute M053,a:b3,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b11,leftstick:b7,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b6,righttrigger:b4,rightx:a3,righty:a4,start:b8,x:b1,y:b0,platform:Windows, 03000000d6200000e589000000000000,Moga 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows, 03000000d62000007162000000000000,Moga Pro,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,rightx:a2,righty:a5,start:b7,x:b2,y:b3,platform:Windows, @@ -582,6 +584,7 @@ 03000000921200004547000000000000,Retro Bit Sega Genesis Controller Adapter,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b7,rightshoulder:b5,righttrigger:b2,start:b6,x:b3,y:b4,platform:Windows, 03000000790000001100000000000000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows, 03000000830500006020000000000000,Retro Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b8,righttrigger:b9,start:b7,x:b2,y:b3,platform:Windows, +0300000003040000c197000000000000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows, 03000000bd12000013d0000000000000,Retrolink Sega Saturn Classic Controller,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b5,lefttrigger:b6,rightshoulder:b2,righttrigger:b7,start:b8,x:b3,y:b4,platform:Windows, 03000000bd12000015d0000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows, 03000000341200000400000000000000,RetroUSB N64 RetroPort,+rightx:b8,+righty:b10,-rightx:b9,-righty:b11,a:b7,b:b6,dpdown:b2,dpleft:b1,dpright:b0,dpup:b3,leftshoulder:b13,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b12,start:b4,platform:Windows, @@ -643,7 +646,6 @@ 030000003b07000004a1000000000000,SFX,a:b0,b:b2,back:b7,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Windows, 03000000f82100001900000000000000,Shogun Bros Chameleon X1,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows, 03000000120c00001c1e000000000000,SnakeByte 4S PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows, -0300000003040000c197000000000000,SNES Controller,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows, 0300000081170000960a000000000000,SNES Controller,a:b4,b:b0,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b5,y:b1,platform:Windows, 03000000811700009d0a000000000000,SNES Controller,a:b0,b:b4,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows, 030000008b2800000300000000000000,SNES Controller,a:b0,b:b4,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Windows, @@ -772,6 +774,7 @@ 03000000341a00000608000000000000,Xeox,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, 03000000450c00002043000000000000,Xeox SL6556BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, 030000006f0e00000300000000000000,XGear,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a5,righty:a2,start:b9,x:b3,y:b0,platform:Windows, +03000000e0ff00000201000000000000,Xiaomi Black Shark (L),back:b0,dpdown:b11,dpleft:b9,dpright:b10,dpup:b8,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,platform:Windows, 03000000172700004431000000000000,Xiaomi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows, 03000000172700003350000000000000,Xiaomi XMGP01YM,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows, 03000000bc2000005060000000000000,Xiaomi XMGP01YM,+lefty:+a2,+righty:+a5,-lefty:-a1,-righty:-a4,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,start:b11,x:b3,y:b4,platform:Windows, @@ -928,6 +931,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 030000005e0400000300000006010000,Microsoft SideWinder,a:b0,b:b1,back:b9,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b8,x:b3,y:b4,platform:Mac OS X, 030000005e0400000700000006010000,Microsoft SideWinder,a:b0,b:b1,back:b8,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,start:b9,x:b3,y:b4,platform:Mac OS X, 030000005e0400002700000001010000,Microsoft SideWinder Plug and Play,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,lefttrigger:b4,righttrigger:b5,x:b2,y:b3,platform:Mac OS X, +030000004523000015e0000072050000,Mobapad Chitu HD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, 03000000d62000007162000001000000,Moga Pro 2,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Mac OS X, 03000000c62400002a89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, 03000000c62400002b89000000010000,MOGA XP5A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X, @@ -979,6 +983,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 03000000790000001100000000000000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Mac OS X, 03000000790000001100000005010000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b6,lefttrigger:b7,rightshoulder:b5,righttrigger:b4,start:b9,x:b0,y:b3,platform:Mac OS X, 03000000830500006020000000010000,Retro Controller,a:b0,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b8,righttrigger:b9,start:b7,x:b2,y:b3,platform:Mac OS X, +0300000003040000c197000000000000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a4,dpleft:-a0,dpright:+a0,dpup:-a4,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Mac OS X, 03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X, 03000000341200000400000000000000,RetroUSB N64 RetroPort,+rightx:b8,+righty:b10,-rightx:b9,-righty:b11,a:b7,b:b6,dpdown:b2,dpleft:b1,dpright:b0,dpup:b3,leftshoulder:b13,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b12,start:b4,platform:Mac OS X, 030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X, @@ -1335,8 +1340,10 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 030000005e040000000b000007040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b12,paddle2:b14,paddle3:b13,paddle4:b15,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 030000005e040000000b000008040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b12,paddle2:b14,paddle3:b13,paddle4:b15,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 050000005e040000050b000003090000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, +050000005e0400008e02000030110000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b13,paddle3:b12,paddle4:b14,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 030000005e040000120b00000b050000,Microsoft Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux, +03000000790000001c18000010010000,Mobapad Chitu HD,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 050000004d4f435554452d3035335800,Mocute 053X,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, 05000000e80400006e0400001b010000,Mocute 053X M59,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 050000004d4f435554452d3035305800,Mocute 054X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, @@ -1490,6 +1497,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 03000000321500000b10000011010000,Razer Wolverine PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,touchpad:b13,x:b0,y:b3,platform:Linux, 03000000790000001100000010010000,Retro Controller,a:b1,b:b2,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,start:b9,x:b0,y:b3,platform:Linux, +0300000003040000c197000011010000,Retrode Adapter,a:b0,b:b4,back:b2,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux, 190000004b4800000111000000010000,RetroGame Joypad,a:b1,b:b0,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, 0300000081170000990a000001010000,Retronic Adapter,a:b0,leftx:a0,lefty:a1,platform:Linux, 0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux, @@ -1639,6 +1647,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 060000005e040000120b000007050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 060000005e040000120b00000b050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 060000005e040000120b00000f050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, +060000005e040000120b00000d050000,Xbox Series X Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,misc1:b11,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 050000005e040000200b000013050000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux, 03000000450c00002043000010010000,XEOX SL6556 BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, 05000000172700004431000029010000,XiaoMi Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux, @@ -1792,6 +1801,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2, 33323763323132376537376266393366,Microsoft Dual Strike,a:b24,b:b23,back:b25,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b27,lefttrigger:b29,rightshoulder:b78,rightx:a0,righty:a1~,start:b26,x:b22,y:b21,platform:Android, 30306461613834333439303734316539,Microsoft SideWinder Pro,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b20,lefttrigger:b9,rightshoulder:b19,righttrigger:b10,start:b17,x:b2,y:b3,platform:Android, 32386235353630393033393135613831,Microsoft Xbox Series Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, +4d4f42415041442050726f2d48440000,Mobapad Chitu HD,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, 4d4f435554452d303533582d4d35312d,Mocute 053X,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, 33343361376163623438613466616531,Mocute M053,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, 39306635663061636563316166303966,Mocute M053,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:b17,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:b18,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android, diff --git a/core/input/input.cpp b/core/input/input.cpp index 19ea8c7317..7fe850069a 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -143,9 +143,15 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_accumulated_input", "enable"), &Input::set_use_accumulated_input); ClassDB::bind_method(D_METHOD("is_using_accumulated_input"), &Input::is_using_accumulated_input); ClassDB::bind_method(D_METHOD("flush_buffered_events"), &Input::flush_buffered_events); + ClassDB::bind_method(D_METHOD("set_emulate_mouse_from_touch", "enable"), &Input::set_emulate_mouse_from_touch); + ClassDB::bind_method(D_METHOD("is_emulating_mouse_from_touch"), &Input::is_emulating_mouse_from_touch); + ClassDB::bind_method(D_METHOD("set_emulate_touch_from_mouse", "enable"), &Input::set_emulate_touch_from_mouse); + ClassDB::bind_method(D_METHOD("is_emulating_touch_from_mouse"), &Input::is_emulating_touch_from_mouse); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_mode"), "set_mouse_mode", "get_mouse_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_accumulated_input"), "set_use_accumulated_input", "is_using_accumulated_input"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emulate_mouse_from_touch"), "set_emulate_mouse_from_touch", "is_emulating_mouse_from_touch"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "emulate_touch_from_mouse"), "set_emulate_touch_from_mouse", "is_emulating_touch_from_mouse"); BIND_ENUM_CONSTANT(MOUSE_MODE_VISIBLE); BIND_ENUM_CONSTANT(MOUSE_MODE_HIDDEN); @@ -192,6 +198,7 @@ void Input::get_argument_options(const StringName &p_function, int p_idx, List<S r_options->push_back(name.quote()); } } + Object::get_argument_options(p_function, p_idx, r_options); } void Input::VelocityTrack::update(const Vector2 &p_delta_p) { @@ -241,8 +248,8 @@ bool Input::is_anything_pressed() const { return true; } - for (const KeyValue<StringName, Input::Action> &E : action_state) { - if (E.value.pressed) { + for (const KeyValue<StringName, Input::ActionState> &E : action_states) { + if (E.value.cache.pressed) { return true; } } @@ -285,12 +292,17 @@ bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const { bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); - return action_state.has(p_action) && action_state[p_action].pressed > 0 && (p_exact ? action_state[p_action].exact : true); + HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action); + if (!E) { + return false; + } + + return E->value.cache.pressed && (p_exact ? E->value.exact : true); } bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); - HashMap<StringName, Action>::ConstIterator E = action_state.find(p_action); + HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action); if (!E) { return false; } @@ -300,7 +312,7 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con } // Backward compatibility for legacy behavior, only return true if currently pressed. - bool pressed_requirement = legacy_just_pressed_behavior ? E->value.pressed : true; + bool pressed_requirement = legacy_just_pressed_behavior ? E->value.cache.pressed : true; if (Engine::get_singleton()->is_in_physics_frame()) { return pressed_requirement && E->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames(); @@ -311,7 +323,7 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); - HashMap<StringName, Action>::ConstIterator E = action_state.find(p_action); + HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action); if (!E) { return false; } @@ -321,7 +333,7 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co } // Backward compatibility for legacy behavior, only return true if currently released. - bool released_requirement = legacy_just_pressed_behavior ? !E->value.pressed : true; + bool released_requirement = legacy_just_pressed_behavior ? !E->value.cache.pressed : true; if (Engine::get_singleton()->is_in_physics_frame()) { return released_requirement && E->value.released_physics_frame == Engine::get_singleton()->get_physics_frames(); @@ -332,7 +344,7 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co float Input::get_action_strength(const StringName &p_action, bool p_exact) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action)); - HashMap<StringName, Action>::ConstIterator E = action_state.find(p_action); + HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action); if (!E) { return 0.0f; } @@ -341,12 +353,12 @@ float Input::get_action_strength(const StringName &p_action, bool p_exact) const return 0.0f; } - return E->value.strength; + return E->value.cache.strength; } float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action)); - HashMap<StringName, Action>::ConstIterator E = action_state.find(p_action); + HashMap<StringName, ActionState>::ConstIterator E = action_states.find(p_action); if (!E) { return 0.0f; } @@ -355,7 +367,7 @@ float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) c return 0.0f; } - return E->value.raw_strength; + return E->value.cache.raw_strength; } float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const { @@ -440,6 +452,18 @@ static String _hex_str(uint8_t p_byte) { void Input::joy_connection_changed(int p_idx, bool p_connected, String p_name, String p_guid, Dictionary p_joypad_info) { _THREAD_SAFE_METHOD_ + + // Clear the pressed status if a Joypad gets disconnected. + if (!p_connected) { + for (KeyValue<StringName, ActionState> &E : action_states) { + HashMap<int, ActionState::DeviceState>::Iterator it = E.value.device_states.find(p_idx); + if (it) { + E.value.device_states.remove(it); + _update_action_cache(E.key, E.value); + } + } + } + Joypad js; js.name = p_connected ? p_name : ""; js.uid = p_connected ? p_guid : ""; @@ -695,53 +719,38 @@ void Input::_parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_em } 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]; - bool is_pressed = false; - - if (!p_event->is_echo()) { - if (p_event->is_action_pressed(E.key)) { - if (jm.is_valid()) { - // If axis is already pressed, don't increase the pressed counter. - if (!action.axis_pressed) { - action.pressed++; - action.axis_pressed = true; - } - } else { - action.pressed++; - } - - is_pressed = true; - if (action.pressed == 1) { - action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); - action.pressed_process_frame = Engine::get_singleton()->get_process_frames(); - } - } else { - bool is_released = true; - if (jm.is_valid()) { - // Same as above. Don't release axis when not pressed. - if (action.axis_pressed) { - action.axis_pressed = false; - } else { - is_released = false; - } - } - - if (is_released) { - if (action.pressed == 1) { - action.released_physics_frame = Engine::get_singleton()->get_physics_frames(); - action.released_process_frame = Engine::get_singleton()->get_process_frames(); - } - action.pressed = MAX(action.pressed - 1, 0); - } - } - action.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true); - } + const int event_index = InputMap::get_singleton()->event_get_index(p_event, E.key); + if (event_index == -1) { + continue; + } + ERR_FAIL_COND_MSG(event_index >= (int)MAX_EVENT, vformat("Input singleton does not support more than %d events assigned to an action.", MAX_EVENT)); + + int device_id = p_event->get_device(); + bool is_pressed = p_event->is_action_pressed(E.key, true); + ActionState &action_state = action_states[E.key]; + + // Update the action's per-device state. + ActionState::DeviceState &device_state = action_state.device_states[device_id]; + device_state.pressed[event_index] = is_pressed; + device_state.strength[event_index] = p_event->get_action_strength(E.key); + device_state.raw_strength[event_index] = p_event->get_action_raw_strength(E.key); + + // Update the action's global state and cache. + if (!is_pressed) { + action_state.api_pressed = false; // Always release the event from action_press() method. + action_state.api_strength = 0.0; + } + action_state.exact = InputMap::get_singleton()->event_is_action(p_event, E.key, true); - if (is_pressed || action.pressed == 0) { - action.strength = p_event->get_action_strength(E.key); - action.raw_strength = p_event->get_action_raw_strength(E.key); - } + bool was_pressed = action_state.cache.pressed; + _update_action_cache(E.key, action_state); + if (action_state.cache.pressed && !was_pressed) { + action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); + } + if (!action_state.cache.pressed && was_pressed) { + action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); } } @@ -857,30 +866,30 @@ Point2i Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, con void Input::action_press(const StringName &p_action, float p_strength) { // Create or retrieve existing action. - Action &action = action_state[p_action]; + ActionState &action_state = action_states[p_action]; - action.pressed++; - if (action.pressed == 1) { - action.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); - action.pressed_process_frame = Engine::get_singleton()->get_process_frames(); + if (!action_state.cache.pressed) { + action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); } - action.strength = p_strength; - action.raw_strength = p_strength; - action.exact = true; + action_state.exact = true; + action_state.api_pressed = true; + action_state.api_strength = p_strength; + _update_action_cache(p_action, action_state); } void Input::action_release(const StringName &p_action) { // Create or retrieve existing action. - Action &action = action_state[p_action]; - - action.pressed--; - if (action.pressed == 0) { - 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 = true; + ActionState &action_state = action_states[p_action]; + action_state.cache.pressed = 0; + action_state.cache.strength = 0.0; + action_state.cache.raw_strength = 0.0; + action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames(); + action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); + action_state.device_states.clear(); + action_state.exact = true; + action_state.api_pressed = false; + action_state.api_strength = 0.0; } void Input::set_emulate_touch_from_mouse(bool p_emulate) { @@ -1038,10 +1047,8 @@ void Input::release_pressed_events() { joy_buttons_pressed.clear(); _joy_axis.clear(); - for (KeyValue<StringName, Input::Action> &E : action_state) { - if (E.value.pressed > 0) { - // Make sure the action is really released. - E.value.pressed = 1; + for (KeyValue<StringName, Input::ActionState> &E : action_states) { + if (E.value.cache.pressed) { action_release(E.key); } } @@ -1096,7 +1103,8 @@ void Input::joy_axis(int p_device, JoyAxis p_axis, float p_value) { return; } - JoyEvent map = _get_mapped_axis_event(map_db[joy.mapping], p_axis, p_value); + JoyAxisRange range; + JoyEvent map = _get_mapped_axis_event(map_db[joy.mapping], p_axis, p_value, range); if (map.type == TYPE_BUTTON) { bool pressed = map.value > 0.5; @@ -1136,7 +1144,7 @@ void Input::joy_axis(int p_device, JoyAxis p_axis, float p_value) { if (map.type == TYPE_AXIS) { JoyAxis axis = JoyAxis(map.index); float value = map.value; - if (axis == JoyAxis::TRIGGER_LEFT || axis == JoyAxis::TRIGGER_RIGHT) { + if (range == FULL_AXIS && (axis == JoyAxis::TRIGGER_LEFT || axis == JoyAxis::TRIGGER_RIGHT)) { // Convert to a value between 0.0f and 1.0f. value = 0.5f + value / 2.0f; } @@ -1207,6 +1215,29 @@ void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value) { parse_input_event(ievent); } +void Input::_update_action_cache(const StringName &p_action_name, ActionState &r_action_state) { + // Update the action cache, computed from the per-device and per-event states. + r_action_state.cache.pressed = false; + r_action_state.cache.strength = 0.0; + r_action_state.cache.raw_strength = 0.0; + + int max_event = InputMap::get_singleton()->action_get_events(p_action_name)->size(); + for (const KeyValue<int, ActionState::DeviceState> &kv : r_action_state.device_states) { + const ActionState::DeviceState &device_state = kv.value; + for (int i = 0; i < max_event; i++) { + r_action_state.cache.pressed = r_action_state.cache.pressed || device_state.pressed[i]; + r_action_state.cache.strength = MAX(r_action_state.cache.strength, device_state.strength[i]); + r_action_state.cache.raw_strength = MAX(r_action_state.cache.raw_strength, device_state.raw_strength[i]); + } + } + + if (r_action_state.api_pressed) { + r_action_state.cache.pressed = true; + r_action_state.cache.strength = MAX(r_action_state.cache.strength, r_action_state.api_strength); + r_action_state.cache.raw_strength = MAX(r_action_state.cache.raw_strength, r_action_state.api_strength); // Use the strength as raw_strength for API-pressed states. + } +} + Input::JoyEvent Input::_get_mapped_button_event(const JoyDeviceMapping &mapping, JoyButton p_button) { JoyEvent event; @@ -1242,7 +1273,7 @@ Input::JoyEvent Input::_get_mapped_button_event(const JoyDeviceMapping &mapping, return event; } -Input::JoyEvent Input::_get_mapped_axis_event(const JoyDeviceMapping &mapping, JoyAxis p_axis, float p_value) { +Input::JoyEvent Input::_get_mapped_axis_event(const JoyDeviceMapping &mapping, JoyAxis p_axis, float p_value, JoyAxisRange &r_range) { JoyEvent event; for (int i = 0; i < mapping.bindings.size(); i++) { @@ -1288,6 +1319,7 @@ Input::JoyEvent Input::_get_mapped_axis_event(const JoyDeviceMapping &mapping, J case TYPE_AXIS: event.index = (int)binding.output.axis.axis; event.value = value; + r_range = binding.output.axis.range; if (binding.output.axis.range != binding.input.axis.range) { switch (binding.output.axis.range) { case POSITIVE_HALF_AXIS: diff --git a/core/input/input.h b/core/input/input.h index 8ce5f64a6a..b98406e884 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -44,6 +44,8 @@ class Input : public Object { static Input *singleton; + static constexpr uint64_t MAX_EVENT = 32; + public: enum MouseMode { MOUSE_MODE_VISIBLE, @@ -98,19 +100,31 @@ private: int64_t mouse_window = 0; bool legacy_just_pressed_behavior = false; - struct Action { + struct ActionState { 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; - int pressed = 0; - bool axis_pressed = false; bool exact = true; - float strength = 0.0f; - float raw_strength = 0.0f; + + struct DeviceState { + bool pressed[MAX_EVENT] = { false }; + float strength[MAX_EVENT] = { 0.0 }; + float raw_strength[MAX_EVENT] = { 0.0 }; + }; + bool api_pressed = false; + float api_strength = 0.0; + HashMap<int, DeviceState> device_states; + + // Cache. + struct ActionStateCache { + bool pressed = false; + float strength = false; + float raw_strength = false; + } cache; }; - HashMap<StringName, Action> action_state; + HashMap<StringName, ActionState> action_states; bool emulate_touch_from_mouse = false; bool emulate_mouse_from_touch = false; @@ -221,12 +235,13 @@ private: Vector<JoyDeviceMapping> map_db; JoyEvent _get_mapped_button_event(const JoyDeviceMapping &mapping, JoyButton p_button); - JoyEvent _get_mapped_axis_event(const JoyDeviceMapping &mapping, JoyAxis p_axis, float p_value); + JoyEvent _get_mapped_axis_event(const JoyDeviceMapping &mapping, JoyAxis p_axis, float p_value, JoyAxisRange &r_range); void _get_mapped_hat_events(const JoyDeviceMapping &mapping, HatDir p_hat, JoyEvent r_events[(size_t)HatDir::MAX]); JoyButton _get_output_button(String output); JoyAxis _get_output_axis(String output); void _button_event(int p_device, JoyButton p_index, bool p_pressed); void _axis_event(int p_device, JoyAxis p_axis, float p_value); + void _update_action_cache(const StringName &p_action_name, ActionState &r_action_state); void _parse_input_event_impl(const Ref<InputEvent> &p_event, bool p_is_emulated); diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index ddfde0e7cd..78b9ada884 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -127,16 +127,21 @@ List<StringName> InputMap::get_actions() const { return actions; } -List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength) const { +List<Ref<InputEvent>>::Element *InputMap::_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength, int *r_event_index) const { ERR_FAIL_COND_V(!p_event.is_valid(), nullptr); + int i = 0; for (List<Ref<InputEvent>>::Element *E = p_action.inputs.front(); E; E = E->next()) { int device = E->get()->get_device(); if (device == ALL_DEVICES || device == p_event->get_device()) { if (E->get()->action_match(p_event, p_exact_match, p_action.deadzone, r_pressed, r_strength, r_raw_strength)) { + if (r_event_index) { + *r_event_index = i; + } return E; } } + i++; } return nullptr; @@ -179,6 +184,7 @@ void InputMap::action_erase_event(const StringName &p_action, const Ref<InputEve List<Ref<InputEvent>>::Element *E = _find_event(input_map[p_action], p_event, true); if (E) { input_map[p_action].inputs.erase(E); + if (Input::get_singleton()->is_action_pressed(p_action)) { Input::get_singleton()->action_release(p_action); } @@ -216,7 +222,13 @@ bool InputMap::event_is_action(const Ref<InputEvent> &p_event, const StringName return event_get_action_status(p_event, p_action, p_exact_match); } -bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength) const { +int InputMap::event_get_index(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match) const { + int index = -1; + event_get_action_status(p_event, p_action, p_exact_match, nullptr, nullptr, nullptr, &index); + return index; +} + +bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match, bool *r_pressed, float *r_strength, float *r_raw_strength, int *r_event_index) const { HashMap<StringName, Action>::Iterator E = input_map.find(p_action); ERR_FAIL_COND_V_MSG(!E, false, suggest_actions(p_action)); @@ -236,7 +248,7 @@ bool InputMap::event_get_action_status(const Ref<InputEvent> &p_event, const Str return input_event_action->get_action() == p_action; } - List<Ref<InputEvent>>::Element *event = _find_event(E->value, p_event, p_exact_match, r_pressed, r_strength, r_raw_strength); + List<Ref<InputEvent>>::Element *event = _find_event(E->value, p_event, p_exact_match, r_pressed, r_strength, r_raw_strength, r_event_index); return event != nullptr; } diff --git a/core/input/input_map.h b/core/input/input_map.h index b4d5beacb3..6407ea489e 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -61,7 +61,7 @@ private: HashMap<String, List<Ref<InputEvent>>> default_builtin_cache; HashMap<String, List<Ref<InputEvent>>> default_builtin_with_overrides_cache; - List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr) const; + List<Ref<InputEvent>>::Element *_find_event(Action &p_action, const Ref<InputEvent> &p_event, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr, int *r_event_index = nullptr) const; TypedArray<InputEvent> _action_get_events(const StringName &p_action); TypedArray<StringName> _get_actions(); @@ -86,7 +86,8 @@ public: const List<Ref<InputEvent>> *action_get_events(const StringName &p_action); bool event_is_action(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false) const; - bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr) const; + int event_get_index(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false) const; + bool event_get_action_status(const Ref<InputEvent> &p_event, const StringName &p_action, bool p_exact_match = false, bool *r_pressed = nullptr, float *r_strength = nullptr, float *r_raw_strength = nullptr, int *r_event_index = nullptr) const; const HashMap<StringName, Action> &get_action_map() const; void load_from_project_settings(); diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 22b63a0929..40c1a53958 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -546,6 +546,10 @@ bool DirAccess::get_include_hidden() const { return include_hidden; } +bool DirAccess::is_case_sensitive(const String &p_path) const { + return true; +} + void DirAccess::_bind_methods() { ClassDB::bind_static_method("DirAccess", D_METHOD("open", "path"), &DirAccess::_open); ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error); @@ -583,6 +587,8 @@ void DirAccess::_bind_methods() { ClassDB::bind_method(D_METHOD("set_include_hidden", "enable"), &DirAccess::set_include_hidden); ClassDB::bind_method(D_METHOD("get_include_hidden"), &DirAccess::get_include_hidden); + ClassDB::bind_method(D_METHOD("is_case_sensitive", "path"), &DirAccess::is_case_sensitive); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_navigational"), "set_include_navigational", "get_include_navigational"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_hidden"), "set_include_hidden", "get_include_hidden"); } diff --git a/core/io/dir_access.h b/core/io/dir_access.h index 52ed688deb..4ee69571f2 100644 --- a/core/io/dir_access.h +++ b/core/io/dir_access.h @@ -159,6 +159,8 @@ public: void set_include_hidden(bool p_enable); bool get_include_hidden() const; + virtual bool is_case_sensitive(const String &p_path) const; + DirAccess() {} virtual ~DirAccess() {} }; diff --git a/core/io/file_access.h b/core/io/file_access.h index 7b9e66bb83..7d346ca2f4 100644 --- a/core/io/file_access.h +++ b/core/io/file_access.h @@ -223,8 +223,8 @@ public: static Vector<uint8_t> get_file_as_bytes(const String &p_path, Error *r_error = nullptr); static String get_file_as_string(const String &p_path, Error *r_error = nullptr); - static PackedByteArray _get_file_as_bytes(const String &p_path) { return get_file_as_bytes(p_path); } - static String _get_file_as_string(const String &p_path) { return get_file_as_string(p_path); }; + static PackedByteArray _get_file_as_bytes(const String &p_path) { return get_file_as_bytes(p_path, &last_file_open_error); } + static String _get_file_as_string(const String &p_path) { return get_file_as_string(p_path, &last_file_open_error); } template <class T> static void make_default(AccessType p_access) { diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 74c5c1c191..265d9ef56c 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -327,7 +327,7 @@ uint64_t FileAccessPack::get_buffer(uint8_t *p_dst, uint64_t p_length) const { to_read = (int64_t)pf.size - (int64_t)pos; } - pos += p_length; + pos += to_read; if (to_read <= 0) { return 0; diff --git a/core/io/file_access_zip.cpp b/core/io/file_access_zip.cpp index d085c29728..dd45332412 100644 --- a/core/io/file_access_zip.cpp +++ b/core/io/file_access_zip.cpp @@ -110,7 +110,7 @@ static void godot_free(voidpf opaque, voidpf address) { } // extern "C" void ZipArchive::close_handle(unzFile p_file) const { - ERR_FAIL_COND_MSG(!p_file, "Cannot close a file if none is open."); + ERR_FAIL_NULL_MSG(p_file, "Cannot close a file if none is open."); unzCloseCurrentFile(p_file); unzClose(p_file); } @@ -136,7 +136,7 @@ unzFile ZipArchive::get_file_handle(String p_file) const { io.free_mem = godot_free; unzFile pkg = unzOpen2(packages[file.package].filename.utf8().get_data(), &io); - ERR_FAIL_COND_V_MSG(!pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'."); + ERR_FAIL_NULL_V_MSG(pkg, nullptr, "Cannot open file '" + packages[file.package].filename + "'."); int unz_err = unzGoToFilePos(pkg, &file.file_pos); if (unz_err != UNZ_OK || unzOpenCurrentFile(pkg) != UNZ_OK) { unzClose(pkg); @@ -168,7 +168,7 @@ bool ZipArchive::try_open_pack(const String &p_path, bool p_replace_files, uint6 io.zerror_file = godot_testerror; unzFile zfile = unzOpen2(p_path.utf8().get_data(), &io); - ERR_FAIL_COND_V(!zfile, false); + ERR_FAIL_NULL_V(zfile, false); unz_global_info64 gi; int err = unzGetGlobalInfo64(zfile, &gi); @@ -241,7 +241,7 @@ Error FileAccessZip::open_internal(const String &p_path, int p_mode_flags) { ZipArchive *arch = ZipArchive::get_singleton(); ERR_FAIL_NULL_V(arch, FAILED); zfile = arch->get_file_handle(p_path); - ERR_FAIL_COND_V(!zfile, FAILED); + ERR_FAIL_NULL_V(zfile, FAILED); int err = unzGetCurrentFileInfo64(zfile, &file_info, nullptr, 0, nullptr, 0, nullptr, 0); ERR_FAIL_COND_V(err != UNZ_OK, FAILED); @@ -265,28 +265,28 @@ bool FileAccessZip::is_open() const { } void FileAccessZip::seek(uint64_t p_position) { - ERR_FAIL_COND(!zfile); + ERR_FAIL_NULL(zfile); unzSeekCurrentFile(zfile, p_position); } void FileAccessZip::seek_end(int64_t p_position) { - ERR_FAIL_COND(!zfile); + ERR_FAIL_NULL(zfile); unzSeekCurrentFile(zfile, get_length() + p_position); } uint64_t FileAccessZip::get_position() const { - ERR_FAIL_COND_V(!zfile, 0); + ERR_FAIL_NULL_V(zfile, 0); return unztell(zfile); } uint64_t FileAccessZip::get_length() const { - ERR_FAIL_COND_V(!zfile, 0); + ERR_FAIL_NULL_V(zfile, 0); return file_info.uncompressed_size; } bool FileAccessZip::eof_reached() const { - ERR_FAIL_COND_V(!zfile, true); + ERR_FAIL_NULL_V(zfile, true); return at_eof; } @@ -299,7 +299,7 @@ uint8_t FileAccessZip::get_8() const { uint64_t FileAccessZip::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); - ERR_FAIL_COND_V(!zfile, -1); + ERR_FAIL_NULL_V(zfile, -1); at_eof = unzeof(zfile); if (at_eof) { diff --git a/core/io/image.cpp b/core/io/image.cpp index 15d0182dfc..c72064e4f7 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -509,6 +509,7 @@ static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p } void Image::convert(Format p_new_format) { + ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, "The Image format specified (" + itos(p_new_format) + ") is out of range. See Image's Format enum."); if (data.size() == 0) { return; } @@ -3773,7 +3774,7 @@ void Image::fix_alpha_edges() { } int closest_dist = max_dist; - uint8_t closest_color[3]; + uint8_t closest_color[3] = { 0 }; int from_x = MAX(0, j - max_radius); int to_x = MIN(width - 1, j + max_radius); diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp index bfef9ebeaf..bc2493d360 100644 --- a/core/io/marshalls.cpp +++ b/core/io/marshalls.cpp @@ -638,6 +638,8 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int if (str.is_empty()) { r_variant = (Object *)nullptr; } else { + ERR_FAIL_COND_V(!ClassDB::can_instantiate(str), ERR_INVALID_DATA); + Object *obj = ClassDB::instantiate(str); ERR_FAIL_NULL_V(obj, ERR_UNAVAILABLE); @@ -1155,10 +1157,12 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo #ifdef REAL_T_IS_DOUBLE case Variant::VECTOR2: case Variant::VECTOR3: + case Variant::VECTOR4: case Variant::PACKED_VECTOR2_ARRAY: case Variant::PACKED_VECTOR3_ARRAY: case Variant::TRANSFORM2D: case Variant::TRANSFORM3D: + case Variant::PROJECTION: case Variant::QUATERNION: case Variant::PLANE: case Variant::BASIS: @@ -1490,6 +1494,8 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo r_len += 4; } else { + ERR_FAIL_COND_V(!ClassDB::can_instantiate(obj->get_class()), ERR_INVALID_PARAMETER); + _encode_string(obj->get_class(), buf, r_len); List<PropertyInfo> props; @@ -1619,8 +1625,10 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo encode_uint32(datalen, buf); buf += 4; const uint8_t *r = data.ptr(); - memcpy(buf, &r[0], datalen * datasize); - buf += datalen * datasize; + if (r) { + memcpy(buf, &r[0], datalen * datasize); + buf += datalen * datasize; + } } r_len += 4 + datalen * datasize; diff --git a/core/io/resource.cpp b/core/io/resource.cpp index e0d42a274a..04ecabaf27 100644 --- a/core/io/resource.cpp +++ b/core/io/resource.cpp @@ -90,6 +90,10 @@ String Resource::get_path() const { return path_cache; } +void Resource::set_path_cache(const String &p_path) { + path_cache = p_path; +} + String Resource::generate_scene_unique_id() { // Generate a unique enough hash, but still user-readable. // If it's not unique it does not matter because the saver will try again. @@ -485,12 +489,14 @@ RWLock ResourceCache::path_cache_lock; #endif void ResourceCache::clear() { - if (resources.size()) { - ERR_PRINT("Resources still in use at exit (run with --verbose for details)."); + if (!resources.is_empty()) { if (OS::get_singleton()->is_stdout_verbose()) { + ERR_PRINT(vformat("%d resources still in use at exit.", resources.size())); for (const KeyValue<String, Resource *> &E : resources) { print_line(vformat("Resource still in use: %s (%s)", E.key, E.value->get_class())); } + } else { + ERR_PRINT(vformat("%d resources still in use at exit (run with --verbose for details).", resources.size())); } } diff --git a/core/io/resource.h b/core/io/resource.h index a9b1a88f6b..610c2150db 100644 --- a/core/io/resource.h +++ b/core/io/resource.h @@ -103,6 +103,7 @@ public: virtual void set_path(const String &p_path, bool p_take_over = false); String get_path() const; + void set_path_cache(const String &p_path); // Set raw path without involving resource cache. _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); } static String generate_scene_unique_id(); diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index ea97e5ecce..20c494516b 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -774,6 +774,8 @@ Error ResourceLoaderBinary::load() { res = Ref<Resource>(r); if (!path.is_empty() && cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { r->set_path(path, cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); //if got here because the resource with same path has different type, replace it + } else if (!path.is_resource_file()) { + r->set_path_cache(path); } r->set_scene_unique_id(id); } @@ -1452,8 +1454,10 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons fw.unref(); Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES); - da->remove(p_path); - da->rename(p_path + ".depren", p_path); + if (da->exists(p_path + ".depren")) { + da->remove(p_path); + da->rename(p_path + ".depren", p_path); + } return OK; } diff --git a/core/io/resource_importer.h b/core/io/resource_importer.h index 0089544caa..e17644058a 100644 --- a/core/io/resource_importer.h +++ b/core/io/resource_importer.h @@ -136,6 +136,7 @@ public: virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset = 0) const = 0; virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const = 0; + virtual void handle_compatibility_options(HashMap<StringName, Variant> &p_import_params) const {} virtual String get_option_group_file() const { return String(); } virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) = 0; diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index f7915261af..0c7764392a 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -340,7 +340,9 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { if (load_task.resource.is_valid()) { if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { - load_task.resource->set_path(load_task.local_path); + load_task.resource->set_path(load_task.local_path, load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE); + } else if (!load_task.local_path.is_resource_file()) { + load_task.resource->set_path_cache(load_task.local_path); } if (load_task.xl_remapped) { @@ -359,6 +361,17 @@ void ResourceLoader::_thread_load_function(void *p_userdata) { if (_loaded_callback) { _loaded_callback(load_task.resource, load_task.local_path); } + } else if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) { + Ref<Resource> existing = ResourceCache::get_ref(load_task.local_path); + if (existing.is_valid()) { + load_task.resource = existing; + load_task.status = THREAD_LOAD_LOADED; + load_task.progress = 1.0; + + if (_loaded_callback) { + _loaded_callback(load_task.resource, load_task.local_path); + } + } } thread_load_mutex.unlock(); @@ -461,7 +474,7 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_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) { + if (p_cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE) { Ref<Resource> existing = ResourceCache::get_ref(local_path); if (existing.is_valid()) { //referencing is fine @@ -918,7 +931,7 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem } // Fallback to p_path if new_path does not exist. - if (!FileAccess::exists(new_path)) { + if (!FileAccess::exists(new_path + ".import") && !FileAccess::exists(new_path)) { WARN_PRINT(vformat("Translation remap '%s' does not exist. Falling back to '%s'.", new_path, p_path)); new_path = p_path; } @@ -1053,8 +1066,9 @@ void ResourceLoader::clear_thread_load_tasks() { thread_load_mutex.lock(); } - for (KeyValue<String, LoadToken *> &E : user_load_tokens) { - memdelete(E.value); + while (user_load_tokens.begin()) { + // User load tokens remove themselves from the map on destruction. + memdelete(user_load_tokens.begin()->value); } user_load_tokens.clear(); @@ -1110,11 +1124,10 @@ bool ResourceLoader::add_custom_resource_format_loader(String script_path) { Ref<Script> s = res; StringName ibt = s->get_instance_base_type(); bool valid_type = ClassDB::is_parent_class(ibt, "ResourceFormatLoader"); - ERR_FAIL_COND_V_MSG(!valid_type, false, "Script does not inherit a CustomResourceLoader: " + script_path + "."); + ERR_FAIL_COND_V_MSG(!valid_type, false, vformat("Failed to add a custom resource loader, script '%s' does not inherit 'ResourceFormatLoader'.", script_path)); Object *obj = ClassDB::instantiate(ibt); - - ERR_FAIL_NULL_V_MSG(obj, false, "Cannot instance script as custom resource loader, expected 'ResourceFormatLoader' inheritance, got: " + String(ibt) + "."); + ERR_FAIL_NULL_V_MSG(obj, false, vformat("Failed to add a custom resource loader, cannot instantiate '%s'.", ibt)); Ref<ResourceFormatLoader> crl = Object::cast_to<ResourceFormatLoader>(obj); crl->set_script(s); diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp index 564a54b913..1c6c18b015 100644 --- a/core/io/resource_saver.cpp +++ b/core/io/resource_saver.cpp @@ -237,11 +237,10 @@ bool ResourceSaver::add_custom_resource_format_saver(String script_path) { Ref<Script> s = res; StringName ibt = s->get_instance_base_type(); bool valid_type = ClassDB::is_parent_class(ibt, "ResourceFormatSaver"); - ERR_FAIL_COND_V_MSG(!valid_type, false, "Script does not inherit a CustomResourceSaver: " + script_path + "."); + ERR_FAIL_COND_V_MSG(!valid_type, false, vformat("Failed to add a custom resource saver, script '%s' does not inherit 'ResourceFormatSaver'.", script_path)); Object *obj = ClassDB::instantiate(ibt); - - ERR_FAIL_NULL_V_MSG(obj, false, "Cannot instance script as custom resource saver, expected 'ResourceFormatSaver' inheritance, got: " + String(ibt) + "."); + ERR_FAIL_NULL_V_MSG(obj, false, vformat("Failed to add a custom resource saver, cannot instantiate '%s'.", ibt)); Ref<ResourceFormatSaver> crl = Object::cast_to<ResourceFormatSaver>(obj); crl->set_script(s); diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp index 216fc24fff..edff3e1f14 100644 --- a/core/io/resource_uid.cpp +++ b/core/io/resource_uid.cpp @@ -35,6 +35,8 @@ #include "core/io/dir_access.h" #include "core/io/file_access.h" +// These constants are off by 1, causing the 'z' and '9' characters never to be used. +// This cannot be fixed without breaking compatibility; see GH-83843. static constexpr uint32_t char_count = ('z' - 'a'); static constexpr uint32_t base = char_count + ('9' - '0'); diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index f0f160940d..fb54058bd9 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -69,7 +69,7 @@ void AStar3D::add_point(int64_t p_id, const Vector3 &p_pos, real_t p_weight_scal } Vector3 AStar3D::get_point_position(int64_t p_id) const { - Point *p; + Point *p = nullptr; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_V_MSG(!p_exists, Vector3(), vformat("Can't get point's position. Point with id: %d doesn't exist.", p_id)); @@ -77,7 +77,7 @@ Vector3 AStar3D::get_point_position(int64_t p_id) const { } void AStar3D::set_point_position(int64_t p_id, const Vector3 &p_pos) { - Point *p; + Point *p = nullptr; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_MSG(!p_exists, vformat("Can't set point's position. Point with id: %d doesn't exist.", p_id)); @@ -85,7 +85,7 @@ void AStar3D::set_point_position(int64_t p_id, const Vector3 &p_pos) { } real_t AStar3D::get_point_weight_scale(int64_t p_id) const { - Point *p; + Point *p = nullptr; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_V_MSG(!p_exists, 0, vformat("Can't get point's weight scale. Point with id: %d doesn't exist.", p_id)); @@ -93,7 +93,7 @@ real_t AStar3D::get_point_weight_scale(int64_t p_id) const { } void AStar3D::set_point_weight_scale(int64_t p_id, real_t p_weight_scale) { - Point *p; + Point *p = nullptr; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_MSG(!p_exists, vformat("Can't set point's weight scale. Point with id: %d doesn't exist.", p_id)); 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)); @@ -102,7 +102,7 @@ void AStar3D::set_point_weight_scale(int64_t p_id, real_t p_weight_scale) { } void AStar3D::remove_point(int64_t p_id) { - Point *p; + Point *p = nullptr; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_MSG(!p_exists, vformat("Can't remove point. Point with id: %d doesn't exist.", p_id)); @@ -130,11 +130,11 @@ void AStar3D::remove_point(int64_t p_id) { void AStar3D::connect_points(int64_t p_id, int64_t p_with_id, bool bidirectional) { ERR_FAIL_COND_MSG(p_id == p_with_id, vformat("Can't connect point with id: %d to itself.", p_id)); - Point *a; + Point *a = nullptr; bool from_exists = points.lookup(p_id, a); ERR_FAIL_COND_MSG(!from_exists, vformat("Can't connect points. Point with id: %d doesn't exist.", p_id)); - Point *b; + Point *b = nullptr; bool to_exists = points.lookup(p_with_id, b); ERR_FAIL_COND_MSG(!to_exists, vformat("Can't connect points. Point with id: %d doesn't exist.", p_with_id)); @@ -166,11 +166,11 @@ void AStar3D::connect_points(int64_t p_id, int64_t p_with_id, bool bidirectional } void AStar3D::disconnect_points(int64_t p_id, int64_t p_with_id, bool bidirectional) { - Point *a; + Point *a = nullptr; bool a_exists = points.lookup(p_id, a); ERR_FAIL_COND_MSG(!a_exists, vformat("Can't disconnect points. Point with id: %d doesn't exist.", p_id)); - Point *b; + Point *b = nullptr; bool b_exists = points.lookup(p_with_id, b); ERR_FAIL_COND_MSG(!b_exists, vformat("Can't disconnect points. Point with id: %d doesn't exist.", p_with_id)); @@ -220,7 +220,7 @@ PackedInt64Array AStar3D::get_point_ids() { } Vector<int64_t> AStar3D::get_point_connections(int64_t p_id) { - Point *p; + Point *p = nullptr; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_V_MSG(!p_exists, Vector<int64_t>(), vformat("Can't get point's connections. Point with id: %d doesn't exist.", p_id)); @@ -386,11 +386,11 @@ real_t AStar3D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) { return scost; } - Point *from_point; + Point *from_point = nullptr; bool from_exists = points.lookup(p_from_id, from_point); ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id)); - Point *to_point; + Point *to_point = nullptr; bool to_exists = points.lookup(p_to_id, to_point); ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id)); @@ -403,11 +403,11 @@ real_t AStar3D::_compute_cost(int64_t p_from_id, int64_t p_to_id) { return scost; } - Point *from_point; + Point *from_point = nullptr; bool from_exists = points.lookup(p_from_id, from_point); ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_from_id)); - Point *to_point; + Point *to_point = nullptr; bool to_exists = points.lookup(p_to_id, to_point); ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_to_id)); @@ -415,11 +415,11 @@ real_t AStar3D::_compute_cost(int64_t p_from_id, int64_t p_to_id) { } Vector<Vector3> AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id) { - Point *a; + Point *a = nullptr; bool from_exists = points.lookup(p_from_id, a); ERR_FAIL_COND_V_MSG(!from_exists, Vector<Vector3>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_from_id)); - Point *b; + Point *b = nullptr; bool to_exists = points.lookup(p_to_id, b); ERR_FAIL_COND_V_MSG(!to_exists, Vector<Vector3>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_to_id)); @@ -464,11 +464,11 @@ Vector<Vector3> AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id) { } Vector<int64_t> AStar3D::get_id_path(int64_t p_from_id, int64_t p_to_id) { - Point *a; + Point *a = nullptr; bool from_exists = points.lookup(p_from_id, a); ERR_FAIL_COND_V_MSG(!from_exists, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_from_id)); - Point *b; + Point *b = nullptr; bool to_exists = points.lookup(p_to_id, b); ERR_FAIL_COND_V_MSG(!to_exists, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_to_id)); @@ -513,7 +513,7 @@ Vector<int64_t> AStar3D::get_id_path(int64_t p_from_id, int64_t p_to_id) { } void AStar3D::set_point_disabled(int64_t p_id, bool p_disabled) { - Point *p; + Point *p = nullptr; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_MSG(!p_exists, vformat("Can't set if point is disabled. Point with id: %d doesn't exist.", p_id)); @@ -521,7 +521,7 @@ void AStar3D::set_point_disabled(int64_t p_id, bool p_disabled) { } bool AStar3D::is_point_disabled(int64_t p_id) const { - Point *p; + Point *p = nullptr; bool p_exists = points.lookup(p_id, p); ERR_FAIL_COND_V_MSG(!p_exists, false, vformat("Can't get if point is disabled. Point with id: %d doesn't exist.", p_id)); @@ -660,11 +660,11 @@ real_t AStar2D::_estimate_cost(int64_t p_from_id, int64_t p_to_id) { return scost; } - AStar3D::Point *from_point; + AStar3D::Point *from_point = nullptr; bool from_exists = astar.points.lookup(p_from_id, from_point); ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_from_id)); - AStar3D::Point *to_point; + AStar3D::Point *to_point = nullptr; bool to_exists = astar.points.lookup(p_to_id, to_point); ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't estimate cost. Point with id: %d doesn't exist.", p_to_id)); @@ -677,11 +677,11 @@ real_t AStar2D::_compute_cost(int64_t p_from_id, int64_t p_to_id) { return scost; } - AStar3D::Point *from_point; + AStar3D::Point *from_point = nullptr; bool from_exists = astar.points.lookup(p_from_id, from_point); ERR_FAIL_COND_V_MSG(!from_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_from_id)); - AStar3D::Point *to_point; + AStar3D::Point *to_point = nullptr; bool to_exists = astar.points.lookup(p_to_id, to_point); ERR_FAIL_COND_V_MSG(!to_exists, 0, vformat("Can't compute cost. Point with id: %d doesn't exist.", p_to_id)); @@ -689,11 +689,11 @@ real_t AStar2D::_compute_cost(int64_t p_from_id, int64_t p_to_id) { } Vector<Vector2> AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id) { - AStar3D::Point *a; + AStar3D::Point *a = nullptr; bool from_exists = astar.points.lookup(p_from_id, a); ERR_FAIL_COND_V_MSG(!from_exists, Vector<Vector2>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_from_id)); - AStar3D::Point *b; + AStar3D::Point *b = nullptr; bool to_exists = astar.points.lookup(p_to_id, b); ERR_FAIL_COND_V_MSG(!to_exists, Vector<Vector2>(), vformat("Can't get point path. Point with id: %d doesn't exist.", p_to_id)); @@ -737,11 +737,11 @@ Vector<Vector2> AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id) { } Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id) { - AStar3D::Point *a; + AStar3D::Point *a = nullptr; bool from_exists = astar.points.lookup(p_from_id, a); ERR_FAIL_COND_V_MSG(!from_exists, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_from_id)); - AStar3D::Point *b; + AStar3D::Point *b = nullptr; bool to_exists = astar.points.lookup(p_to_id, b); ERR_FAIL_COND_V_MSG(!to_exists, Vector<int64_t>(), vformat("Can't get id path. Point with id: %d doesn't exist.", p_to_id)); diff --git a/core/math/aabb.cpp b/core/math/aabb.cpp index 1071df0979..e1d49dacc0 100644 --- a/core/math/aabb.cpp +++ b/core/math/aabb.cpp @@ -117,6 +117,11 @@ AABB AABB::intersection(const AABB &p_aabb) const { return AABB(min, max - min); } +#ifdef MINGW_ENABLED +#undef near +#undef far +#endif + bool AABB::intersects_ray(const Vector3 &p_from, const Vector3 &p_dir, Vector3 *r_clip, Vector3 *r_normal) const { #ifdef MATH_CHECKS if (unlikely(size.x < 0 || size.y < 0 || size.z < 0)) { diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 9796ac59c2..cd8c87b158 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -89,13 +89,26 @@ Basis Basis::orthogonalized() const { return c; } +// Returns true if the basis vectors are orthogonal (perpendicular), so it has no skew or shear, and can be decomposed into rotation and scale. +// See https://en.wikipedia.org/wiki/Orthogonal_basis bool Basis::is_orthogonal() const { - Basis identity; - Basis m = (*this) * transposed(); + const Vector3 x = get_column(0); + const Vector3 y = get_column(1); + const Vector3 z = get_column(2); + return Math::is_zero_approx(x.dot(y)) && Math::is_zero_approx(x.dot(z)) && Math::is_zero_approx(y.dot(z)); +} - return m.is_equal_approx(identity); +// Returns true if the basis vectors are orthonormal (orthogonal and normalized), so it has no scale, skew, or shear. +// See https://en.wikipedia.org/wiki/Orthonormal_basis +bool Basis::is_orthonormal() const { + const Vector3 x = get_column(0); + const Vector3 y = get_column(1); + const Vector3 z = get_column(2); + return Math::is_equal_approx(x.length_squared(), 1) && Math::is_equal_approx(y.length_squared(), 1) && Math::is_equal_approx(z.length_squared(), 1) && Math::is_zero_approx(x.dot(y)) && Math::is_zero_approx(x.dot(z)) && Math::is_zero_approx(y.dot(z)); } +// Returns true if the basis is conformal (orthogonal, uniform scale, preserves angles and distance ratios). +// See https://en.wikipedia.org/wiki/Conformal_linear_transformation bool Basis::is_conformal() const { const Vector3 x = get_column(0); const Vector3 y = get_column(1); @@ -104,6 +117,7 @@ bool Basis::is_conformal() const { return Math::is_equal_approx(x_len_sq, y.length_squared()) && Math::is_equal_approx(x_len_sq, z.length_squared()) && Math::is_zero_approx(x.dot(y)) && Math::is_zero_approx(x.dot(z)) && Math::is_zero_approx(y.dot(z)); } +// Returns true if the basis only has diagonal elements, so it may only have scale or flip, but no rotation, skew, or shear. bool Basis::is_diagonal() const { return ( Math::is_zero_approx(rows[0][1]) && Math::is_zero_approx(rows[0][2]) && @@ -111,8 +125,9 @@ bool Basis::is_diagonal() const { Math::is_zero_approx(rows[2][0]) && Math::is_zero_approx(rows[2][1])); } +// Returns true if the basis is a pure rotation matrix, so it has no scale, skew, shear, or flip. bool Basis::is_rotation() const { - return Math::is_equal_approx(determinant(), 1, (real_t)UNIT_EPSILON) && is_orthogonal(); + return is_conformal() && Math::is_equal_approx(determinant(), 1, (real_t)UNIT_EPSILON); } #ifdef MATH_CHECKS diff --git a/core/math/basis.h b/core/math/basis.h index adacd1c216..b4d971464e 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -138,6 +138,7 @@ struct _NO_DISCARD_ Basis { _FORCE_INLINE_ Basis operator*(const real_t p_val) const; bool is_orthogonal() const; + bool is_orthonormal() const; bool is_conformal() const; bool is_diagonal() const; bool is_rotation() const; diff --git a/core/math/convex_hull.cpp b/core/math/convex_hull.cpp index f8456ec998..68d995fe67 100644 --- a/core/math/convex_hull.cpp +++ b/core/math/convex_hull.cpp @@ -2278,7 +2278,7 @@ Error ConvexHullComputer::convex_hull(const Vector<Vector3> &p_points, Geometry3 uint32_t edges_copied = 0; for (uint32_t i = 0; i < ch.edges.size(); i++) { - ERR_CONTINUE(edge_faces[i] == -1); // Sanity check + ERR_CONTINUE(edge_faces[i] == -1); // Safety check. uint32_t a = (&ch.edges[i])->get_source_vertex(); uint32_t b = (&ch.edges[i])->get_target_vertex(); diff --git a/core/math/dynamic_bvh.h b/core/math/dynamic_bvh.h index dbc1cb31de..9b49fcc3c8 100644 --- a/core/math/dynamic_bvh.h +++ b/core/math/dynamic_bvh.h @@ -328,7 +328,8 @@ void DynamicBVH::aabb_query(const AABB &p_box, QueryResult &r_result) { volume.min = p_box.position; volume.max = p_box.position + p_box.size; - const Node **stack = (const Node **)alloca(ALLOCA_STACK_SIZE * sizeof(const Node *)); + const Node **alloca_stack = (const Node **)alloca(ALLOCA_STACK_SIZE * sizeof(const Node *)); + const Node **stack = alloca_stack; stack[0] = bvh_root; int32_t depth = 1; int32_t threshold = ALLOCA_STACK_SIZE - 2; @@ -343,7 +344,8 @@ void DynamicBVH::aabb_query(const AABB &p_box, QueryResult &r_result) { if (depth > threshold) { if (aux_stack.is_empty()) { aux_stack.resize(ALLOCA_STACK_SIZE * 2); - memcpy(aux_stack.ptr(), stack, ALLOCA_STACK_SIZE * sizeof(const Node *)); + memcpy(aux_stack.ptr(), alloca_stack, ALLOCA_STACK_SIZE * sizeof(const Node *)); + alloca_stack = nullptr; } else { aux_stack.resize(aux_stack.size() * 2); } @@ -384,7 +386,8 @@ void DynamicBVH::convex_query(const Plane *p_planes, int p_plane_count, const Ve } } - const Node **stack = (const Node **)alloca(ALLOCA_STACK_SIZE * sizeof(const Node *)); + const Node **alloca_stack = (const Node **)alloca(ALLOCA_STACK_SIZE * sizeof(const Node *)); + const Node **stack = alloca_stack; stack[0] = bvh_root; int32_t depth = 1; int32_t threshold = ALLOCA_STACK_SIZE - 2; @@ -399,7 +402,8 @@ void DynamicBVH::convex_query(const Plane *p_planes, int p_plane_count, const Ve if (depth > threshold) { if (aux_stack.is_empty()) { aux_stack.resize(ALLOCA_STACK_SIZE * 2); - memcpy(aux_stack.ptr(), stack, ALLOCA_STACK_SIZE * sizeof(const Node *)); + memcpy(aux_stack.ptr(), alloca_stack, ALLOCA_STACK_SIZE * sizeof(const Node *)); + alloca_stack = nullptr; } else { aux_stack.resize(aux_stack.size() * 2); } @@ -436,7 +440,8 @@ void DynamicBVH::ray_query(const Vector3 &p_from, const Vector3 &p_to, QueryResu Vector3 bounds[2]; - const Node **stack = (const Node **)alloca(ALLOCA_STACK_SIZE * sizeof(const Node *)); + const Node **alloca_stack = (const Node **)alloca(ALLOCA_STACK_SIZE * sizeof(const Node *)); + const Node **stack = alloca_stack; stack[0] = bvh_root; int32_t depth = 1; int32_t threshold = ALLOCA_STACK_SIZE - 2; @@ -456,7 +461,8 @@ void DynamicBVH::ray_query(const Vector3 &p_from, const Vector3 &p_to, QueryResu if (depth > threshold) { if (aux_stack.is_empty()) { aux_stack.resize(ALLOCA_STACK_SIZE * 2); - memcpy(aux_stack.ptr(), stack, ALLOCA_STACK_SIZE * sizeof(const Node *)); + memcpy(aux_stack.ptr(), alloca_stack, ALLOCA_STACK_SIZE * sizeof(const Node *)); + alloca_stack = nullptr; } else { aux_stack.resize(aux_stack.size() * 2); } diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h index 0e5702e0af..b37fce9e9c 100644 --- a/core/math/geometry_2d.h +++ b/core/math/geometry_2d.h @@ -306,10 +306,12 @@ public: Vector<Delaunay2D::Triangle> tr = Delaunay2D::triangulate(p_points); Vector<int> triangles; + triangles.resize(3 * tr.size()); + int *ptr = triangles.ptrw(); for (int i = 0; i < tr.size(); i++) { - triangles.push_back(tr[i].points[0]); - triangles.push_back(tr[i].points[1]); - triangles.push_back(tr[i].points[2]); + *ptr++ = tr[i].points[0]; + *ptr++ = tr[i].points[1]; + *ptr++ = tr[i].points[2]; } return triangles; } diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index 366ccca4cb..3060f31970 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -198,6 +198,22 @@ public: #endif } + // These methods assume (p_num + p_den) doesn't overflow. + static _ALWAYS_INLINE_ int32_t division_round_up(int32_t p_num, int32_t p_den) { + int32_t offset = (p_num < 0 && p_den < 0) ? 1 : -1; + return (p_num + p_den + offset) / p_den; + } + static _ALWAYS_INLINE_ uint32_t division_round_up(uint32_t p_num, uint32_t p_den) { + return (p_num + p_den - 1) / p_den; + } + static _ALWAYS_INLINE_ int64_t division_round_up(int64_t p_num, int64_t p_den) { + int32_t offset = (p_num < 0 && p_den < 0) ? 1 : -1; + return (p_num + p_den + offset) / p_den; + } + static _ALWAYS_INLINE_ uint64_t division_round_up(uint64_t p_num, uint64_t p_den) { + return (p_num + p_den - 1) / p_den; + } + static _ALWAYS_INLINE_ bool is_finite(double p_val) { return isfinite(p_val); } static _ALWAYS_INLINE_ bool is_finite(float p_val) { return isfinite(p_val); } diff --git a/core/math/rect2.h b/core/math/rect2.h index 6ccb76cd10..5f403458fd 100644 --- a/core/math/rect2.h +++ b/core/math/rect2.h @@ -285,6 +285,10 @@ struct _NO_DISCARD_ Rect2 { return Rect2(Point2(position.x + MIN(size.x, (real_t)0), position.y + MIN(size.y, (real_t)0)), size.abs()); } + _FORCE_INLINE_ Rect2 round() const { + return Rect2(position.round(), size.round()); + } + Vector2 get_support(const Vector2 &p_normal) const { Vector2 half_extents = size * 0.5f; Vector2 ofs = position + half_extents; diff --git a/core/math/vector2i.h b/core/math/vector2i.h index e6850347c3..b2c75beb4d 100644 --- a/core/math/vector2i.h +++ b/core/math/vector2i.h @@ -85,6 +85,14 @@ struct _NO_DISCARD_ Vector2i { return Vector2i(MAX(x, p_vector2i.x), MAX(y, p_vector2i.y)); } + double distance_to(const Vector2i &p_to) const { + return (p_to - *this).length(); + } + + int64_t distance_squared_to(const Vector2i &p_to) const { + return (p_to - *this).length_squared(); + } + Vector2i operator+(const Vector2i &p_v) const; void operator+=(const Vector2i &p_v); Vector2i operator-(const Vector2i &p_v) const; diff --git a/core/math/vector3i.h b/core/math/vector3i.h index 53d3829a99..5a5e9deda8 100644 --- a/core/math/vector3i.h +++ b/core/math/vector3i.h @@ -87,6 +87,9 @@ struct _NO_DISCARD_ Vector3i { Vector3i clamp(const Vector3i &p_min, const Vector3i &p_max) const; Vector3i snapped(const Vector3i &p_step) const; + _FORCE_INLINE_ double distance_to(const Vector3i &p_to) const; + _FORCE_INLINE_ int64_t distance_squared_to(const Vector3i &p_to) const; + /* Operators */ _FORCE_INLINE_ Vector3i &operator+=(const Vector3i &p_v); @@ -143,6 +146,14 @@ Vector3i Vector3i::sign() const { return Vector3i(SIGN(x), SIGN(y), SIGN(z)); } +double Vector3i::distance_to(const Vector3i &p_to) const { + return (p_to - *this).length(); +} + +int64_t Vector3i::distance_squared_to(const Vector3i &p_to) const { + return (p_to - *this).length_squared(); +} + /* Operators */ Vector3i &Vector3i::operator+=(const Vector3i &p_v) { diff --git a/core/math/vector4i.h b/core/math/vector4i.h index b815aa8e76..7d85d473d9 100644 --- a/core/math/vector4i.h +++ b/core/math/vector4i.h @@ -84,6 +84,9 @@ struct _NO_DISCARD_ Vector4i { _FORCE_INLINE_ void zero(); + _FORCE_INLINE_ double distance_to(const Vector4i &p_to) const; + _FORCE_INLINE_ int64_t distance_squared_to(const Vector4i &p_to) const; + _FORCE_INLINE_ Vector4i abs() const; _FORCE_INLINE_ Vector4i sign() const; Vector4i clamp(const Vector4i &p_min, const Vector4i &p_max) const; @@ -139,6 +142,14 @@ double Vector4i::length() const { return Math::sqrt((double)length_squared()); } +double Vector4i::distance_to(const Vector4i &p_to) const { + return (p_to - *this).length(); +} + +int64_t Vector4i::distance_squared_to(const Vector4i &p_to) const { + return (p_to - *this).length_squared(); +} + Vector4i Vector4i::abs() const { return Vector4i(Math::abs(x), Math::abs(y), Math::abs(z), Math::abs(w)); } diff --git a/core/object/callable_method_pointer.h b/core/object/callable_method_pointer.h index 2dbb7e468e..f8e8c4d7e9 100644 --- a/core/object/callable_method_pointer.h +++ b/core/object/callable_method_pointer.h @@ -81,35 +81,27 @@ template <class T, class... P> class CallableCustomMethodPointer : public CallableCustomMethodPointerBase { struct Data { T *instance; -#ifdef DEBUG_ENABLED uint64_t object_id; -#endif void (T::*method)(P...); } data; public: virtual ObjectID get_object() const { -#ifdef DEBUG_ENABLED if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) { return ObjectID(); } -#endif 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 { -#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 + ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); call_with_variant_args(data.instance, data.method, p_arguments, p_argcount, r_call_error); } CallableCustomMethodPointer(T *p_instance, void (T::*p_method)(P...)) { memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. data.instance = p_instance; -#ifdef DEBUG_ENABLED data.object_id = p_instance->get_instance_id(); -#endif data.method = p_method; _setup((uint32_t *)&data, sizeof(Data)); } @@ -135,36 +127,28 @@ template <class T, class R, class... P> class CallableCustomMethodPointerRet : public CallableCustomMethodPointerBase { struct Data { T *instance; -#ifdef DEBUG_ENABLED uint64_t object_id; -#endif R(T::*method) (P...); } data; public: virtual ObjectID get_object() const { -#ifdef DEBUG_ENABLED if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) { return ObjectID(); } -#endif 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 { -#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 + ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); call_with_variant_args_ret(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); } CallableCustomMethodPointerRet(T *p_instance, R (T::*p_method)(P...)) { memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. data.instance = p_instance; -#ifdef DEBUG_ENABLED data.object_id = p_instance->get_instance_id(); -#endif data.method = p_method; _setup((uint32_t *)&data, sizeof(Data)); } @@ -190,36 +174,28 @@ template <class T, class R, class... P> class CallableCustomMethodPointerRetC : public CallableCustomMethodPointerBase { struct Data { T *instance; -#ifdef DEBUG_ENABLED uint64_t object_id; -#endif R(T::*method) (P...) const; } data; public: virtual ObjectID get_object() const override { -#ifdef DEBUG_ENABLED if (ObjectDB::get_instance(ObjectID(data.object_id)) == nullptr) { return ObjectID(); } -#endif 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 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 + ERR_FAIL_NULL_MSG(ObjectDB::get_instance(ObjectID(data.object_id)), "Invalid Object id '" + uitos(data.object_id) + "', can't call method."); call_with_variant_args_retc(data.instance, data.method, p_arguments, p_argcount, r_return_value, r_call_error); } CallableCustomMethodPointerRetC(T *p_instance, R (T::*p_method)(P...) const) { memset(&data, 0, sizeof(Data)); // Clear beforehand, may have padding bytes. data.instance = p_instance; -#ifdef DEBUG_ENABLED data.object_id = p_instance->get_instance_id(); -#endif data.method = p_method; _setup((uint32_t *)&data, sizeof(Data)); } diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index c594f4a9b4..bf1bd0de93 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -98,9 +98,24 @@ void ClassDB::get_class_list(List<StringName> *p_classes) { p_classes->push_back(E.key); } - p_classes->sort(); + p_classes->sort_custom<StringName::AlphCompare>(); } +#ifdef TOOLS_ENABLED +void ClassDB::get_extensions_class_list(List<StringName> *p_classes) { + OBJTYPE_RLOCK; + + for (const KeyValue<StringName, ClassInfo> &E : classes) { + if (E.value.api != API_EXTENSION && E.value.api != API_EDITOR_EXTENSION) { + continue; + } + p_classes->push_back(E.key); + } + + p_classes->sort_custom<StringName::AlphCompare>(); +} +#endif + void ClassDB::get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes) { OBJTYPE_RLOCK; @@ -165,8 +180,8 @@ ClassDB::APIType ClassDB::get_api_type(const StringName &p_class) { } uint32_t ClassDB::get_api_hash(APIType p_api) { - OBJTYPE_RLOCK; #ifdef DEBUG_METHODS_ENABLED + OBJTYPE_WLOCK; if (api_hashes_cache.has(p_api)) { return api_hashes_cache[p_api]; @@ -175,7 +190,9 @@ uint32_t ClassDB::get_api_hash(APIType p_api) { uint64_t hash = hash_murmur3_one_64(HashMapHasherDefault::hash(VERSION_FULL_CONFIG)); List<StringName> class_list; - ClassDB::get_class_list(&class_list); + for (const KeyValue<StringName, ClassInfo> &E : classes) { + class_list.push_back(E.key); + } // Must be alphabetically sorted for hash to compute. class_list.sort_custom<StringName::AlphCompare>(); @@ -859,8 +876,8 @@ void ClassDB::get_enum_constants(const StringName &p_class, const StringName &p_ } void ClassDB::set_method_error_return_values(const StringName &p_class, const StringName &p_method, const Vector<Error> &p_values) { - OBJTYPE_RLOCK; #ifdef DEBUG_METHODS_ENABLED + OBJTYPE_WLOCK; ClassInfo *type = classes.getptr(p_class); ERR_FAIL_NULL(type); @@ -871,6 +888,7 @@ void ClassDB::set_method_error_return_values(const StringName &p_class, const St Vector<Error> ClassDB::get_method_error_return_values(const StringName &p_class, const StringName &p_method) { #ifdef DEBUG_METHODS_ENABLED + OBJTYPE_RLOCK; ClassInfo *type = classes.getptr(p_class); ERR_FAIL_NULL_V(type, Vector<Error>()); @@ -1415,6 +1433,8 @@ void ClassDB::_bind_compatibility(ClassInfo *type, MethodBind *p_method) { } void ClassDB::_bind_method_custom(const StringName &p_class, MethodBind *p_method, bool p_compatibility) { + OBJTYPE_WLOCK; + ClassInfo *type = classes.getptr(p_class); if (!type) { ERR_FAIL_MSG("Couldn't bind custom method '" + p_method->get_name() + "' for instance '" + p_class + "'."); diff --git a/core/object/class_db.h b/core/object/class_db.h index 5c2c59d508..7a4ee1afa4 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -251,6 +251,9 @@ public: } static void get_class_list(List<StringName> *p_classes); +#ifdef TOOLS_ENABLED + static void get_extensions_class_list(List<StringName> *p_classes); +#endif static void get_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes); static void get_direct_inheriters_from_class(const StringName &p_class, List<StringName> *p_classes); static StringName get_parent_class_nocheck(const StringName &p_class); diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index 0f3cf3916a..ae70981f72 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -1,78 +1,74 @@ -proto = """ -#define GDVIRTUAL$VER($RET m_name $ARG) \\ -StringName _gdvirtual_##m_name##_sn = #m_name;\\ -mutable bool _gdvirtual_##m_name##_initialized = false;\\ -mutable void* _gdvirtual_##m_name = nullptr;\\ -template<bool required>\\ -_FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ - ScriptInstance *_script_instance = ((Object*)(this))->get_script_instance();\\ - if (_script_instance) {\\ - Callable::CallError ce; \\ - $CALLSIARGS\\ - $CALLSIBEGIN_script_instance->callp(_gdvirtual_##m_name##_sn, $CALLSIARGPASS, ce);\\ - if (ce.error == Callable::CallError::CALL_OK) {\\ - $CALLSIRET\\ +proto = """#define GDVIRTUAL$VER($RET m_name $ARG)\\ + StringName _gdvirtual_##m_name##_sn = #m_name;\\ + mutable bool _gdvirtual_##m_name##_initialized = false;\\ + mutable void *_gdvirtual_##m_name = nullptr;\\ + template <bool required>\\ + _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST {\\ + ScriptInstance *_script_instance = ((Object *)(this))->get_script_instance();\\ + if (_script_instance) {\\ + Callable::CallError ce;\\ + $CALLSIARGS\\ + $CALLSIBEGIN_script_instance->callp(_gdvirtual_##m_name##_sn, $CALLSIARGPASS, ce);\\ + if (ce.error == Callable::CallError::CALL_OK) {\\ + $CALLSIRET\\ + return true;\\ + }\\ + }\\ + if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\ + _gdvirtual_##m_name = nullptr;\\ + if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ + _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + } else if (_get_extension()->get_virtual) {\\ + _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + }\\ + GDVIRTUAL_TRACK(_gdvirtual_##m_name, _gdvirtual_##m_name##_initialized);\\ + _gdvirtual_##m_name##_initialized = true;\\ + }\\ + if (_gdvirtual_##m_name) {\\ + $CALLPTRARGS\\ + $CALLPTRRETDEF\\ + if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ + _get_extension()->call_virtual_with_data(_get_extension_instance(), &_gdvirtual_##m_name##_sn, _gdvirtual_##m_name, $CALLPTRARGPASS, $CALLPTRRETPASS);\\ + $CALLPTRRET\\ + } else {\\ + ((GDExtensionClassCallVirtual)_gdvirtual_##m_name)(_get_extension_instance(), $CALLPTRARGPASS, $CALLPTRRETPASS);\\ + $CALLPTRRET\\ + }\\ return true;\\ - } \\ + }\\ + if (required) {\\ + ERR_PRINT_ONCE("Required virtual method " + get_class() + "::" + #m_name + " must be overridden before calling.");\\ + $RVOID\\ + }\\ + return false;\\ }\\ - if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\ - _gdvirtual_##m_name = nullptr;\\ - if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ - _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ - } else if (_get_extension()->get_virtual) {\\ - _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ - }\\ - GDVIRTUAL_TRACK(_gdvirtual_##m_name, _gdvirtual_##m_name##_initialized); \\ - _gdvirtual_##m_name##_initialized = true;\\ - }\\ - if (_gdvirtual_##m_name) {\\ - $CALLPTRARGS\\ - $CALLPTRRETDEF\\ - if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ - _get_extension()->call_virtual_with_data(_get_extension_instance(), &_gdvirtual_##m_name##_sn, _gdvirtual_##m_name, $CALLPTRARGPASS,$CALLPTRRETPASS);\\ - $CALLPTRRET\\ - } else {\\ - ((GDExtensionClassCallVirtual)_gdvirtual_##m_name)(_get_extension_instance(),$CALLPTRARGPASS,$CALLPTRRETPASS);\\ - $CALLPTRRET\\ - }\\ - return true;\\ - }\\ - \\ - if (required) {\\ - ERR_PRINT_ONCE("Required virtual method " + get_class() + "::" + #m_name + " must be overridden before calling.");\\ - $RVOID\\ - }\\ -\\ - return false;\\ -}\\ -_FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const { \\ - ScriptInstance *_script_instance = ((Object*)(this))->get_script_instance();\\ - if (_script_instance) {\\ - return _script_instance->has_method(_gdvirtual_##m_name##_sn);\\ - }\\ - if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\ - _gdvirtual_##m_name = nullptr;\\ - if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ - _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ - } else if (_get_extension()->get_virtual) {\\ - _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ - }\\ - GDVIRTUAL_TRACK(_gdvirtual_##m_name, _gdvirtual_##m_name##_initialized); \\ - _gdvirtual_##m_name##_initialized = true;\\ - }\\ - if (_gdvirtual_##m_name) {\\ - return true;\\ + _FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const {\\ + ScriptInstance *_script_instance = ((Object *)(this))->get_script_instance();\\ + if (_script_instance && _script_instance->has_method(_gdvirtual_##m_name##_sn)) {\\ + return true;\\ + }\\ + if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\ + _gdvirtual_##m_name = nullptr;\\ + if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ + _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + } else if (_get_extension()->get_virtual) {\\ + _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + }\\ + GDVIRTUAL_TRACK(_gdvirtual_##m_name, _gdvirtual_##m_name##_initialized);\\ + _gdvirtual_##m_name##_initialized = true;\\ + }\\ + if (_gdvirtual_##m_name) {\\ + return true;\\ + }\\ + return false;\\ }\\ - return false;\\ -}\\ -\\ -_FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() { \\ - MethodInfo method_info;\\ - method_info.name = #m_name;\\ - method_info.flags = METHOD_FLAG_VIRTUAL;\\ - $FILL_METHOD_INFO\\ - return method_info;\\ -} + _FORCE_INLINE_ static MethodInfo _gdvirtual_##m_name##_get_method_info() {\\ + MethodInfo method_info;\\ + method_info.name = #m_name;\\ + method_info.flags = $METHOD_FLAGS;\\ + $FILL_METHOD_INFO\\ + return method_info;\\ + } """ @@ -83,22 +79,23 @@ def generate_version(argcount, const=False, returns=False): method_info = "" if returns: sproto += "R" - s = s.replace("$RET", "m_ret, ") + s = s.replace("$RET", "m_ret,") s = s.replace("$RVOID", "(void)r_ret;") # If required, may lead to uninitialized errors s = s.replace("$CALLPTRRETDEF", "PtrToArg<m_ret>::EncodeT ret;") - method_info += "\tmethod_info.return_val = GetTypeInfo<m_ret>::get_class_info();\\\n" - method_info += "\tmethod_info.return_val_metadata = GetTypeInfo<m_ret>::METADATA;\\\n" + method_info += "method_info.return_val = GetTypeInfo<m_ret>::get_class_info();\\\n" + method_info += "\t\tmethod_info.return_val_metadata = GetTypeInfo<m_ret>::METADATA;" else: - s = s.replace("$RET", "") - s = s.replace("$RVOID", "") - s = s.replace("$CALLPTRRETDEF", "") + s = s.replace("$RET ", "") + s = s.replace("\t\t\t$RVOID\\\n", "") + s = s.replace("\t\t\t$CALLPTRRETDEF\\\n", "") if const: sproto += "C" s = s.replace("$CONST", "const") - method_info += "\tmethod_info.flags|=METHOD_FLAG_CONST;\\\n" + s = s.replace("$METHOD_FLAGS", "METHOD_FLAG_VIRTUAL | METHOD_FLAG_CONST") else: - s = s.replace("$CONST", "") + s = s.replace("$CONST ", "") + s = s.replace("$METHOD_FLAGS", "METHOD_FLAG_VIRTUAL") s = s.replace("$VER", sproto) argtext = "" @@ -108,9 +105,9 @@ def generate_version(argcount, const=False, returns=False): callptrargsptr = "" if argcount > 0: argtext += ", " - callsiargs = "Variant vargs[" + str(argcount) + "]={" - callsiargptrs = "\t\tconst Variant *vargptrs[" + str(argcount) + "]={" - callptrargsptr = "\t\tGDExtensionConstTypePtr argptrs[" + str(argcount) + "]={" + callsiargs = f"Variant vargs[{argcount}] = {{ " + callsiargptrs = f"\t\t\tconst Variant *vargptrs[{argcount}] = {{ " + callptrargsptr = f"\t\t\tGDExtensionConstTypePtr argptrs[{argcount}] = {{ " callptrargs = "" for i in range(argcount): if i > 0: @@ -118,52 +115,55 @@ def generate_version(argcount, const=False, returns=False): callargtext += ", " callsiargs += ", " callsiargptrs += ", " - callptrargs += "\t\t" + callptrargs += "\t\t\t" callptrargsptr += ", " - argtext += "m_type" + str(i + 1) - callargtext += "m_type" + str(i + 1) + " arg" + str(i + 1) - callsiargs += "Variant(arg" + str(i + 1) + ")" - callsiargptrs += "&vargs[" + str(i) + "]" + argtext += f"m_type{i + 1}" + callargtext += f"m_type{i + 1} arg{i + 1}" + callsiargs += f"Variant(arg{i + 1})" + callsiargptrs += f"&vargs[{i}]" callptrargs += ( - "PtrToArg<m_type" + str(i + 1) + ">::EncodeT argval" + str(i + 1) + " = arg" + str(i + 1) + ";\\\n" - ) - callptrargsptr += "&argval" + str(i + 1) - method_info += "\tmethod_info.arguments.push_back(GetTypeInfo<m_type" + str(i + 1) + ">::get_class_info());\\\n" - method_info += ( - "\tmethod_info.arguments_metadata.push_back(GetTypeInfo<m_type" + str(i + 1) + ">::METADATA);\\\n" + f"PtrToArg<m_type{i + 1}>::EncodeT argval{i + 1} = (PtrToArg<m_type{i + 1}>::EncodeT)arg{i + 1};\\\n" ) + callptrargsptr += f"&argval{i + 1}" + if method_info: + method_info += "\\\n\t\t" + method_info += f"method_info.arguments.push_back(GetTypeInfo<m_type{i + 1}>::get_class_info());\\\n" + method_info += f"\t\tmethod_info.arguments_metadata.push_back(GetTypeInfo<m_type{i + 1}>::METADATA);" if argcount: - callsiargs += "};\\\n" - callsiargptrs += "};\\\n" + callsiargs += " };\\\n" + callsiargptrs += " };" s = s.replace("$CALLSIARGS", callsiargs + callsiargptrs) - s = s.replace("$CALLSIARGPASS", "(const Variant **)vargptrs," + str(argcount)) - callptrargsptr += "};\\\n" + s = s.replace("$CALLSIARGPASS", f"(const Variant **)vargptrs, {argcount}") + callptrargsptr += " };" s = s.replace("$CALLPTRARGS", callptrargs + callptrargsptr) - s = s.replace("$CALLPTRARGPASS", "reinterpret_cast<GDExtensionConstTypePtr*>(argptrs)") + s = s.replace("$CALLPTRARGPASS", "reinterpret_cast<GDExtensionConstTypePtr *>(argptrs)") else: - s = s.replace("$CALLSIARGS", "") + s = s.replace("\t\t\t$CALLSIARGS\\\n", "") s = s.replace("$CALLSIARGPASS", "nullptr, 0") - s = s.replace("$CALLPTRARGS", "") + s = s.replace("\t\t\t$CALLPTRARGS\\\n", "") s = s.replace("$CALLPTRARGPASS", "nullptr") if returns: if argcount > 0: - callargtext += "," - callargtext += " m_ret& r_ret" + callargtext += ", " + callargtext += "m_ret &r_ret" s = s.replace("$CALLSIBEGIN", "Variant ret = ") s = s.replace("$CALLSIRET", "r_ret = VariantCaster<m_ret>::cast(ret);") s = s.replace("$CALLPTRRETPASS", "&ret") s = s.replace("$CALLPTRRET", "r_ret = (m_ret)ret;") else: s = s.replace("$CALLSIBEGIN", "") - s = s.replace("$CALLSIRET", "") + s = s.replace("\t\t\t\t$CALLSIRET\\\n", "") s = s.replace("$CALLPTRRETPASS", "nullptr") - s = s.replace("$CALLPTRRET", "") + s = s.replace("\t\t\t\t$CALLPTRRET\\\n", "") - s = s.replace("$ARG", argtext) + s = s.replace(" $ARG", argtext) s = s.replace("$CALLARGS", callargtext) - s = s.replace("$FILL_METHOD_INFO", method_info) + if method_info: + s = s.replace("$FILL_METHOD_INFO", method_info) + else: + s = s.replace("\t\t$FILL_METHOD_INFO\\\n", method_info) return s @@ -171,21 +171,21 @@ def generate_version(argcount, const=False, returns=False): def run(target, source, env): max_versions = 12 - txt = """ + txt = """/* THIS FILE IS GENERATED DO NOT EDIT */ #ifndef GDVIRTUAL_GEN_H #define GDVIRTUAL_GEN_H #include "core/object/script_instance.h" #ifdef TOOLS_ENABLED -#define GDVIRTUAL_TRACK(m_virtual, m_initialized) \\ - if (_get_extension()->reloadable) {\\ - VirtualMethodTracker *tracker = memnew(VirtualMethodTracker);\\ - tracker->method = (void **)&m_virtual;\\ - tracker->initialized = &m_initialized;\\ - tracker->next = virtual_method_list;\\ - virtual_method_list = tracker;\\ - } +#define GDVIRTUAL_TRACK(m_virtual, m_initialized)\\ + if (_get_extension()->reloadable) {\\ + VirtualMethodTracker *tracker = memnew(VirtualMethodTracker);\\ + tracker->method = (void **)&m_virtual;\\ + tracker->initialized = &m_initialized;\\ + tracker->next = virtual_method_list;\\ + virtual_method_list = tracker;\\ + } #else #define GDVIRTUAL_TRACK(m_virtual, m_initialized) #endif @@ -193,13 +193,13 @@ def run(target, source, env): """ for i in range(max_versions + 1): - txt += "/* " + str(i) + " Arguments */\n\n" + txt += f"/* {i} Arguments */\n\n" txt += generate_version(i, False, False) txt += generate_version(i, False, True) txt += generate_version(i, True, False) txt += generate_version(i, True, True) - txt += "#endif" + txt += "#endif // GDVIRTUAL_GEN_H\n" with open(target[0], "w") as f: f.write(txt) diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index 506f8291eb..a394c957d0 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -35,8 +35,10 @@ #include "core/object/class_db.h" #include "core/object/script_language.h" +#include <stdio.h> + #ifdef DEV_ENABLED -// Includes sanity checks to ensure that a queue set as a thread singleton override +// Includes safety 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) { \ @@ -93,7 +95,7 @@ Error CallQueue::push_callablep(const Callable &p_callable, const Variant **p_ar 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); + fprintf(stderr, "Failed method: %s. Message queue out of memory. %s\n", String(p_callable).utf8().get_data(), error_text.utf8().get_data()); statistics(); UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; @@ -144,7 +146,7 @@ Error CallQueue::push_set(ObjectID p_id, const StringName &p_prop, const Variant 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); + fprintf(stderr, "Failed set: %s: %s target ID: %s. Message queue out of memory. %s\n", type.utf8().get_data(), String(p_prop).utf8().get_data(), itos(p_id).utf8().get_data(), error_text.utf8().get_data()); statistics(); UNLOCK_MUTEX; @@ -181,7 +183,7 @@ Error CallQueue::push_notification(ObjectID p_id, int p_notification) { 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); + fprintf(stderr, "Failed notification: %s target ID: %s. Message queue out of memory. %s\n", itos(p_notification).utf8().get_data(), itos(p_id).utf8().get_data(), error_text.utf8().get_data()); statistics(); UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; @@ -537,7 +539,7 @@ CallQueue::~CallQueue() { if (!allocator_is_custom) { memdelete(allocator); } - // This is done here to avoid a circular dependency between the sanity checks and the thread singleton pointer. + // This is done here to avoid a circular dependency between the safety checks and the thread singleton pointer. if (this == MessageQueue::thread_singleton) { MessageQueue::thread_singleton = nullptr; } diff --git a/core/object/object.cpp b/core/object/object.cpp index f62b93d0ff..5a776e2106 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -198,6 +198,7 @@ bool Object::_predelete() { notification(NOTIFICATION_PREDELETE, true); if (_predelete_ok) { _class_name_ptr = nullptr; // Must restore, so constructors/destructors have proper class name access at each stage. + notification(NOTIFICATION_PREDELETE_CLEANUP, true); } return _predelete_ok; } @@ -666,8 +667,16 @@ bool Object::has_method(const StringName &p_method) const { } MethodBind *method = ClassDB::get_method(get_class_name(), p_method); + if (method != nullptr) { + return true; + } - return method != nullptr; + const Script *scr = Object::cast_to<Script>(this); + if (scr != nullptr) { + return scr->has_static_method(p_method); + } + + return false; } Variant Object::getvar(const Variant &p_key, bool *r_valid) const { @@ -1101,8 +1110,7 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int Error err = OK; for (const Connection &c : slot_conns) { - Object *target = c.callable.get_object(); - if (!target) { + if (!c.callable.is_valid()) { // Target might have been deleted during signal callback, this is expected and OK. continue; } @@ -1125,7 +1133,8 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int continue; } #endif - if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD && !ClassDB::class_exists(target->get_class_name())) { + Object *target = c.callable.get_object(); + if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD && target && !ClassDB::class_exists(target->get_class_name())) { //most likely object is not initialized yet, do not throw error. } else { ERR_PRINT("Error calling from signal '" + String(p_name) + "' to callable: " + Variant::get_callable_error_text(c.callable, args, argc, ce) + "."); @@ -1305,8 +1314,14 @@ void Object::get_signals_connected_to_this(List<Connection> *p_connections) cons Error Object::connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags) { ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is null."); - Object *target_object = p_callable.get_object(); - ERR_FAIL_NULL_V_MSG(target_object, ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null."); + if (p_callable.is_standard()) { + // FIXME: This branch should probably removed in favor of the `is_valid()` branch, but there exist some classes + // that call `connect()` before they are fully registered with ClassDB. Until all such classes can be found + // and registered soon enough this branch is needed to allow `connect()` to succeed. + ERR_FAIL_NULL_V_MSG(p_callable.get_object(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null."); + } else { + ERR_FAIL_COND_V_MSG(!p_callable.is_valid(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is not valid: " + p_callable); + } SignalData *s = signal_map.getptr(p_signal); if (!s) { @@ -1332,32 +1347,34 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui s = &signal_map[p_signal]; } - Callable target = p_callable; - //compare with the base callable, so binds can be ignored - if (s->slot_map.has(*target.get_base_comparator())) { + if (s->slot_map.has(*p_callable.get_base_comparator())) { if (p_flags & CONNECT_REFERENCE_COUNTED) { - s->slot_map[*target.get_base_comparator()].reference_count++; + s->slot_map[*p_callable.get_base_comparator()].reference_count++; return OK; } else { ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Signal '" + p_signal + "' is already connected to given callable '" + p_callable + "' in that object."); } } + Object *target_object = p_callable.get_object(); + SignalData::Slot slot; Connection conn; - conn.callable = target; + conn.callable = p_callable; conn.signal = ::Signal(this, p_signal); conn.flags = p_flags; slot.conn = conn; - slot.cE = target_object->connections.push_back(conn); + if (target_object) { + slot.cE = target_object->connections.push_back(conn); + } if (p_flags & CONNECT_REFERENCE_COUNTED) { slot.reference_count = 1; } //use callable version as key, so binds can be ignored - s->slot_map[*target.get_base_comparator()] = slot; + s->slot_map[*p_callable.get_base_comparator()] = slot; return OK; } @@ -1378,9 +1395,7 @@ bool Object::is_connected(const StringName &p_signal, const Callable &p_callable ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + "."); } - Callable target = p_callable; - - return s->slot_map.has(*target.get_base_comparator()); + return s->slot_map.has(*p_callable.get_base_comparator()); } void Object::disconnect(const StringName &p_signal, const Callable &p_callable) { @@ -1390,9 +1405,6 @@ void Object::disconnect(const StringName &p_signal, const Callable &p_callable) 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_NULL_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) || @@ -1412,7 +1424,13 @@ bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, } } - target_object->connections.erase(slot->cE); + if (slot->cE) { + Object *target_object = p_callable.get_object(); + if (target_object) { + target_object->connections.erase(slot->cE); + } + } + s->slot_map.erase(*p_callable.get_base_comparator()); if (s->slot_map.is_empty() && ClassDB::has_signal(get_class_name(), p_signal)) { @@ -1863,7 +1881,6 @@ bool Object::has_instance_binding(void *p_token) { return found; } -#ifdef TOOLS_ENABLED void Object::free_instance_binding(void *p_token) { bool found = false; _instance_binding_mutex.lock(); @@ -1888,6 +1905,7 @@ void Object::free_instance_binding(void *p_token) { _instance_binding_mutex.unlock(); } +#ifdef TOOLS_ENABLED void Object::clear_internal_extension() { ERR_FAIL_NULL(_extension); diff --git a/core/object/object.h b/core/object/object.h index 60dc031d4d..d697f14b7e 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -72,12 +72,12 @@ enum PropertyHint { PROPERTY_HINT_COLOR_NO_ALPHA, ///< used for ignoring alpha component when editing a color PROPERTY_HINT_OBJECT_ID, PROPERTY_HINT_TYPE_STRING, ///< a type string, the hint is the base type to choose - PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE, ///< so something else can provide this (used in scripts) + PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE, // Deprecated. PROPERTY_HINT_OBJECT_TOO_BIG, ///< object is too big to send PROPERTY_HINT_NODE_PATH_VALID_TYPES, PROPERTY_HINT_SAVE_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,". This opens a save dialog PROPERTY_HINT_GLOBAL_SAVE_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,". This opens a save dialog - PROPERTY_HINT_INT_IS_OBJECTID, + PROPERTY_HINT_INT_IS_OBJECTID, // Deprecated. PROPERTY_HINT_INT_IS_POINTER, PROPERTY_HINT_ARRAY_TYPE, PROPERTY_HINT_LOCALE_ID, @@ -105,7 +105,7 @@ enum PropertyUsageFlags { PROPERTY_USAGE_SCRIPT_VARIABLE = 1 << 12, PROPERTY_USAGE_STORE_IF_NULL = 1 << 13, PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED = 1 << 14, - PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE = 1 << 15, + PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE = 1 << 15, // Deprecated. PROPERTY_USAGE_CLASS_IS_ENUM = 1 << 16, PROPERTY_USAGE_NIL_IS_VARIANT = 1 << 17, PROPERTY_USAGE_ARRAY = 1 << 18, // Used in the inspector to group properties as elements of an array. @@ -115,7 +115,7 @@ enum PropertyUsageFlags { PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT = 1 << 22, PROPERTY_USAGE_RESOURCE_NOT_PERSISTENT = 1 << 23, PROPERTY_USAGE_KEYING_INCREMENTS = 1 << 24, // Used in inspector to increment property when keyed in animation player. - PROPERTY_USAGE_DEFERRED_SET_RESOURCE = 1 << 25, // when loading, the resource for this property can be set at the end of loading. + PROPERTY_USAGE_DEFERRED_SET_RESOURCE = 1 << 25, // Deprecated. 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. @@ -235,7 +235,7 @@ struct MethodInfo { return arguments_metadata.size() > p_arg ? arguments_metadata[p_arg] : 0; } - inline bool operator==(const MethodInfo &p_method) const { return id == p_method.id; } + inline bool operator==(const MethodInfo &p_method) const { return id == p_method.id && name == p_method.name; } inline bool operator<(const MethodInfo &p_method) const { return id == p_method.id ? (name < p_method.name) : (id < p_method.id); } operator Dictionary() const; @@ -656,7 +656,7 @@ private: friend class RefCounted; bool type_is_reference = false; - std::mutex _instance_binding_mutex; + BinaryMutex _instance_binding_mutex; struct InstanceBinding { void *binding = nullptr; void *token = nullptr; @@ -801,6 +801,8 @@ public: NOTIFICATION_POSTINITIALIZE = 0, NOTIFICATION_PREDELETE = 1, NOTIFICATION_EXTENSION_RELOADED = 2, + // Internal notification to send after NOTIFICATION_PREDELETE, not bound to scripting. + NOTIFICATION_PREDELETE_CLEANUP = 3, }; /* TYPE API */ @@ -970,9 +972,9 @@ public: // Used on creation by binding only. void set_instance_binding(void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks); bool has_instance_binding(void *p_token); + void free_instance_binding(void *p_token); #ifdef TOOLS_ENABLED - void free_instance_binding(void *p_token); void clear_internal_extension(); void reset_internal_extension(ObjectGDExtension *p_extension); #endif diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index 011f4203ea..0b2d5e41cf 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -39,10 +39,11 @@ ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES]; int ScriptServer::_language_count = 0; +bool ScriptServer::languages_ready = false; +Mutex ScriptServer::languages_mutex; bool ScriptServer::scripting_enabled = true; bool ScriptServer::reload_scripts_on_save = false; -SafeFlag ScriptServer::languages_finished; // Used until GH-76581 is fixed properly. ScriptEditRequestFunction ScriptServer::edit_request_func = nullptr; void Script::_notification(int p_what) { @@ -137,6 +138,8 @@ void Script::_bind_methods() { ClassDB::bind_method(D_METHOD("get_base_script"), &Script::get_base_script); ClassDB::bind_method(D_METHOD("get_instance_base_type"), &Script::get_instance_base_type); + ClassDB::bind_method(D_METHOD("get_global_name"), &Script::get_global_name); + ClassDB::bind_method(D_METHOD("has_script_signal", "signal_name"), &Script::has_script_signal); ClassDB::bind_method(D_METHOD("get_script_property_list"), &Script::_get_script_property_list); @@ -160,12 +163,13 @@ bool ScriptServer::is_scripting_enabled() { } ScriptLanguage *ScriptServer::get_language(int p_idx) { + MutexLock lock(languages_mutex); ERR_FAIL_INDEX_V(p_idx, _language_count, nullptr); - return _languages[p_idx]; } Error ScriptServer::register_language(ScriptLanguage *p_language) { + MutexLock lock(languages_mutex); ERR_FAIL_NULL_V(p_language, ERR_INVALID_PARAMETER); ERR_FAIL_COND_V_MSG(_language_count >= MAX_LANGUAGES, ERR_UNAVAILABLE, "Script languages limit has been reach, cannot register more."); for (int i = 0; i < _language_count; i++) { @@ -179,6 +183,8 @@ Error ScriptServer::register_language(ScriptLanguage *p_language) { } Error ScriptServer::unregister_language(const ScriptLanguage *p_language) { + MutexLock lock(languages_mutex); + for (int i = 0; i < _language_count; i++) { if (_languages[i] == p_language) { _language_count--; @@ -219,17 +225,53 @@ void ScriptServer::init_languages() { } } - for (int i = 0; i < _language_count; i++) { - _languages[i]->init(); + HashSet<ScriptLanguage *> langs_to_init; + { + MutexLock lock(languages_mutex); + for (int i = 0; i < _language_count; i++) { + if (_languages[i]) { + langs_to_init.insert(_languages[i]); + } + } + } + + for (ScriptLanguage *E : langs_to_init) { + E->init(); + } + + { + MutexLock lock(languages_mutex); + languages_ready = true; } } void ScriptServer::finish_languages() { - for (int i = 0; i < _language_count; i++) { - _languages[i]->finish(); + HashSet<ScriptLanguage *> langs_to_finish; + + { + MutexLock lock(languages_mutex); + for (int i = 0; i < _language_count; i++) { + if (_languages[i]) { + langs_to_finish.insert(_languages[i]); + } + } + } + + for (ScriptLanguage *E : langs_to_finish) { + E->finish(); } + + { + MutexLock lock(languages_mutex); + languages_ready = false; + } + global_classes_clear(); - languages_finished.set(); +} + +bool ScriptServer::are_languages_initialized() { + MutexLock lock(languages_mutex); + return languages_ready; } void ScriptServer::set_reload_scripts_on_save(bool p_enable) { @@ -241,7 +283,8 @@ bool ScriptServer::is_reload_scripts_on_save_enabled() { } void ScriptServer::thread_enter() { - if (!languages_finished.is_set()) { + MutexLock lock(languages_mutex); + if (!languages_ready) { return; } for (int i = 0; i < _language_count; i++) { @@ -250,7 +293,8 @@ void ScriptServer::thread_enter() { } void ScriptServer::thread_exit() { - if (!languages_finished.is_set()) { + MutexLock lock(languages_mutex); + if (!languages_ready) { return; } for (int i = 0; i < _language_count; i++) { @@ -538,9 +582,6 @@ void PlaceHolderScriptInstance::get_property_list(List<PropertyInfo> *p_properti } else { for (const PropertyInfo &E : properties) { PropertyInfo pinfo = E; - if (!values.has(pinfo.name)) { - pinfo.usage |= PROPERTY_USAGE_SCRIPT_DEFAULT_VALUE; - } p_properties->push_back(E); } } @@ -592,6 +633,10 @@ bool PlaceHolderScriptInstance::has_method(const StringName &p_method) const { void PlaceHolderScriptInstance::update(const List<PropertyInfo> &p_properties, const HashMap<StringName, Variant> &p_values) { HashSet<StringName> new_values; for (const PropertyInfo &E : p_properties) { + if (E.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_SUBGROUP | PROPERTY_USAGE_CATEGORY)) { + continue; + } + StringName n = E.name; new_values.insert(n); diff --git a/core/object/script_language.h b/core/object/script_language.h index ca08e9837a..69da50f074 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -52,9 +52,11 @@ class ScriptServer { static ScriptLanguage *_languages[MAX_LANGUAGES]; static int _language_count; + static bool languages_ready; + static Mutex languages_mutex; + static bool scripting_enabled; static bool reload_scripts_on_save; - static SafeFlag languages_finished; // Used until GH-76581 is fixed properly. struct GlobalScriptClass { StringName language; @@ -98,8 +100,7 @@ public: static void init_languages(); static void finish_languages(); - - static bool are_languages_finished() { return languages_finished.is_set(); } + static bool are_languages_initialized(); }; class PlaceHolderScriptInstance; @@ -145,7 +146,10 @@ public: virtual PropertyInfo get_class_category() const; #endif // TOOLS_ENABLED + // TODO: In the next compat breakage rename to `*_script_*` to disambiguate from `Object::has_method()`. virtual bool has_method(const StringName &p_method) const = 0; + virtual bool has_static_method(const StringName &p_method) const { return false; } + virtual MethodInfo get_method_info(const StringName &p_method) const = 0; virtual bool is_tool() const = 0; @@ -236,6 +240,7 @@ public: virtual void get_reserved_words(List<String> *p_words) const = 0; virtual bool is_control_flow_keyword(String p_string) const = 0; virtual void get_comment_delimiters(List<String> *p_delimiters) const = 0; + virtual void get_doc_comment_delimiters(List<String> *p_delimiters) const = 0; virtual void get_string_delimiters(List<String> *p_delimiters) const = 0; virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { return Ref<Script>(); } virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) { return Vector<ScriptTemplate>(); } @@ -379,10 +384,12 @@ public: uint64_t call_count; uint64_t total_time; uint64_t self_time; + uint64_t internal_time; }; virtual void profiling_start() = 0; virtual void profiling_stop() = 0; + virtual void profiling_set_save_native_calls(bool p_enable) = 0; virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) = 0; virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) = 0; diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp index ce1109781a..be62cabe25 100644 --- a/core/object/script_language_extension.cpp +++ b/core/object/script_language_extension.cpp @@ -55,6 +55,7 @@ void ScriptExtension::_bind_methods() { GDVIRTUAL_BIND(_get_class_icon_path); GDVIRTUAL_BIND(_has_method, "method"); + GDVIRTUAL_BIND(_has_static_method, "method"); GDVIRTUAL_BIND(_get_method_info, "method"); GDVIRTUAL_BIND(_is_tool); @@ -91,6 +92,7 @@ void ScriptLanguageExtension::_bind_methods() { GDVIRTUAL_BIND(_get_reserved_words); GDVIRTUAL_BIND(_is_control_flow_keyword, "keyword"); GDVIRTUAL_BIND(_get_comment_delimiters); + GDVIRTUAL_BIND(_get_doc_comment_delimiters); GDVIRTUAL_BIND(_get_string_delimiters); GDVIRTUAL_BIND(_make_template, "template", "class_name", "base_class_name"); GDVIRTUAL_BIND(_get_built_in_templates, "object"); @@ -105,7 +107,7 @@ void ScriptLanguageExtension::_bind_methods() { GDVIRTUAL_BIND(_supports_builtin_mode); GDVIRTUAL_BIND(_supports_documentation); GDVIRTUAL_BIND(_can_inherit_from_file); - GDVIRTUAL_BIND(_find_function, "class_name", "function_name"); + GDVIRTUAL_BIND(_find_function, "function", "code"); GDVIRTUAL_BIND(_make_function, "class_name", "function_name", "function_args"); GDVIRTUAL_BIND(_open_in_external_editor, "script", "line", "column"); GDVIRTUAL_BIND(_overrides_external_editor); @@ -143,6 +145,7 @@ void ScriptLanguageExtension::_bind_methods() { GDVIRTUAL_BIND(_profiling_start); GDVIRTUAL_BIND(_profiling_stop); + GDVIRTUAL_BIND(_profiling_set_save_native_calls, "enable"); GDVIRTUAL_BIND(_profiling_get_accumulated_data, "info_array", "info_max"); GDVIRTUAL_BIND(_profiling_get_frame_data, "info_array", "info_max"); diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index beb8064a33..852b2aebd8 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -99,6 +99,7 @@ public: #endif // TOOLS_ENABLED EXBIND1RC(bool, has_method, const StringName &) + EXBIND1RC(bool, has_static_method, const StringName &) GDVIRTUAL1RC(Dictionary, _get_method_info, const StringName &) virtual MethodInfo get_method_info(const StringName &p_method) const override { @@ -240,6 +241,16 @@ public: } } + GDVIRTUAL0RC(Vector<String>, _get_doc_comment_delimiters) + + virtual void get_doc_comment_delimiters(List<String> *p_words) const override { + Vector<String> ret; + GDVIRTUAL_CALL(_get_doc_comment_delimiters, ret); + for (int i = 0; i < ret.size(); i++) { + p_words->push_back(ret[i]); + } + } + GDVIRTUAL0RC(Vector<String>, _get_string_delimiters) virtual void get_string_delimiters(List<String> *p_words) const override { @@ -302,6 +313,9 @@ public: ERR_CONTINUE(!err.has("message")); ScriptError serr; + if (err.has("path")) { + serr.path = err["path"]; + } serr.line = err["line"]; serr.column = err["column"]; serr.message = err["message"]; @@ -593,6 +607,7 @@ public: EXBIND0(profiling_start) EXBIND0(profiling_stop) + EXBIND1(profiling_set_save_native_calls, bool) GDVIRTUAL2R(int, _profiling_get_accumulated_data, GDExtensionPtr<ScriptLanguageExtensionProfilingInfo>, int) diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index b0f9501985..3c1d4ef95e 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -71,9 +71,7 @@ bool UndoRedo::_redo(bool p_execute) { } current_action++; - if (p_execute) { - _process_operation_list(actions.write[current_action].do_ops.front()); - } + _process_operation_list(actions.write[current_action].do_ops.front(), p_execute); version++; emit_signal(SNAME("version_changed")); @@ -136,17 +134,22 @@ void UndoRedo::add_do_method(const Callable &p_callable) { ERR_FAIL_COND(action_level <= 0); ERR_FAIL_COND((current_action + 1) >= actions.size()); - Object *object = p_callable.get_object(); - ERR_FAIL_NULL(object); + ObjectID object_id = p_callable.get_object_id(); + Object *object = ObjectDB::get_instance(object_id); + ERR_FAIL_COND(object_id.is_valid() && object == nullptr); Operation do_op; do_op.callable = p_callable; - do_op.object = p_callable.get_object_id(); + do_op.object = object_id; if (Object::cast_to<RefCounted>(object)) { do_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object)); } do_op.type = Operation::TYPE_METHOD; do_op.name = p_callable.get_method(); + if (do_op.name == StringName()) { + // There's no `get_method()` for custom callables, so use `operator String()` instead. + do_op.name = static_cast<String>(p_callable); + } actions.write[current_action + 1].do_ops.push_back(do_op); } @@ -161,18 +164,23 @@ void UndoRedo::add_undo_method(const Callable &p_callable) { return; } - Object *object = p_callable.get_object(); - ERR_FAIL_NULL(object); + ObjectID object_id = p_callable.get_object_id(); + Object *object = ObjectDB::get_instance(object_id); + ERR_FAIL_COND(object_id.is_valid() && object == nullptr); Operation undo_op; undo_op.callable = p_callable; - undo_op.object = p_callable.get_object_id(); + undo_op.object = object_id; if (Object::cast_to<RefCounted>(object)) { undo_op.ref = Ref<RefCounted>(Object::cast_to<RefCounted>(object)); } undo_op.type = Operation::TYPE_METHOD; undo_op.force_keep_in_merge_ends = force_keep_in_merge_ends; undo_op.name = p_callable.get_method(); + if (undo_op.name == StringName()) { + // There's no `get_method()` for custom callables, so use `operator String()` instead. + undo_op.name = static_cast<String>(p_callable); + } actions.write[current_action + 1].undo_ops.push_back(undo_op); } @@ -293,6 +301,8 @@ void UndoRedo::commit_action(bool p_execute) { return; //still nested } + bool add_message = !merging; + if (merging) { version--; merging = false; @@ -306,12 +316,12 @@ void UndoRedo::commit_action(bool p_execute) { _redo(p_execute); // perform action committing--; - if (callback && actions.size() > 0) { + if (add_message && callback && actions.size() > 0) { callback(callback_ud, actions[actions.size() - 1].name); } } -void UndoRedo::_process_operation_list(List<Operation>::Element *E) { +void UndoRedo::_process_operation_list(List<Operation>::Element *E, bool p_execute) { const int PREALLOCATE_ARGS_COUNT = 16; LocalVector<const Variant *> args; @@ -327,18 +337,20 @@ void UndoRedo::_process_operation_list(List<Operation>::Element *E) { switch (op.type) { case Operation::TYPE_METHOD: { - Callable::CallError ce; - Variant ret; - op.callable.callp(nullptr, 0, ret, ce); - if (ce.error != Callable::CallError::CALL_OK) { - ERR_PRINT("Error calling UndoRedo method operation '" + String(op.name) + "': " + Variant::get_call_error_text(obj, op.name, nullptr, 0, ce)); - } + if (p_execute) { + Callable::CallError ce; + Variant ret; + op.callable.callp(nullptr, 0, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT("Error calling UndoRedo method operation '" + String(op.name) + "': " + Variant::get_call_error_text(obj, op.name, nullptr, 0, ce)); + } #ifdef TOOLS_ENABLED - Resource *res = Object::cast_to<Resource>(obj); - if (res) { - res->set_edited(true); - } + Resource *res = Object::cast_to<Resource>(obj); + if (res) { + res->set_edited(true); + } #endif + } if (method_callback) { Vector<Variant> binds; @@ -363,13 +375,16 @@ void UndoRedo::_process_operation_list(List<Operation>::Element *E) { } } break; case Operation::TYPE_PROPERTY: { - obj->set(op.name, op.value); + if (p_execute) { + obj->set(op.name, op.value); #ifdef TOOLS_ENABLED - Resource *res = Object::cast_to<Resource>(obj); - if (res) { - res->set_edited(true); - } + Resource *res = Object::cast_to<Resource>(obj); + if (res) { + res->set_edited(true); + } #endif + } + if (property_callback) { property_callback(prop_callback_ud, obj, op.name, op.value); } @@ -390,7 +405,7 @@ bool UndoRedo::undo() { if (current_action < 0) { return false; //nothing to redo } - _process_operation_list(actions.write[current_action].undo_ops.front()); + _process_operation_list(actions.write[current_action].undo_ops.front(), true); current_action--; version--; emit_signal(SNAME("version_changed")); @@ -450,6 +465,10 @@ bool UndoRedo::has_redo() const { return (current_action + 1) < actions.size(); } +bool UndoRedo::is_merging() const { + return merging; +} + uint64_t UndoRedo::get_version() const { return version; } diff --git a/core/object/undo_redo.h b/core/object/undo_redo.h index 389d8714f7..b3a3322e4b 100644 --- a/core/object/undo_redo.h +++ b/core/object/undo_redo.h @@ -85,7 +85,7 @@ private: uint64_t version = 1; void _pop_history_tail(); - void _process_operation_list(List<Operation>::Element *E); + void _process_operation_list(List<Operation>::Element *E, bool p_execute); void _discard_redo(); bool _redo(bool p_execute); @@ -131,6 +131,8 @@ public: bool has_undo() const; bool has_redo() const; + bool is_merging() const; + uint64_t get_version() const; void set_commit_notify_callback(CommitNotifyCallback p_callback, void *p_ud); diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index cfc3ac3bcf..631767219f 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -30,6 +30,7 @@ #include "worker_thread_pool.h" +#include "core/object/script_language.h" #include "core/os/os.h" #include "core/os/thread_safe.h" @@ -60,6 +61,14 @@ void WorkerThreadPool::_process_task(Task *p_task) { set_current_thread_safe_for_nodes(false); pool_thread_index = thread_ids[Thread::get_caller_id()]; ThreadData &curr_thread = threads[pool_thread_index]; + // Since the WorkerThreadPool is started before the script server, + // its pre-created threads can't have ScriptServer::thread_enter() called on them early. + // Therefore, we do it late at the first opportunity, so in case the task + // about to be run uses scripting, guarantees are held. + if (!curr_thread.ready_for_scripting && ScriptServer::are_languages_initialized()) { + ScriptServer::thread_enter(); + curr_thread.ready_for_scripting = true; + } task_mutex.lock(); p_task->pool_thread_index = pool_thread_index; if (low_priority) { @@ -75,10 +84,6 @@ void WorkerThreadPool::_process_task(Task *p_task) { if (p_task->group) { // Handling a group bool do_post = false; - Callable::CallError ce; - Variant ret; - Variant arg; - Variant *argptr = &arg; while (true) { uint32_t work_index = p_task->group->index.postincrement(); @@ -91,8 +96,7 @@ void WorkerThreadPool::_process_task(Task *p_task) { } else if (p_task->template_userdata) { p_task->template_userdata->callback_indexed(work_index); } else { - arg = work_index; - p_task->callable.callp((const Variant **)&argptr, 1, ret, ce); + p_task->callable.call(work_index); } // This is the only way to ensure posting is done when all tasks are really complete. @@ -141,9 +145,7 @@ void WorkerThreadPool::_process_task(Task *p_task) { p_task->template_userdata->callback(); memdelete(p_task->template_userdata); } else { - Callable::CallError ce; - Variant ret; - p_task->callable.callp(nullptr, 0, ret, ce); + p_task->callable.call(); } task_mutex.lock(); @@ -533,6 +535,11 @@ void WorkerThreadPool::wait_for_group_task_completion(GroupID p_group) { task_mutex.unlock(); } +int WorkerThreadPool::get_thread_index() { + Thread::ID tid = Thread::get_caller_id(); + return singleton->thread_ids.has(tid) ? singleton->thread_ids[tid] : -1; +} + void WorkerThreadPool::init(int p_thread_count, bool p_use_native_threads_low_priority, float p_low_priority_task_ratio) { ERR_FAIL_COND(threads.size() > 0); if (p_thread_count < 0) { diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index d4d9387765..dd56f95cae 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -106,6 +106,7 @@ private: uint32_t index; Thread thread; Task *current_low_prio_task = nullptr; + bool ready_for_scripting = false; }; TightLocalVector<ThreadData> threads; @@ -196,6 +197,8 @@ public: _FORCE_INLINE_ int get_thread_count() const { return threads.size(); } static WorkerThreadPool *get_singleton() { return singleton; } + static int get_thread_index(); + void init(int p_thread_count = -1, bool p_use_native_threads_low_priority = true, float p_low_priority_task_ratio = 0.3); void finish(); WorkerThreadPool(); diff --git a/core/os/condition_variable.h b/core/os/condition_variable.h index 6037ff327d..6a6996019d 100644 --- a/core/os/condition_variable.h +++ b/core/os/condition_variable.h @@ -31,7 +31,14 @@ #ifndef CONDITION_VARIABLE_H #define CONDITION_VARIABLE_H +#ifdef MINGW_ENABLED +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#include "thirdparty/mingw-std-threads/mingw.condition_variable.h" +#define THREADING_NAMESPACE mingw_stdthread +#else #include <condition_variable> +#define THREADING_NAMESPACE std +#endif // An object one or multiple threads can wait on a be notified by some other. // Normally, you want to use a semaphore for such scenarios, but when the @@ -40,12 +47,12 @@ // own mutex to tie the wait-notify to some other behavior, you need to use this. class ConditionVariable { - mutable std::condition_variable condition; + mutable THREADING_NAMESPACE::condition_variable condition; public: template <class BinaryMutexT> _ALWAYS_INLINE_ void wait(const MutexLock<BinaryMutexT> &p_lock) const { - condition.wait(const_cast<std::unique_lock<std::mutex> &>(p_lock.lock)); + condition.wait(const_cast<THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &>(p_lock.lock)); } _ALWAYS_INLINE_ void notify_one() const { diff --git a/core/os/mutex.cpp b/core/os/mutex.cpp index 7dbb60590b..5d4e457c5f 100644 --- a/core/os/mutex.cpp +++ b/core/os/mutex.cpp @@ -40,7 +40,7 @@ void _global_unlock() { _global_mutex.unlock(); } -template class MutexImpl<std::recursive_mutex>; -template class MutexImpl<std::mutex>; -template class MutexLock<MutexImpl<std::recursive_mutex>>; -template class MutexLock<MutexImpl<std::mutex>>; +template class MutexImpl<THREADING_NAMESPACE::recursive_mutex>; +template class MutexImpl<THREADING_NAMESPACE::mutex>; +template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>; +template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>; diff --git a/core/os/mutex.h b/core/os/mutex.h index cee0f8af74..03af48ca7c 100644 --- a/core/os/mutex.h +++ b/core/os/mutex.h @@ -34,7 +34,14 @@ #include "core/error/error_macros.h" #include "core/typedefs.h" +#ifdef MINGW_ENABLED +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#include "thirdparty/mingw-std-threads/mingw.mutex.h" +#define THREADING_NAMESPACE mingw_stdthread +#else #include <mutex> +#define THREADING_NAMESPACE std +#endif template <class MutexT> class MutexLock; @@ -73,9 +80,9 @@ template <int Tag> class SafeBinaryMutex { friend class MutexLock<SafeBinaryMutex>; - using StdMutexType = std::mutex; + using StdMutexType = THREADING_NAMESPACE::mutex; - mutable std::mutex mutex; + mutable THREADING_NAMESPACE::mutex mutex; static thread_local uint32_t count; public: @@ -115,7 +122,7 @@ template <class MutexT> class MutexLock { friend class ConditionVariable; - std::unique_lock<typename MutexT::StdMutexType> lock; + THREADING_NAMESPACE::unique_lock<typename MutexT::StdMutexType> lock; public: _ALWAYS_INLINE_ explicit MutexLock(const MutexT &p_mutex) : @@ -128,7 +135,7 @@ template <int Tag> class MutexLock<SafeBinaryMutex<Tag>> { friend class ConditionVariable; - std::unique_lock<std::mutex> lock; + THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> lock; public: _ALWAYS_INLINE_ explicit MutexLock(const SafeBinaryMutex<Tag> &p_mutex) : @@ -140,12 +147,12 @@ public: }; }; -using Mutex = MutexImpl<std::recursive_mutex>; // Recursive, for general use -using BinaryMutex = MutexImpl<std::mutex>; // Non-recursive, handle with care +using Mutex = MutexImpl<THREADING_NAMESPACE::recursive_mutex>; // Recursive, for general use +using BinaryMutex = MutexImpl<THREADING_NAMESPACE::mutex>; // Non-recursive, handle with care -extern template class MutexImpl<std::recursive_mutex>; -extern template class MutexImpl<std::mutex>; -extern template class MutexLock<MutexImpl<std::recursive_mutex>>; -extern template class MutexLock<MutexImpl<std::mutex>>; +extern template class MutexImpl<THREADING_NAMESPACE::recursive_mutex>; +extern template class MutexImpl<THREADING_NAMESPACE::mutex>; +extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::recursive_mutex>>; +extern template class MutexLock<MutexImpl<THREADING_NAMESPACE::mutex>>; #endif // MUTEX_H diff --git a/core/os/os.cpp b/core/os/os.cpp index 991b179e1f..26ae286979 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -39,7 +39,15 @@ #include "core/version_generated.gen.h" #include <stdarg.h> + +#ifdef MINGW_ENABLED +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#include "thirdparty/mingw-std-threads/mingw.thread.h" +#define THREADING_NAMESPACE mingw_stdthread +#else #include <thread> +#define THREADING_NAMESPACE std +#endif OS *OS::singleton = nullptr; uint64_t OS::target_ticks = 0; @@ -359,7 +367,7 @@ String OS::get_unique_id() const { } int OS::get_processor_count() const { - return std::thread::hardware_concurrency(); + return THREADING_NAMESPACE::thread::hardware_concurrency(); } String OS::get_processor_name() const { @@ -490,6 +498,12 @@ bool OS::has_feature(const String &p_feature) { } #endif +#if defined(IOS_SIMULATOR) + if (p_feature == "simulator") { + return true; + } +#endif + if (_check_internal_feature_support(p_feature)) { return true; } @@ -612,17 +626,22 @@ String OS::get_benchmark_file() { return benchmark_file; } -void OS::benchmark_begin_measure(const String &p_what) { +void OS::benchmark_begin_measure(const String &p_context, const String &p_what) { #ifdef TOOLS_ENABLED - start_benchmark_from[p_what] = OS::get_singleton()->get_ticks_usec(); + Pair<String, String> mark_key(p_context, p_what); + ERR_FAIL_COND_MSG(benchmark_marks_from.has(mark_key), vformat("Benchmark key '%s:%s' already exists.", p_context, p_what)); + + benchmark_marks_from[mark_key] = OS::get_singleton()->get_ticks_usec(); #endif } -void OS::benchmark_end_measure(const String &p_what) { +void OS::benchmark_end_measure(const String &p_context, 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); + Pair<String, String> mark_key(p_context, p_what); + ERR_FAIL_COND_MSG(!benchmark_marks_from.has(mark_key), vformat("Benchmark key '%s:%s' doesn't exist.", p_context, p_what)); - startup_benchmark_json[p_what] = total_f; + uint64_t total = OS::get_singleton()->get_ticks_usec() - benchmark_marks_from[mark_key]; + double total_f = double(total) / double(1000000); + benchmark_marks_final[mark_key] = total_f; #endif } @@ -631,19 +650,33 @@ void OS::benchmark_dump() { if (!use_benchmark) { return; } + if (!benchmark_file.is_empty()) { Ref<FileAccess> f = FileAccess::open(benchmark_file, FileAccess::WRITE); if (f.is_valid()) { + Dictionary benchmark_marks; + for (const KeyValue<Pair<String, String>, double> &E : benchmark_marks_final) { + const String mark_key = vformat("[%s] %s", E.key.first, E.key.second); + benchmark_marks[mark_key] = E.value; + } + Ref<JSON> json; json.instantiate(); - f->store_string(json->stringify(startup_benchmark_json, "\t", false, true)); + f->store_string(json->stringify(benchmark_marks, "\t", false, true)); } } else { - List<Variant> keys; - startup_benchmark_json.get_key_list(&keys); + HashMap<String, String> results; + for (const KeyValue<Pair<String, String>, double> &E : benchmark_marks_final) { + if (E.key.first == "Startup" && !results.has(E.key.first)) { + results.insert(E.key.first, "", true); // Hack to make sure "Startup" always comes first. + } + + results[E.key.first] += vformat("\t\t- %s: %.3f msec.\n", E.key.second, (E.value * 1000)); + } + print_line("BENCHMARK:"); - for (const Variant &K : keys) { - print_line("\t-", K, ": ", startup_benchmark_json[K], +" sec."); + for (const KeyValue<String, String> &E : results) { + print_line(vformat("\t[%s]\n%s", E.key, E.value)); } } #endif diff --git a/core/os/os.h b/core/os/os.h index cc5ebe1bc8..e22514dce3 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -79,8 +79,8 @@ class OS { // For tracking benchmark data bool use_benchmark = false; String benchmark_file; - HashMap<String, uint64_t> start_benchmark_from; - Dictionary startup_benchmark_json; + HashMap<Pair<String, String>, uint64_t, PairHash<String, String>> benchmark_marks_from; + HashMap<Pair<String, String>, double, PairHash<String, String>> benchmark_marks_final; protected: void _set_logger(CompositeLogger *p_logger); @@ -313,8 +313,8 @@ public: 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_begin_measure(const String &p_context, const String &p_what); + virtual void benchmark_end_measure(const String &p_context, const String &p_what); virtual void benchmark_dump(); virtual void process_and_drop_events() {} diff --git a/core/os/rw_lock.h b/core/os/rw_lock.h index a232fcb1ce..e5bb6aec2b 100644 --- a/core/os/rw_lock.h +++ b/core/os/rw_lock.h @@ -33,10 +33,17 @@ #include "core/typedefs.h" +#ifdef MINGW_ENABLED +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#include "thirdparty/mingw-std-threads/mingw.shared_mutex.h" +#define THREADING_NAMESPACE mingw_stdthread +#else #include <shared_mutex> +#define THREADING_NAMESPACE std +#endif class RWLock { - mutable std::shared_timed_mutex mutex; + mutable THREADING_NAMESPACE::shared_timed_mutex mutex; public: // Lock the RWLock, block if locked by someone else. diff --git a/core/os/semaphore.h b/core/os/semaphore.h index 66dfb3ee02..8bb1529bbd 100644 --- a/core/os/semaphore.h +++ b/core/os/semaphore.h @@ -37,13 +37,21 @@ #include "core/error/error_macros.h" #endif +#ifdef MINGW_ENABLED +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#include "thirdparty/mingw-std-threads/mingw.condition_variable.h" +#include "thirdparty/mingw-std-threads/mingw.mutex.h" +#define THREADING_NAMESPACE mingw_stdthread +#else #include <condition_variable> #include <mutex> +#define THREADING_NAMESPACE std +#endif class Semaphore { private: - mutable std::mutex mutex; - mutable std::condition_variable condition; + mutable THREADING_NAMESPACE::mutex mutex; + mutable THREADING_NAMESPACE::condition_variable condition; mutable uint32_t count = 0; // Initialized as locked. #ifdef DEBUG_ENABLED mutable uint32_t awaiters = 0; @@ -57,7 +65,7 @@ public: } _ALWAYS_INLINE_ void wait() const { - std::unique_lock lock(mutex); + THREADING_NAMESPACE::unique_lock lock(mutex); #ifdef DEBUG_ENABLED ++awaiters; #endif @@ -116,7 +124,7 @@ public: "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(); + new (&condition) THREADING_NAMESPACE::condition_variable(); } } #endif diff --git a/core/os/thread.cpp b/core/os/thread.cpp index 03e2c5409d..2ba90ba42c 100644 --- a/core/os/thread.cpp +++ b/core/os/thread.cpp @@ -69,8 +69,7 @@ void Thread::callback(ID p_caller_id, const Settings &p_settings, Callback p_cal 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); + thread = THREADING_NAMESPACE::thread(&Thread::callback, id, p_settings, p_callback, p_user); return id; } @@ -82,8 +81,7 @@ void Thread::wait_to_finish() { 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); + thread = THREADING_NAMESPACE::thread(); id = UNASSIGNED_ID; } diff --git a/core/os/thread.h b/core/os/thread.h index 3e307adfff..cc954678f9 100644 --- a/core/os/thread.h +++ b/core/os/thread.h @@ -42,7 +42,14 @@ #include "core/templates/safe_refcount.h" #include "core/typedefs.h" +#ifdef MINGW_ENABLED +#define MINGW_STDTHREAD_REDUNDANCY_WARNING +#include "thirdparty/mingw-std-threads/mingw.thread.h" +#define THREADING_NAMESPACE mingw_stdthread +#else #include <thread> +#define THREADING_NAMESPACE std +#endif class String; @@ -82,7 +89,7 @@ private: ID id = UNASSIGNED_ID; static SafeNumeric<uint64_t> id_counter; static thread_local ID caller_id; - std::thread thread; + THREADING_NAMESPACE::thread thread; static void callback(ID p_caller_id, const Settings &p_settings, Thread::Callback p_callback, void *p_userdata); diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index b4ac533779..2785d1daa5 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -120,7 +120,8 @@ 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"); + OS::get_singleton()->benchmark_begin_measure("Core", "Register Types"); + //consistency check static_assert(sizeof(Callable) <= 16); @@ -296,7 +297,7 @@ void register_core_types() { worker_thread_pool = memnew(WorkerThreadPool); - OS::get_singleton()->benchmark_end_measure("register_core_types"); + OS::get_singleton()->benchmark_end_measure("Core", "Register Types"); } void register_core_settings() { @@ -311,6 +312,8 @@ void register_core_settings() { } void register_core_singletons() { + OS::get_singleton()->benchmark_begin_measure("Core", "Register Singletons"); + GDREGISTER_CLASS(ProjectSettings); GDREGISTER_ABSTRACT_CLASS(IP); GDREGISTER_CLASS(core_bind::Geometry2D); @@ -346,24 +349,35 @@ void register_core_singletons() { Engine::get_singleton()->add_singleton(Engine::Singleton("GDExtensionManager", GDExtensionManager::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceUID", ResourceUID::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", worker_thread_pool)); + + OS::get_singleton()->benchmark_end_measure("Core", "Register Singletons"); } void register_core_extensions() { + OS::get_singleton()->benchmark_begin_measure("Core", "Register Extensions"); + // Hardcoded for now. GDExtension::initialize_gdextensions(); gdextension_manager->load_extensions(); gdextension_manager->initialize_extensions(GDExtension::INITIALIZATION_LEVEL_CORE); _is_core_extensions_registered = true; + + OS::get_singleton()->benchmark_end_measure("Core", "Register Extensions"); } void unregister_core_extensions() { + OS::get_singleton()->benchmark_begin_measure("Core", "Unregister Extensions"); + if (_is_core_extensions_registered) { gdextension_manager->deinitialize_extensions(GDExtension::INITIALIZATION_LEVEL_CORE); } + GDExtension::finalize_gdextensions(); + + OS::get_singleton()->benchmark_end_measure("Core", "Unregister Extensions"); } void unregister_core_types() { - OS::get_singleton()->benchmark_begin_measure("unregister_core_types"); + OS::get_singleton()->benchmark_begin_measure("Core", "Unregister Types"); // Destroy singletons in reverse order to ensure dependencies are not broken. @@ -434,5 +448,5 @@ void unregister_core_types() { CoreStringNames::free(); StringName::cleanup(); - OS::get_singleton()->benchmark_end_measure("unregister_core_types"); + OS::get_singleton()->benchmark_end_measure("Core", "Unregister Types"); } diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp index 4402e44ad4..658297d805 100644 --- a/core/string/string_name.cpp +++ b/core/string/string_name.cpp @@ -100,11 +100,9 @@ void StringName::cleanup() { lost_strings++; if (OS::get_singleton()->is_stdout_verbose()) { - if (d->cname) { - print_line("Orphan StringName: " + String(d->cname)); - } else { - print_line("Orphan StringName: " + String(d->name)); - } + String dname = String(d->cname ? d->cname : d->name); + + print_line(vformat("Orphan StringName: %s (static: %d, total: %d)", dname, d->static_count.get(), d->refcount.get())); } } @@ -113,7 +111,7 @@ void StringName::cleanup() { } } if (lost_strings) { - print_verbose("StringName: " + itos(lost_strings) + " unclaimed string names at exit."); + print_verbose(vformat("StringName: %d unclaimed string names at exit.", lost_strings)); } configured = false; } @@ -124,7 +122,7 @@ void StringName::unref() { if (_data && _data->refcount.unref()) { MutexLock lock(mutex); - if (_data->static_count.get() > 0) { + if (CoreGlobals::leak_reporting_enabled && _data->static_count.get() > 0) { if (_data->cname) { ERR_PRINT("BUG: Unreferenced static string to 0: " + String(_data->cname)); } else { diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 02380c92bb..8fcf2b24b5 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -518,13 +518,17 @@ String TranslationServer::get_country_name(const String &p_country) const { } void TranslationServer::set_locale(const String &p_locale) { - locale = standardize_locale(p_locale); + String new_locale = standardize_locale(p_locale); + if (locale == new_locale) { + return; + } + + locale = new_locale; + ResourceLoader::reload_translation_remaps(); if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); } - - ResourceLoader::reload_translation_remaps(); } String TranslationServer::get_locale() const { @@ -816,10 +820,11 @@ bool TranslationServer::is_pseudolocalization_enabled() const { void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) { pseudolocalization_enabled = p_enabled; + ResourceLoader::reload_translation_remaps(); + if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); } - ResourceLoader::reload_translation_remaps(); } void TranslationServer::set_editor_pseudolocalization(bool p_enabled) { @@ -836,10 +841,11 @@ void TranslationServer::reload_pseudolocalization() { pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix"); pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders"); + ResourceLoader::reload_translation_remaps(); + if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); } - ResourceLoader::reload_translation_remaps(); } StringName TranslationServer::pseudolocalize(const StringName &p_message) const { diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 5a42a3af12..a24cff4f11 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -305,7 +305,11 @@ void String::copy_from(const char *p_cstr) { char32_t *dst = this->ptrw(); for (size_t i = 0; i <= len; i++) { +#if CHAR_MIN == 0 + uint8_t c = p_cstr[i]; +#else uint8_t c = p_cstr[i] >= 0 ? p_cstr[i] : uint8_t(256 + p_cstr[i]); +#endif if (c == 0 && i < len) { print_unicode_error("NUL character", true); dst[i] = _replacement_char; @@ -338,7 +342,11 @@ void String::copy_from(const char *p_cstr, const int p_clip_to) { char32_t *dst = this->ptrw(); for (int i = 0; i < len; i++) { +#if CHAR_MIN == 0 + uint8_t c = p_cstr[i]; +#else uint8_t c = p_cstr[i] >= 0 ? p_cstr[i] : uint8_t(256 + p_cstr[i]); +#endif if (c == 0) { print_unicode_error("NUL character", true); dst[i] = _replacement_char; @@ -544,7 +552,11 @@ String &String::operator+=(const char *p_str) { char32_t *dst = ptrw() + lhs_len; for (size_t i = 0; i <= rhs_len; i++) { +#if CHAR_MIN == 0 + uint8_t c = p_str[i]; +#else uint8_t c = p_str[i] >= 0 ? p_str[i] : uint8_t(256 + p_str[i]); +#endif if (c == 0 && i < rhs_len) { print_unicode_error("NUL character", true); dst[i] = _replacement_char; @@ -1814,7 +1826,11 @@ Error String::parse_utf8(const char *p_utf8, int p_len, bool p_skip_cr) { int skip = 0; uint8_t c_start = 0; while (ptrtmp != ptrtmp_limit && *ptrtmp) { +#if CHAR_MIN == 0 + uint8_t c = *ptrtmp; +#else uint8_t c = *ptrtmp >= 0 ? *ptrtmp : uint8_t(256 + *ptrtmp); +#endif if (skip == 0) { if (p_skip_cr && c == '\r') { @@ -1882,7 +1898,11 @@ Error String::parse_utf8(const char *p_utf8, int p_len, bool p_skip_cr) { int skip = 0; uint32_t unichar = 0; while (cstr_size) { +#if CHAR_MIN == 0 + uint8_t c = *p_utf8; +#else uint8_t c = *p_utf8 >= 0 ? *p_utf8 : uint8_t(256 + *p_utf8); +#endif if (skip == 0) { if (p_skip_cr && c == '\r') { @@ -3974,24 +3994,22 @@ bool String::is_absolute_path() const { } } -static _FORCE_INLINE_ bool _is_valid_identifier_bit(int p_index, char32_t p_char) { - if (p_index == 0 && is_digit(p_char)) { - return false; // No start with number plz. - } - return is_ascii_identifier_char(p_char); -} - String String::validate_identifier() const { if (is_empty()) { return "_"; // Empty string is not a valid identifier; } - String result = *this; + String result; + if (is_digit(operator[](0))) { + result = "_" + *this; + } else { + result = *this; + } + int len = result.length(); char32_t *buffer = result.ptrw(); - for (int i = 0; i < len; i++) { - if (!_is_valid_identifier_bit(i, buffer[i])) { + if (!is_ascii_identifier_char(buffer[i])) { buffer[i] = '_'; } } @@ -4006,10 +4024,14 @@ bool String::is_valid_identifier() const { return false; } + if (is_digit(operator[](0))) { + return false; + } + const char32_t *str = &operator[](0); for (int i = 0; i < len; i++) { - if (!_is_valid_identifier_bit(i, str[i])) { + if (!is_ascii_identifier_char(str[i])) { return false; } } @@ -4697,11 +4719,16 @@ String String::property_name_encode() const { static const char32_t invalid_node_name_characters[] = { '.', ':', '@', '/', '\"', UNIQUE_NODE_PREFIX[0], 0 }; -String String::get_invalid_node_name_characters() { +String String::get_invalid_node_name_characters(bool p_allow_internal) { // Do not use this function for critical validation. String r; const char32_t *c = invalid_node_name_characters; while (*c) { + if (p_allow_internal && *c == '@') { + c++; + continue; + } + if (c != invalid_node_name_characters) { r += " "; } @@ -4815,6 +4842,7 @@ String String::sprintf(const Array &values, bool *error) const { bool pad_with_zeros = false; bool left_justified = false; bool show_sign = false; + bool as_unsigned = false; if (error) { *error = true; @@ -4855,16 +4883,27 @@ String String::sprintf(const Array &values, bool *error) const { case 'x': break; case 'X': - base = 16; capitalize = true; break; } // Get basic number. - String str = String::num_int64(ABS(value), base, capitalize); + String str; + if (!as_unsigned) { + str = String::num_int64(ABS(value), base, capitalize); + } else { + uint64_t uvalue = *((uint64_t *)&value); + // In unsigned hex, if the value fits in 32 bits, trim it down to that. + if (base == 16 && value < 0 && value >= INT32_MIN) { + uvalue &= 0xffffffff; + } + str = String::num_uint64(uvalue, base, capitalize); + } int number_len = str.length(); + bool negative = value < 0 && !as_unsigned; + // Padding. - int pad_chars_count = (value < 0 || show_sign) ? min_chars - 1 : min_chars; + int pad_chars_count = (negative || show_sign) ? min_chars - 1 : min_chars; String pad_char = pad_with_zeros ? String("0") : String(" "); if (left_justified) { str = str.rpad(pad_chars_count, pad_char); @@ -4873,8 +4912,8 @@ String String::sprintf(const Array &values, bool *error) const { } // Sign. - if (show_sign || value < 0) { - String sign_char = value < 0 ? "-" : "+"; + if (show_sign || negative) { + String sign_char = negative ? "-" : "+"; if (left_justified) { str = str.insert(0, sign_char); } else { @@ -5067,6 +5106,10 @@ String String::sprintf(const Array &values, bool *error) const { show_sign = true; break; } + case 'u': { // Treat as unsigned (for int/hex). + as_unsigned = true; + break; + } case '0': case '1': case '2': diff --git a/core/string/ustring.h b/core/string/ustring.h index f45392eee1..897b06fc6d 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -437,7 +437,7 @@ public: String property_name_encode() const; // node functions - static String get_invalid_node_name_characters(); + static String get_invalid_node_name_characters(bool p_allow_internal = false); String validate_node_name() const; String validate_identifier() const; String validate_filename() const; diff --git a/core/templates/hashfuncs.h b/core/templates/hashfuncs.h index 2a212f3dcb..05960292f5 100644 --- a/core/templates/hashfuncs.h +++ b/core/templates/hashfuncs.h @@ -310,7 +310,7 @@ struct HashMapHasherDefault { static _FORCE_INLINE_ uint32_t hash(const char16_t p_uchar) { return hash_fmix32(p_uchar); } static _FORCE_INLINE_ uint32_t hash(const char32_t p_uchar) { return hash_fmix32(p_uchar); } static _FORCE_INLINE_ uint32_t hash(const RID &p_rid) { return hash_one_uint64(p_rid.get_id()); } - static _FORCE_INLINE_ uint32_t hash(const CharString &p_char_string) { return hash_djb2(p_char_string.ptr()); } + static _FORCE_INLINE_ uint32_t hash(const CharString &p_char_string) { return hash_djb2(p_char_string.get_data()); } static _FORCE_INLINE_ uint32_t hash(const StringName &p_string_name) { return p_string_name.hash(); } static _FORCE_INLINE_ uint32_t hash(const NodePath &p_path) { return p_path.hash(); } static _FORCE_INLINE_ uint32_t hash(const ObjectID &p_id) { return hash_one_uint64(p_id); } diff --git a/core/templates/list.h b/core/templates/list.h index 6393b942ff..354e826a43 100644 --- a/core/templates/list.h +++ b/core/templates/list.h @@ -132,6 +132,8 @@ public: data->erase(this); } + void transfer_to_back(List<T, A> *p_dst_list); + _FORCE_INLINE_ Element() {} }; @@ -762,4 +764,41 @@ public: } }; +template <class T, class A> +void List<T, A>::Element::transfer_to_back(List<T, A> *p_dst_list) { + // Detach from current. + + if (data->first == this) { + data->first = data->first->next_ptr; + } + if (data->last == this) { + data->last = data->last->prev_ptr; + } + if (prev_ptr) { + prev_ptr->next_ptr = next_ptr; + } + if (next_ptr) { + next_ptr->prev_ptr = prev_ptr; + } + data->size_cache--; + + // Attach to the back of the new one. + + if (!p_dst_list->_data) { + p_dst_list->_data = memnew_allocator(_Data, A); + p_dst_list->_data->first = this; + p_dst_list->_data->last = nullptr; + p_dst_list->_data->size_cache = 0; + prev_ptr = nullptr; + } else { + p_dst_list->_data->last->next_ptr = this; + prev_ptr = p_dst_list->_data->last; + } + p_dst_list->_data->last = this; + next_ptr = nullptr; + + data = p_dst_list->_data; + p_dst_list->_data->size_cache++; +} + #endif // LIST_H diff --git a/core/templates/paged_allocator.h b/core/templates/paged_allocator.h index deb2937771..6f3f78d4d2 100644 --- a/core/templates/paged_allocator.h +++ b/core/templates/paged_allocator.h @@ -58,7 +58,7 @@ public: }; template <class... Args> - T *alloc(const Args &&...p_args) { + T *alloc(Args &&...p_args) { if (thread_safe) { spin_lock.lock(); } @@ -99,6 +99,10 @@ public: } } + template <class... Args> + T *new_allocation(Args &&...p_args) { return alloc(p_args...); } + void delete_allocation(T *p_mem) { free(p_mem); } + private: void _reset(bool p_allow_unfreed) { if (!p_allow_unfreed || !std::is_trivially_destructible<T>::value) { @@ -144,7 +148,7 @@ public: if (thread_safe) { spin_lock.lock(); } - ERR_FAIL_COND(page_pool != nullptr); //sanity check + ERR_FAIL_COND(page_pool != nullptr); // Safety check. ERR_FAIL_COND(p_page_size == 0); page_size = nearest_power_of_2_templated(p_page_size); page_mask = page_size - 1; diff --git a/core/templates/paged_array.h b/core/templates/paged_array.h index 863e3eef11..69a792958a 100644 --- a/core/templates/paged_array.h +++ b/core/templates/paged_array.h @@ -53,7 +53,12 @@ class PagedArrayPool { SpinLock spin_lock; public: - uint32_t alloc_page() { + struct PageInfo { + T *page = nullptr; + uint32_t page_id = 0; + }; + + PageInfo alloc_page() { spin_lock.lock(); if (unlikely(pages_available == 0)) { uint32_t pages_used = pages_allocated; @@ -69,13 +74,11 @@ public: } pages_available--; - uint32_t page = available_page_pool[pages_available]; + uint32_t page_id = available_page_pool[pages_available]; + T *page = page_pool[page_id]; spin_lock.unlock(); - return page; - } - T *get_page(uint32_t p_page_id) { - return page_pool[p_page_id]; + return PageInfo{ page, page_id }; } void free_page(uint32_t p_page_id) { @@ -190,9 +193,9 @@ public: _grow_page_array(); //keep out of inline } - uint32_t page_id = page_pool->alloc_page(); - page_data[page_count] = page_pool->get_page(page_id); - page_ids[page_count] = page_id; + typename PagedArrayPool<T>::PageInfo page_info = page_pool->alloc_page(); + page_data[page_count] = page_info.page; + page_ids[page_count] = page_info.page_id; } // place the new value diff --git a/core/templates/rb_map.h b/core/templates/rb_map.h index 0a33809bb2..152fddd011 100644 --- a/core/templates/rb_map.h +++ b/core/templates/rb_map.h @@ -96,6 +96,8 @@ public: typedef KeyValue<K, V> ValueType; struct Iterator { + friend class RBMap<K, V, C, A>; + _FORCE_INLINE_ KeyValue<K, V> &operator*() const { return E->key_value(); } @@ -109,11 +111,16 @@ public: return *this; } - _FORCE_INLINE_ bool operator==(const Iterator &b) const { return E == b.E; } - _FORCE_INLINE_ bool operator!=(const Iterator &b) const { return E != b.E; } + _FORCE_INLINE_ bool operator==(const Iterator &p_it) const { return E == p_it.E; } + _FORCE_INLINE_ bool operator!=(const Iterator &p_it) const { return E != p_it.E; } explicit operator bool() const { return E != nullptr; } + + Iterator &operator=(const Iterator &p_it) { + E = p_it.E; + return *this; + } Iterator(Element *p_E) { E = p_E; } Iterator() {} Iterator(const Iterator &p_it) { E = p_it.E; } @@ -136,11 +143,16 @@ public: return *this; } - _FORCE_INLINE_ bool operator==(const ConstIterator &b) const { return E == b.E; } - _FORCE_INLINE_ bool operator!=(const ConstIterator &b) const { return E != b.E; } + _FORCE_INLINE_ bool operator==(const ConstIterator &p_it) const { return E == p_it.E; } + _FORCE_INLINE_ bool operator!=(const ConstIterator &p_it) const { return E != p_it.E; } explicit operator bool() const { return E != nullptr; } + + ConstIterator &operator=(const ConstIterator &p_it) { + E = p_it.E; + return *this; + } ConstIterator(const Element *p_E) { E = p_E; } ConstIterator() {} ConstIterator(const ConstIterator &p_it) { E = p_it.E; } diff --git a/core/templates/safe_refcount.h b/core/templates/safe_refcount.h index bfc9f6fc9a..20fb0c6501 100644 --- a/core/templates/safe_refcount.h +++ b/core/templates/safe_refcount.h @@ -182,7 +182,7 @@ class SafeRefCount { SafeNumeric<uint32_t> count; #ifdef DEV_ENABLED - _ALWAYS_INLINE_ void _check_unref_sanity() { + _ALWAYS_INLINE_ void _check_unref_safety() { // This won't catch every misuse, but it's better than nothing. CRASH_COND_MSG(count.get() == 0, "Trying to unreference a SafeRefCount which is already zero is wrong and a symptom of it being misused.\n" @@ -202,14 +202,14 @@ public: _ALWAYS_INLINE_ bool unref() { // true if must be disposed of #ifdef DEV_ENABLED - _check_unref_sanity(); + _check_unref_safety(); #endif return count.decrement() == 0; } _ALWAYS_INLINE_ uint32_t unrefval() { // 0 if must be disposed of #ifdef DEV_ENABLED - _check_unref_sanity(); + _check_unref_safety(); #endif return count.decrement(); } diff --git a/core/typedefs.h b/core/typedefs.h index 24c247fd38..803b2e5ae0 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -92,6 +92,7 @@ #undef Error #undef OK #undef CONNECT_DEFERRED // override from Windows SDK, clashes with Object enum +#undef MemoryBarrier #endif // Make room for our constexpr's below by overriding potential system-specific macros. diff --git a/core/variant/callable.cpp b/core/variant/callable.cpp index d7034f1c00..0b1174c873 100644 --- a/core/variant/callable.cpp +++ b/core/variant/callable.cpp @@ -47,6 +47,13 @@ void Callable::callp(const Variant **p_arguments, int p_argcount, Variant &r_ret r_call_error.expected = 0; r_return_value = Variant(); } else if (is_custom()) { + if (!is_valid()) { + r_call_error.error = CallError::CALL_ERROR_INSTANCE_IS_NULL; + r_call_error.argument = 0; + r_call_error.expected = 0; + r_return_value = Variant(); + return; + } custom->call(p_arguments, p_argcount, r_return_value, r_call_error); } else { Object *obj = ObjectDB::get_instance(ObjectID(object)); diff --git a/core/variant/callable.h b/core/variant/callable.h index 086e5d2a00..3ae424e9bf 100644 --- a/core/variant/callable.h +++ b/core/variant/callable.h @@ -69,6 +69,8 @@ public: int expected = 0; }; + template <typename... VarArgs> + Variant call(VarArgs... p_args) const; void callp(const Variant **p_arguments, int p_argcount, Variant &r_return_value, CallError &r_call_error) const; void call_deferredp(const Variant **p_arguments, int p_argcount) const; Variant callv(const Array &p_arguments) const; diff --git a/core/variant/callable_bind.cpp b/core/variant/callable_bind.cpp index 9a6380a55f..6d6c60cbd4 100644 --- a/core/variant/callable_bind.cpp +++ b/core/variant/callable_bind.cpp @@ -133,7 +133,7 @@ void CallableCustomBind::get_bound_arguments(Vector<Variant> &r_arguments, int & } void CallableCustomBind::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { - const Variant **args = (const Variant **)alloca(sizeof(const Variant **) * (binds.size() + p_argcount)); + const Variant **args = (const Variant **)alloca(sizeof(Variant *) * (binds.size() + p_argcount)); for (int i = 0; i < p_argcount; i++) { args[i] = (const Variant *)p_arguments[i]; } @@ -145,7 +145,7 @@ void CallableCustomBind::call(const Variant **p_arguments, int p_argcount, Varia } Error CallableCustomBind::rpc(int p_peer_id, const Variant **p_arguments, int p_argcount, Callable::CallError &r_call_error) const { - const Variant **args = (const Variant **)alloca(sizeof(const Variant **) * (binds.size() + p_argcount)); + const Variant **args = (const Variant **)alloca(sizeof(Variant *) * (binds.size() + p_argcount)); for (int i = 0; i < p_argcount; i++) { args[i] = (const Variant *)p_arguments[i]; } diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp index 141ce25fa6..8b61a8993a 100644 --- a/core/variant/dictionary.cpp +++ b/core/variant/dictionary.cpp @@ -150,6 +150,15 @@ Variant Dictionary::get(const Variant &p_key, const Variant &p_default) const { return *result; } +Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) { + const Variant *result = getptr(p_key); + if (!result) { + operator[](p_key) = p_default; + return p_default; + } + return *result; +} + int Dictionary::size() const { return _p->variant_map.size(); } diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h index 8935d35ed9..f94a0da80a 100644 --- a/core/variant/dictionary.h +++ b/core/variant/dictionary.h @@ -58,6 +58,7 @@ public: Variant get_valid(const Variant &p_key) const; Variant get(const Variant &p_key, const Variant &p_default) const; + Variant get_or_add(const Variant &p_key, const Variant &p_default); int size() const; bool is_empty() const; diff --git a/core/variant/type_info.h b/core/variant/type_info.h index e89658d25b..c1f2f86a96 100644 --- a/core/variant/type_info.h +++ b/core/variant/type_info.h @@ -287,14 +287,17 @@ class BitField { int64_t value = 0; public: - _FORCE_INLINE_ void set_flag(T p_flag) { value |= (int64_t)p_flag; } + _FORCE_INLINE_ BitField<T> &set_flag(T p_flag) { + value |= (int64_t)p_flag; + return *this; + } _FORCE_INLINE_ bool has_flag(T p_flag) const { return value & (int64_t)p_flag; } _FORCE_INLINE_ bool is_empty() const { return value == 0; } _FORCE_INLINE_ void clear_flag(T p_flag) { value &= ~(int64_t)p_flag; } _FORCE_INLINE_ void clear() { value = 0; } - _FORCE_INLINE_ BitField() = default; - _FORCE_INLINE_ BitField(int64_t p_value) { value = p_value; } - _FORCE_INLINE_ BitField(T p_value) { value = (int64_t)p_value; } + _FORCE_INLINE_ constexpr BitField() = default; + _FORCE_INLINE_ constexpr BitField(int64_t p_value) { value = p_value; } + _FORCE_INLINE_ constexpr BitField(T p_value) { value = (int64_t)p_value; } _FORCE_INLINE_ operator int64_t() const { return value; } _FORCE_INLINE_ operator Variant() const { return value; } }; diff --git a/core/variant/variant.cpp b/core/variant/variant.cpp index 63ea3274ce..c352d800f9 100644 --- a/core/variant/variant.cpp +++ b/core/variant/variant.cpp @@ -931,7 +931,7 @@ bool Variant::is_zero() const { return *reinterpret_cast<const ::RID *>(_data._mem) == ::RID(); } case OBJECT: { - return _get_obj().obj == nullptr; + return get_validated_object() == nullptr; } case CALLABLE: { return reinterpret_cast<const Callable *>(_data._mem)->is_null(); @@ -1291,7 +1291,13 @@ void Variant::zero() { break; default: + Type prev_type = type; this->clear(); + if (type != prev_type) { + // clear() changes type to NIL, so it needs to be restored. + Callable::CallError ce; + Variant::construct(prev_type, *this, nullptr, 0, ce); + } break; } } @@ -2117,7 +2123,7 @@ Variant::operator ::RID() const { } else if (type == OBJECT && _get_obj().obj) { #ifdef DEBUG_ENABLED if (EngineDebugger::is_active()) { - ERR_FAIL_COND_V_MSG(ObjectDB::get_instance(_get_obj().id) == nullptr, ::RID(), "Invalid pointer (object was freed)."); + ERR_FAIL_NULL_V_MSG(ObjectDB::get_instance(_get_obj().id), ::RID(), "Invalid pointer (object was freed)."); } #endif Callable::CallError ce; diff --git a/core/variant/variant.h b/core/variant/variant.h index d698f85754..e93733040a 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -70,6 +70,7 @@ typedef Vector<int32_t> PackedInt32Array; typedef Vector<int64_t> PackedInt64Array; typedef Vector<float> PackedFloat32Array; typedef Vector<double> PackedFloat64Array; +typedef Vector<real_t> PackedRealArray; typedef Vector<String> PackedStringArray; typedef Vector<Vector2> PackedVector2Array; typedef Vector<Vector3> PackedVector3Array; @@ -338,6 +339,9 @@ public: _FORCE_INLINE_ bool is_num() const { return type == INT || type == FLOAT; } + _FORCE_INLINE_ bool is_string() const { + return type == STRING || type == STRING_NAME; + } _FORCE_INLINE_ bool is_array() const { return type >= ARRAY; } @@ -568,6 +572,7 @@ public: static ValidatedBuiltInMethod get_validated_builtin_method(Variant::Type p_type, const StringName &p_method); static PTRBuiltInMethod get_ptr_builtin_method(Variant::Type p_type, const StringName &p_method); + static MethodInfo get_builtin_method_info(Variant::Type p_type, const StringName &p_method); static int get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method); static Variant::Type get_builtin_method_argument_type(Variant::Type p_type, const StringName &p_method, int p_argument); static String get_builtin_method_argument_name(Variant::Type p_type, const StringName &p_method, int p_argument); @@ -834,6 +839,20 @@ String vformat(const String &p_text, const VarArgs... p_args) { } template <typename... VarArgs> +Variant Callable::call(VarArgs... p_args) const { + Variant args[sizeof...(p_args) + 1] = { p_args..., 0 }; // +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]; + } + + Variant ret; + CallError ce; + callp(sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), ret, ce); + return ret; +} + +template <typename... VarArgs> Callable Callable::bind(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]; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index f041d2c95e..1daad2058e 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1114,6 +1114,46 @@ struct VariantBuiltInMethodInfo { Variant::Type return_type; int argument_count = 0; Variant::Type (*get_argument_type)(int p_arg) = nullptr; + + MethodInfo get_method_info(const StringName &p_name) const { + MethodInfo mi; + mi.name = p_name; + + if (has_return_type) { + mi.return_val.type = return_type; + if (mi.return_val.type == Variant::NIL) { + mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } + } + + if (is_const) { + mi.flags |= METHOD_FLAG_CONST; + } + if (is_vararg) { + mi.flags |= METHOD_FLAG_VARARG; + } + if (is_static) { + mi.flags |= METHOD_FLAG_STATIC; + } + + for (int i = 0; i < argument_count; i++) { + PropertyInfo pi; +#ifdef DEBUG_METHODS_ENABLED + pi.name = argument_names[i]; +#else + pi.name = "arg" + itos(i + 1); +#endif + pi.type = (*get_argument_type)(i); + if (pi.type == Variant::NIL) { + pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + } + mi.arguments.push_back(pi); + } + + mi.default_arguments = default_arguments; + + return mi; + } }; typedef OAHashMap<StringName, VariantBuiltInMethodInfo> BuiltinMethodMap; @@ -1268,6 +1308,13 @@ Variant::PTRBuiltInMethod Variant::get_ptr_builtin_method(Variant::Type p_type, return method->ptrcall; } +MethodInfo Variant::get_builtin_method_info(Variant::Type p_type, const StringName &p_method) { + ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, MethodInfo()); + const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method); + ERR_FAIL_NULL_V(method, MethodInfo()); + return method->get_method_info(p_method); +} + int Variant::get_builtin_method_argument_count(Variant::Type p_type, const StringName &p_method) { ERR_FAIL_INDEX_V(p_type, Variant::VARIANT_MAX, 0); const VariantBuiltInMethodInfo *method = builtin_method_info[p_type].lookup_ptr(p_method); @@ -1378,43 +1425,7 @@ void Variant::get_method_list(List<MethodInfo> *p_list) const { for (const StringName &E : builtin_method_names[type]) { const VariantBuiltInMethodInfo *method = builtin_method_info[type].lookup_ptr(E); ERR_CONTINUE(!method); - - MethodInfo mi; - mi.name = E; - - //return type - if (method->has_return_type) { - mi.return_val.type = method->return_type; - if (mi.return_val.type == Variant::NIL) { - mi.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } - } - - if (method->is_const) { - mi.flags |= METHOD_FLAG_CONST; - } - if (method->is_vararg) { - mi.flags |= METHOD_FLAG_VARARG; - } - if (method->is_static) { - mi.flags |= METHOD_FLAG_STATIC; - } - for (int i = 0; i < method->argument_count; i++) { - PropertyInfo pi; -#ifdef DEBUG_METHODS_ENABLED - pi.name = method->argument_names[i]; -#else - pi.name = "arg" + itos(i + 1); -#endif - pi.type = method->get_argument_type(i); - if (pi.type == Variant::NIL) { - pi.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } - mi.arguments.push_back(pi); - } - - mi.default_arguments = method->default_arguments; - p_list->push_back(mi); + p_list->push_back(method->get_method_info(E)); } } } @@ -1796,6 +1807,8 @@ static void _register_variant_builtin_methods() { bind_method(Vector2i, aspect, sarray(), varray()); bind_method(Vector2i, max_axis_index, sarray(), varray()); bind_method(Vector2i, min_axis_index, sarray(), varray()); + bind_method(Vector2i, distance_to, sarray("to"), varray()); + bind_method(Vector2i, distance_squared_to, sarray("to"), varray()); bind_method(Vector2i, length, sarray(), varray()); bind_method(Vector2i, length_squared, sarray(), varray()); bind_method(Vector2i, sign, sarray(), varray()); @@ -1886,6 +1899,8 @@ static void _register_variant_builtin_methods() { bind_method(Vector3i, min_axis_index, sarray(), varray()); bind_method(Vector3i, max_axis_index, sarray(), varray()); + bind_method(Vector3i, distance_to, sarray("to"), varray()); + bind_method(Vector3i, distance_squared_to, sarray("to"), varray()); bind_method(Vector3i, length, sarray(), varray()); bind_method(Vector3i, length_squared, sarray(), varray()); bind_method(Vector3i, sign, sarray(), varray()); @@ -1932,6 +1947,8 @@ static void _register_variant_builtin_methods() { bind_method(Vector4i, abs, sarray(), varray()); bind_method(Vector4i, clamp, sarray("min", "max"), varray()); bind_method(Vector4i, snapped, sarray("step"), varray()); + bind_method(Vector4i, distance_to, sarray("to"), varray()); + bind_method(Vector4i, distance_squared_to, sarray("to"), varray()); /* Plane */ @@ -2195,6 +2212,7 @@ static void _register_variant_builtin_methods() { bind_method(Dictionary, values, sarray(), varray()); bind_method(Dictionary, duplicate, sarray("deep"), varray(false)); bind_method(Dictionary, get, sarray("key", "default"), varray(Variant())); + bind_method(Dictionary, get_or_add, sarray("key", "default"), varray(Variant())); bind_method(Dictionary, make_read_only, sarray(), varray()); bind_method(Dictionary, is_read_only, sarray(), varray()); diff --git a/core/variant/variant_callable.cpp b/core/variant/variant_callable.cpp new file mode 100644 index 0000000000..dc31b6d1ac --- /dev/null +++ b/core/variant/variant_callable.cpp @@ -0,0 +1,81 @@ +/**************************************************************************/ +/* variant_callable.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 "variant_callable.h" + +#include "core/templates/hashfuncs.h" + +bool VariantCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { + return p_a->hash() == p_b->hash(); +} + +bool VariantCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { + return p_a->hash() < p_b->hash(); +} + +uint32_t VariantCallable::hash() const { + return h; +} + +String VariantCallable::get_as_text() const { + return vformat("%s::%s (Callable)", Variant::get_type_name(variant.get_type()), method); +} + +CallableCustom::CompareEqualFunc VariantCallable::get_compare_equal_func() const { + return compare_equal; +} + +CallableCustom::CompareLessFunc VariantCallable::get_compare_less_func() const { + return compare_less; +} + +bool VariantCallable::is_valid() const { + return Variant::has_builtin_method(variant.get_type(), method); +} + +StringName VariantCallable::get_method() const { + return method; +} + +ObjectID VariantCallable::get_object() const { + return ObjectID(); +} + +void VariantCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { + Variant v = variant; + v.callp(method, p_arguments, p_argcount, r_return_value, r_call_error); +} + +VariantCallable::VariantCallable(const Variant &p_variant, const StringName &p_method) { + variant = p_variant; + method = p_method; + h = variant.hash(); + h = hash_murmur3_one_64(Variant::get_builtin_method_hash(variant.get_type(), method), h); +} diff --git a/core/variant/variant_callable.h b/core/variant/variant_callable.h new file mode 100644 index 0000000000..3f2b058aaf --- /dev/null +++ b/core/variant/variant_callable.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* variant_callable.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 VARIANT_CALLABLE_H +#define VARIANT_CALLABLE_H + +#include "core/variant/callable.h" +#include "core/variant/variant.h" + +class VariantCallable : public CallableCustom { + Variant variant; + StringName method; + uint32_t h = 0; + + static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); + static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); + +public: + uint32_t hash() const override; + String get_as_text() const override; + CompareEqualFunc get_compare_equal_func() const override; + CompareLessFunc get_compare_less_func() const override; + bool is_valid() const override; + StringName get_method() const override; + ObjectID get_object() const override; + void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; + + VariantCallable(const Variant &p_variant, const StringName &p_method); +}; + +#endif // VARIANT_CALLABLE_H diff --git a/core/variant/variant_internal.h b/core/variant/variant_internal.h index 782053b613..171074188f 100644 --- a/core/variant/variant_internal.h +++ b/core/variant/variant_internal.h @@ -418,7 +418,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); } @@ -1302,192 +1302,192 @@ struct VariantInitializer<Object *> { }; template <class T> -struct VariantZeroAssigner { +struct VariantDefaultInitializer { }; template <> -struct VariantZeroAssigner<bool> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_bool(v) = false; } +struct VariantDefaultInitializer<bool> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_bool(v) = false; } }; template <> -struct VariantZeroAssigner<int64_t> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_int(v) = 0; } +struct VariantDefaultInitializer<int64_t> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_int(v) = 0; } }; template <> -struct VariantZeroAssigner<double> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_float(v) = 0.0; } +struct VariantDefaultInitializer<double> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_float(v) = 0.0; } }; template <> -struct VariantZeroAssigner<float> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_float(v) = 0.0; } +struct VariantDefaultInitializer<float> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_float(v) = 0.0; } }; template <> -struct VariantZeroAssigner<String> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_string(v) = String(); } +struct VariantDefaultInitializer<String> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_string(v) = String(); } }; template <> -struct VariantZeroAssigner<Vector2> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_vector2(v) = Vector2(); } +struct VariantDefaultInitializer<Vector2> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_vector2(v) = Vector2(); } }; template <> -struct VariantZeroAssigner<Vector2i> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_vector2i(v) = Vector2i(); } +struct VariantDefaultInitializer<Vector2i> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_vector2i(v) = Vector2i(); } }; template <> -struct VariantZeroAssigner<Rect2> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_rect2(v) = Rect2(); } +struct VariantDefaultInitializer<Rect2> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_rect2(v) = Rect2(); } }; template <> -struct VariantZeroAssigner<Rect2i> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_rect2i(v) = Rect2i(); } +struct VariantDefaultInitializer<Rect2i> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_rect2i(v) = Rect2i(); } }; template <> -struct VariantZeroAssigner<Vector3> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_vector3(v) = Vector3(); } +struct VariantDefaultInitializer<Vector3> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_vector3(v) = Vector3(); } }; template <> -struct VariantZeroAssigner<Vector3i> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_vector3i(v) = Vector3i(); } +struct VariantDefaultInitializer<Vector3i> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_vector3i(v) = Vector3i(); } }; template <> -struct VariantZeroAssigner<Vector4> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_vector4(v) = Vector4(); } +struct VariantDefaultInitializer<Vector4> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_vector4(v) = Vector4(); } }; template <> -struct VariantZeroAssigner<Vector4i> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_vector4i(v) = Vector4i(); } +struct VariantDefaultInitializer<Vector4i> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_vector4i(v) = Vector4i(); } }; template <> -struct VariantZeroAssigner<Transform2D> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_transform2d(v) = Transform2D(); } +struct VariantDefaultInitializer<Transform2D> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_transform2d(v) = Transform2D(); } }; template <> -struct VariantZeroAssigner<Plane> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_plane(v) = Plane(); } +struct VariantDefaultInitializer<Plane> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_plane(v) = Plane(); } }; template <> -struct VariantZeroAssigner<Quaternion> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_quaternion(v) = Quaternion(); } +struct VariantDefaultInitializer<Quaternion> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_quaternion(v) = Quaternion(); } }; template <> -struct VariantZeroAssigner<AABB> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_aabb(v) = AABB(); } +struct VariantDefaultInitializer<AABB> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_aabb(v) = AABB(); } }; template <> -struct VariantZeroAssigner<Basis> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_basis(v) = Basis(); } +struct VariantDefaultInitializer<Basis> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_basis(v) = Basis(); } }; template <> -struct VariantZeroAssigner<Transform3D> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_transform(v) = Transform3D(); } +struct VariantDefaultInitializer<Transform3D> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_transform(v) = Transform3D(); } }; template <> -struct VariantZeroAssigner<Projection> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_projection(v) = Projection(); } +struct VariantDefaultInitializer<Projection> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_projection(v) = Projection(); } }; template <> -struct VariantZeroAssigner<Color> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_color(v) = Color(); } +struct VariantDefaultInitializer<Color> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_color(v) = Color(); } }; template <> -struct VariantZeroAssigner<StringName> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_string_name(v) = StringName(); } +struct VariantDefaultInitializer<StringName> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_string_name(v) = StringName(); } }; template <> -struct VariantZeroAssigner<NodePath> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_node_path(v) = NodePath(); } +struct VariantDefaultInitializer<NodePath> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_node_path(v) = NodePath(); } }; template <> -struct VariantZeroAssigner<::RID> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_rid(v) = RID(); } +struct VariantDefaultInitializer<::RID> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_rid(v) = RID(); } }; template <> -struct VariantZeroAssigner<Callable> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_callable(v) = Callable(); } +struct VariantDefaultInitializer<Callable> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_callable(v) = Callable(); } }; template <> -struct VariantZeroAssigner<Signal> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_signal(v) = Signal(); } +struct VariantDefaultInitializer<Signal> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_signal(v) = Signal(); } }; template <> -struct VariantZeroAssigner<Dictionary> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_dictionary(v) = Dictionary(); } +struct VariantDefaultInitializer<Dictionary> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_dictionary(v) = Dictionary(); } }; template <> -struct VariantZeroAssigner<Array> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_array(v) = Array(); } +struct VariantDefaultInitializer<Array> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_array(v) = Array(); } }; template <> -struct VariantZeroAssigner<PackedByteArray> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_byte_array(v) = PackedByteArray(); } +struct VariantDefaultInitializer<PackedByteArray> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_byte_array(v) = PackedByteArray(); } }; template <> -struct VariantZeroAssigner<PackedInt32Array> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_int32_array(v) = PackedInt32Array(); } +struct VariantDefaultInitializer<PackedInt32Array> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_int32_array(v) = PackedInt32Array(); } }; template <> -struct VariantZeroAssigner<PackedInt64Array> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_int64_array(v) = PackedInt64Array(); } +struct VariantDefaultInitializer<PackedInt64Array> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_int64_array(v) = PackedInt64Array(); } }; template <> -struct VariantZeroAssigner<PackedFloat32Array> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_float32_array(v) = PackedFloat32Array(); } +struct VariantDefaultInitializer<PackedFloat32Array> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_float32_array(v) = PackedFloat32Array(); } }; template <> -struct VariantZeroAssigner<PackedFloat64Array> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_float64_array(v) = PackedFloat64Array(); } +struct VariantDefaultInitializer<PackedFloat64Array> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_float64_array(v) = PackedFloat64Array(); } }; template <> -struct VariantZeroAssigner<PackedStringArray> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_string_array(v) = PackedStringArray(); } +struct VariantDefaultInitializer<PackedStringArray> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_string_array(v) = PackedStringArray(); } }; template <> -struct VariantZeroAssigner<PackedVector2Array> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_vector2_array(v) = PackedVector2Array(); } +struct VariantDefaultInitializer<PackedVector2Array> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_vector2_array(v) = PackedVector2Array(); } }; template <> -struct VariantZeroAssigner<PackedVector3Array> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_vector3_array(v) = PackedVector3Array(); } +struct VariantDefaultInitializer<PackedVector3Array> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_vector3_array(v) = PackedVector3Array(); } }; template <> -struct VariantZeroAssigner<PackedColorArray> { - static _FORCE_INLINE_ void zero(Variant *v) { *VariantInternal::get_color_array(v) = PackedColorArray(); } +struct VariantDefaultInitializer<PackedColorArray> { + static _FORCE_INLINE_ void init(Variant *v) { *VariantInternal::get_color_array(v) = PackedColorArray(); } }; template <class T> @@ -1504,7 +1504,7 @@ struct VariantTypeChanger { VariantInitializer<T>::init(v); } - VariantZeroAssigner<T>::zero(v); + VariantDefaultInitializer<T>::init(v); } }; diff --git a/core/variant/variant_op.h b/core/variant/variant_op.h index 9e6367ab6d..17ad126891 100644 --- a/core/variant/variant_op.h +++ b/core/variant/variant_op.h @@ -549,14 +549,14 @@ public: class OperatorEvaluatorEqualObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const Object *a = p_left.get_validated_object(); - const Object *b = p_right.get_validated_object(); + const ObjectID &a = VariantInternal::get_object_id(&p_left); + const ObjectID &b = VariantInternal::get_object_id(&p_right); *r_ret = a == b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const Object *a = left->get_validated_object(); - const Object *b = right->get_validated_object(); + const ObjectID &a = VariantInternal::get_object_id(left); + const ObjectID &b = VariantInternal::get_object_id(right); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -568,12 +568,12 @@ public: class OperatorEvaluatorEqualObjectNil { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const Object *a = p_left.get_validated_object(); + const Object *a = p_left.operator Object *(); *r_ret = a == nullptr; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const Object *a = left->get_validated_object(); + const Object *a = left->operator Object *(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a == nullptr; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -585,12 +585,12 @@ public: class OperatorEvaluatorEqualNilObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - const Object *b = p_right.get_validated_object(); + const Object *b = p_right.operator Object *(); *r_ret = nullptr == b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - const Object *b = right->get_validated_object(); + const Object *b = right->operator Object *(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr == b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -620,14 +620,14 @@ public: class OperatorEvaluatorNotEqualObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - Object *a = p_left.get_validated_object(); - Object *b = p_right.get_validated_object(); + const ObjectID &a = VariantInternal::get_object_id(&p_left); + const ObjectID &b = VariantInternal::get_object_id(&p_right); *r_ret = a != b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - Object *a = left->get_validated_object(); - Object *b = right->get_validated_object(); + const ObjectID &a = VariantInternal::get_object_id(left); + const ObjectID &b = VariantInternal::get_object_id(right); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -639,12 +639,12 @@ public: class OperatorEvaluatorNotEqualObjectNil { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - Object *a = p_left.get_validated_object(); + Object *a = p_left.operator Object *(); *r_ret = a != nullptr; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - Object *a = left->get_validated_object(); + Object *a = left->operator Object *(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = a != nullptr; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { @@ -656,12 +656,12 @@ public: class OperatorEvaluatorNotEqualNilObject { public: static void evaluate(const Variant &p_left, const Variant &p_right, Variant *r_ret, bool &r_valid) { - Object *b = p_right.get_validated_object(); + Object *b = p_right.operator Object *(); *r_ret = nullptr != b; r_valid = true; } static inline void validated_evaluate(const Variant *left, const Variant *right, Variant *r_ret) { - Object *b = right->get_validated_object(); + Object *b = right->operator Object *(); *VariantGetInternalPtr<bool>::get_ptr(r_ret) = nullptr != b; } static void ptr_evaluate(const void *left, const void *right, void *r_ret) { diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index fea1622222..e35751fd61 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -1026,7 +1026,10 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, Ref<Resource> res; Error err = p_res_parser->ext_func(p_res_parser->userdata, p_stream, res, line, r_err_str); if (err) { - return err; + // If the file is missing, the error can be ignored. + if (err != ERR_FILE_NOT_FOUND && err != ERR_CANT_OPEN) { + return err; + } } value = res; @@ -1075,7 +1078,7 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, return ERR_PARSE_ERROR; } - static HashMap<StringName, Variant::Type> builtin_types; + static HashMap<String, Variant::Type> builtin_types; if (builtin_types.is_empty()) { for (int i = 1; i < Variant::VARIANT_MAX; i++) { builtin_types[Variant::get_type_name((Variant::Type)i)] = (Variant::Type)i; diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index 05f7abf32c..b0e0a885f4 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -30,6 +30,8 @@ #include "variant_setget.h" +#include "variant_callable.h" + struct VariantSetterGetterInfo { void (*setter)(Variant *base, const Variant *value, bool &valid); void (*getter)(const Variant *base, Variant *value); @@ -264,42 +266,45 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool } Variant Variant::get_named(const StringName &p_member, bool &r_valid) const { - Variant ret; uint32_t s = variant_setters_getters[type].size(); if (s) { for (uint32_t i = 0; i < s; i++) { if (variant_setters_getters_names[type][i] == p_member) { + Variant ret; variant_setters_getters[type][i].getter(this, &ret); r_valid = true; return ret; } } + } - r_valid = false; - - } else if (type == Variant::OBJECT) { - Object *obj = get_validated_object(); - if (!obj) { - r_valid = false; - return "Instance base is null."; - } else { - return obj->get(p_member, &r_valid); - } - } else if (type == Variant::DICTIONARY) { - const Variant *v = VariantGetInternalPtr<Dictionary>::get_ptr(this)->getptr(p_member); - if (v) { - r_valid = true; - - return *v; - } else { - r_valid = false; - } - - } else { - r_valid = false; + switch (type) { + case Variant::OBJECT: { + Object *obj = get_validated_object(); + if (!obj) { + r_valid = false; + return "Instance base is null."; + } else { + return obj->get(p_member, &r_valid); + } + } break; + case Variant::DICTIONARY: { + const Variant *v = VariantGetInternalPtr<Dictionary>::get_ptr(this)->getptr(p_member); + if (v) { + r_valid = true; + return *v; + } + } break; + default: { + if (Variant::has_builtin_method(type, p_member)) { + r_valid = true; + return Callable(memnew(VariantCallable(*this, p_member))); + } + } break; } - return ret; + r_valid = false; + return Variant(); } /**** INDEXED SETTERS AND GETTERS ****/ diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 6f334d1859..cc48394b64 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -121,16 +121,27 @@ Variant VariantUtilityFunctions::floor(Variant x, Callable::CallError &r_error) case Variant::VECTOR2: { return VariantInternalAccessor<Vector2>::get(&x).floor(); } break; + case Variant::VECTOR2I: { + return VariantInternalAccessor<Vector2i>::get(&x); + } break; case Variant::VECTOR3: { return VariantInternalAccessor<Vector3>::get(&x).floor(); } break; + case Variant::VECTOR3I: { + return VariantInternalAccessor<Vector3i>::get(&x); + } break; case Variant::VECTOR4: { return VariantInternalAccessor<Vector4>::get(&x).floor(); } break; + case Variant::VECTOR4I: { + return VariantInternalAccessor<Vector4i>::get(&x); + } break; default: { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + return R"(Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i".)"; + } break; } } @@ -154,16 +165,27 @@ Variant VariantUtilityFunctions::ceil(Variant x, Callable::CallError &r_error) { case Variant::VECTOR2: { return VariantInternalAccessor<Vector2>::get(&x).ceil(); } break; + case Variant::VECTOR2I: { + return VariantInternalAccessor<Vector2i>::get(&x); + } break; case Variant::VECTOR3: { return VariantInternalAccessor<Vector3>::get(&x).ceil(); } break; + case Variant::VECTOR3I: { + return VariantInternalAccessor<Vector3i>::get(&x); + } break; case Variant::VECTOR4: { return VariantInternalAccessor<Vector4>::get(&x).ceil(); } break; + case Variant::VECTOR4I: { + return VariantInternalAccessor<Vector4i>::get(&x); + } break; default: { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + return R"(Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i".)"; + } break; } } @@ -187,16 +209,27 @@ Variant VariantUtilityFunctions::round(Variant x, Callable::CallError &r_error) case Variant::VECTOR2: { return VariantInternalAccessor<Vector2>::get(&x).round(); } break; + case Variant::VECTOR2I: { + return VariantInternalAccessor<Vector2i>::get(&x); + } break; case Variant::VECTOR3: { return VariantInternalAccessor<Vector3>::get(&x).round(); } break; + case Variant::VECTOR3I: { + return VariantInternalAccessor<Vector3i>::get(&x); + } break; case Variant::VECTOR4: { return VariantInternalAccessor<Vector4>::get(&x).round(); } break; + case Variant::VECTOR4I: { + return VariantInternalAccessor<Vector4i>::get(&x); + } break; default: { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + return R"(Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i".)"; + } break; } } @@ -236,9 +269,11 @@ Variant VariantUtilityFunctions::abs(const Variant &x, Callable::CallError &r_er return VariantInternalAccessor<Vector4i>::get(&x).abs(); } break; default: { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + return R"(Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i".)"; + } break; } } @@ -278,9 +313,11 @@ Variant VariantUtilityFunctions::sign(const Variant &x, Callable::CallError &r_e return VariantInternalAccessor<Vector4i>::get(&x).sign(); } break; default: { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + return R"(Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i".)"; + } break; } } @@ -333,14 +370,40 @@ int VariantUtilityFunctions::step_decimals(float step) { } Variant VariantUtilityFunctions::snapped(const Variant &x, const Variant &step, Callable::CallError &r_error) { - r_error.error = Callable::CallError::CALL_OK; - if (x.get_type() != step.get_type() && !((x.get_type() == Variant::INT && step.get_type() == Variant::FLOAT) || (x.get_type() == Variant::FLOAT && step.get_type() == Variant::INT))) { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; - r_error.argument = 1; - r_error.expected = x.get_type(); - return Variant(); + switch (x.get_type()) { + case Variant::INT: + case Variant::FLOAT: + case Variant::VECTOR2: + case Variant::VECTOR2I: + case Variant::VECTOR3: + case Variant::VECTOR3I: + case Variant::VECTOR4: + case Variant::VECTOR4I: + break; + default: + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + return R"(Argument "x" must be "int", "float", "Vector2", "Vector2i", "Vector3", "Vector3i", "Vector4", or "Vector4i".)"; + } + + if (x.get_type() != step.get_type()) { + if (x.get_type() == Variant::INT || x.get_type() == Variant::FLOAT) { + if (step.get_type() != Variant::INT && step.get_type() != Variant::FLOAT) { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = Variant::NIL; + return R"(Argument "step" must be "int" or "float".)"; + } + } else { + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 1; + r_error.expected = x.get_type(); + return Variant(); + } } + r_error.error = Callable::CallError::CALL_OK; switch (step.get_type()) { case Variant::INT: { return snappedi(x, VariantInternalAccessor<int64_t>::get(&step)); @@ -367,9 +430,8 @@ Variant VariantUtilityFunctions::snapped(const Variant &x, const Variant &step, return VariantInternalAccessor<Vector4i>::get(&x).snapped(VariantInternalAccessor<Vector4i>::get(&step)); } break; default: { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } + return Variant(); // Already handled. + } break; } } @@ -382,7 +444,23 @@ int64_t VariantUtilityFunctions::snappedi(double x, int64_t step) { } Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, double weight, Callable::CallError &r_error) { - r_error.error = Callable::CallError::CALL_OK; + switch (from.get_type()) { + case Variant::INT: + case Variant::FLOAT: + case Variant::VECTOR2: + case Variant::VECTOR3: + case Variant::VECTOR4: + case Variant::QUATERNION: + case Variant::BASIS: + case Variant::COLOR: + break; + default: + r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; + r_error.argument = 0; + r_error.expected = Variant::NIL; + return R"(Argument "from" must be "int", "float", "Vector2", "Vector3", "Vector4", "Quaternion", "Basis, or "Color".)"; + } + if (from.get_type() != to.get_type()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_ARGUMENT; r_error.argument = 1; @@ -390,6 +468,7 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do return Variant(); } + r_error.error = Callable::CallError::CALL_OK; switch (from.get_type()) { case Variant::INT: { return lerpf(VariantInternalAccessor<int64_t>::get(&from), to, weight); @@ -416,9 +495,8 @@ Variant VariantUtilityFunctions::lerp(const Variant &from, const Variant &to, do return VariantInternalAccessor<Color>::get(&from).lerp(VariantInternalAccessor<Color>::get(&to), weight); } break; default: { - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; - return Variant(); - } + return Variant(); // Already handled. + } break; } } |