diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 500eb84d08bc..e79817a2e76f 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -85,6 +85,11 @@ Copyright: 2001, Robert Penner 2007-2014, Juan Linietsky, Ariel Manzur License: Expat +Files: ./servers/physics_2d/godot_joints_2d.cpp +Comment: Chipmunk2D Joint Constraints +Copyright: 2007, Scott Lembcke +License: Expat + Files: ./servers/physics_3d/collision_solver_3d_sat.cpp Comment: Open Dynamics Engine Copyright: 2001-2003, Russell L. Smith, Alen Ladavac, Nguyen Binh @@ -141,6 +146,11 @@ Comment: AMD FidelityFX Super Resolution Copyright: 2021, Advanced Micro Devices, Inc. License: Expat +Files: ./thirdparty/amd-fsr2/ +Comment: AMD FidelityFX Super Resolution 2 +Copyright: 2022-2023, Advanced Micro Devices, Inc. +License: Expat + Files: ./thirdparty/angle/ Comment: ANGLE Copyright: 2018, The ANGLE Project Authors. @@ -166,6 +176,11 @@ Comment: CA certificates Copyright: Mozilla Contributors License: MPL-2.0 +Files: ./thirdparty/clipper2/ +Comment: Clipper2 +Copyright: 2010-2013, Angus Johnson +License: BSL-1.0 + Files: ./thirdparty/cvtt/ Comment: Convection Texture Tools Stand-Alone Kernels Copyright: 2018, Eric Lasota diff --git a/SConstruct b/SConstruct index d01062db8d3c..a06dfc1bde5e 100644 --- a/SConstruct +++ b/SConstruct @@ -226,6 +226,7 @@ opts.Add("scu_limit", "Max includes per SCU file when using scu_build (determine # Thirdparty libraries opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True)) opts.Add(BoolVariable("builtin_certs", "Use the built-in SSL certificates bundles", True)) +opts.Add(BoolVariable("builtin_clipper2", "Use the built-in Clipper2 library", True)) opts.Add(BoolVariable("builtin_embree", "Use the built-in Embree library", True)) opts.Add(BoolVariable("builtin_enet", "Use the built-in ENet library", True)) opts.Add(BoolVariable("builtin_freetype", "Use the built-in FreeType library", True)) diff --git a/core/SCsub b/core/SCsub index ab78eeedc714..3b1a7ca79a1d 100644 --- a/core/SCsub +++ b/core/SCsub @@ -89,6 +89,24 @@ if env["brotli"] and env["builtin_brotli"]: env_thirdparty.add_source_files(thirdparty_obj, thirdparty_brotli_sources) +# Clipper2 Thirdparty source files used for polygon and polyline boolean operations. +if env["builtin_clipper2"]: + thirdparty_clipper_dir = "#thirdparty/clipper2/" + thirdparty_clipper_sources = [ + "src/clipper.engine.cpp", + "src/clipper.offset.cpp", + "src/clipper.rectclip.cpp", + ] + thirdparty_clipper_sources = [thirdparty_clipper_dir + file for file in thirdparty_clipper_sources] + + env_thirdparty.Prepend(CPPPATH=[thirdparty_clipper_dir + "include"]) + env.Prepend(CPPPATH=[thirdparty_clipper_dir + "include"]) + + env_thirdparty.Append(CPPDEFINES=["CLIPPER2_ENABLED"]) + env.Append(CPPDEFINES=["CLIPPER2_ENABLED"]) + + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_clipper_sources) + # Zlib library, can be unbundled if env["builtin_zlib"]: thirdparty_zlib_dir = "#thirdparty/zlib/" diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 17d3bdb744de..0e27d556ec94 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -260,14 +260,21 @@ bool Engine::is_printing_error_messages() const { } void Engine::add_singleton(const Singleton &p_singleton) { - ERR_FAIL_COND_MSG(singleton_ptrs.has(p_singleton.name), "Can't register singleton that already exists: " + String(p_singleton.name)); + ERR_FAIL_COND_MSG(singleton_ptrs.has(p_singleton.name), vformat("Can't register singleton '%s' because it already exists.", p_singleton.name)); singletons.push_back(p_singleton); singleton_ptrs[p_singleton.name] = p_singleton.ptr; } Object *Engine::get_singleton_object(const StringName &p_name) const { HashMap::ConstIterator E = singleton_ptrs.find(p_name); - ERR_FAIL_COND_V_MSG(!E, nullptr, "Failed to retrieve non-existent singleton '" + String(p_name) + "'."); + ERR_FAIL_COND_V_MSG(!E, nullptr, vformat("Failed to retrieve non-existent singleton '%s'.", p_name)); + +#ifdef TOOLS_ENABLED + if (!is_editor_hint() && is_singleton_editor_only(p_name)) { + ERR_FAIL_V_MSG(nullptr, vformat("Can't retrieve singleton '%s' outside of editor.", p_name)); + } +#endif + return E->value; } @@ -282,6 +289,19 @@ bool Engine::is_singleton_user_created(const StringName &p_name) const { return false; } + +bool Engine::is_singleton_editor_only(const StringName &p_name) const { + ERR_FAIL_COND_V(!singleton_ptrs.has(p_name), false); + + for (const Singleton &E : singletons) { + if (E.name == p_name && E.editor_only) { + return true; + } + } + + return false; +} + void Engine::remove_singleton(const StringName &p_name) { ERR_FAIL_COND(!singleton_ptrs.has(p_name)); @@ -300,6 +320,12 @@ bool Engine::has_singleton(const StringName &p_name) const { void Engine::get_singletons(List *p_singletons) { for (const Singleton &E : singletons) { +#ifdef TOOLS_ENABLED + if (!is_editor_hint() && E.editor_only) { + continue; + } +#endif + p_singletons->push_back(E); } } diff --git a/core/config/engine.h b/core/config/engine.h index ff88fbc787cf..b64309a9e838 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -44,8 +44,11 @@ class Engine { struct Singleton { StringName name; Object *ptr = nullptr; - StringName class_name; //used for binding generation hinting + StringName class_name; // Used for binding generation hinting. + // Singleton scope flags. bool user_created = false; + bool editor_only = false; + Singleton(const StringName &p_name = StringName(), Object *p_ptr = nullptr, const StringName &p_class_name = StringName()); }; @@ -79,6 +82,7 @@ class Engine { bool editor_hint = false; bool project_manager_hint = false; + bool extension_reloading = false; static Engine *singleton; @@ -129,6 +133,7 @@ class Engine { Object *get_singleton_object(const StringName &p_name) const; void remove_singleton(const StringName &p_name); bool is_singleton_user_created(const StringName &p_name) const; + bool is_singleton_editor_only(const StringName &p_name) const; #ifdef TOOLS_ENABLED _FORCE_INLINE_ void set_editor_hint(bool p_enabled) { editor_hint = p_enabled; } @@ -136,12 +141,18 @@ class Engine { _FORCE_INLINE_ void set_project_manager_hint(bool p_enabled) { project_manager_hint = p_enabled; } _FORCE_INLINE_ bool is_project_manager_hint() const { return project_manager_hint; } + + _FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) { extension_reloading = p_enabled; } + _FORCE_INLINE_ bool is_extension_reloading_enabled() const { return extension_reloading; } #else _FORCE_INLINE_ void set_editor_hint(bool p_enabled) {} _FORCE_INLINE_ bool is_editor_hint() const { return false; } _FORCE_INLINE_ void set_project_manager_hint(bool p_enabled) {} _FORCE_INLINE_ bool is_project_manager_hint() const { return false; } + + _FORCE_INLINE_ void set_extension_reloading_enabled(bool p_enabled) {} + _FORCE_INLINE_ bool is_extension_reloading_enabled() const { return false; } #endif Dictionary get_version_info() const; diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 5ba800ebfeeb..30499942407f 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -962,6 +962,7 @@ Error ProjectSettings::_save_custom_bnd(const String &p_file) { // add other par #ifdef TOOLS_ENABLED bool _csproj_exists(String p_root_dir) { Ref dir = DirAccess::open(p_root_dir); + ERR_FAIL_COND_V(dir.is_null(), false); dir->list_dir_begin(); String file_name = dir->_get_next(); diff --git a/core/extension/extension_api_dump.cpp b/core/extension/extension_api_dump.cpp index f11a4bc9a4c0..58cb51245a23 100644 --- a/core/extension/extension_api_dump.cpp +++ b/core/extension/extension_api_dump.cpp @@ -39,6 +39,7 @@ #include "core/version.h" #ifdef TOOLS_ENABLED +#include "editor/editor_help.h" static String get_builtin_or_variant_type_name(const Variant::Type p_type) { if (p_type == Variant::NIL) { @@ -88,7 +89,16 @@ static String get_type_meta_name(const GodotTypeInfo::Metadata metadata) { return argmeta[metadata]; } -Dictionary GDExtensionAPIDump::generate_extension_api() { +static String fix_doc_description(const String &p_bbcode) { + // Based on what EditorHelp does. + + return p_bbcode.dedent() + .replace("\t", "") + .replace("\r", "") + .strip_edges(); +} + +Dictionary GDExtensionAPIDump::generate_extension_api(bool p_include_docs) { Dictionary api_dump; { @@ -460,12 +470,22 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { api_dump["builtin_class_member_offsets"] = core_type_member_offsets; } + if (p_include_docs) { + EditorHelp::generate_doc(false); + } + { // Global enums and constants. Array constants; HashMap>> enum_list; HashMap enum_is_bitfield; + const DocData::ClassDoc *global_scope_doc = nullptr; + if (p_include_docs) { + global_scope_doc = EditorHelp::get_doc_data()->class_list.getptr("@GlobalScope"); + CRASH_COND_MSG(!global_scope_doc, "Could not find '@GlobalScope' in DocData."); + } + for (int i = 0; i < CoreConstants::get_global_constant_count(); i++) { int64_t value = CoreConstants::get_global_constant_value(i); String enum_name = CoreConstants::get_global_constant_enum(i); @@ -479,6 +499,14 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d["name"] = name; d["value"] = value; d["is_bitfield"] = bitfield; + 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); + break; + } + } + } constants.push_back(d); } } @@ -490,11 +518,25 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Dictionary d1; d1["name"] = E.key; d1["is_bitfield"] = enum_is_bitfield[E.key]; + 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); + } + } Array values; for (const Pair &F : E.value) { Dictionary d2; d2["name"] = F.first; d2["value"] = F.second; + 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); + break; + } + } + } values.push_back(d2); } d1["values"] = values; @@ -509,6 +551,12 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { List utility_func_names; Variant::get_utility_function_list(&utility_func_names); + const DocData::ClassDoc *global_scope_doc = nullptr; + if (p_include_docs) { + global_scope_doc = EditorHelp::get_doc_data()->class_list.getptr("@GlobalScope"); + CRASH_COND_MSG(!global_scope_doc, "Could not find '@GlobalScope' in DocData."); + } + for (const StringName &name : utility_func_names) { Dictionary func; func["name"] = String(name); @@ -545,6 +593,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { func["arguments"] = arguments; } + 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); + break; + } + } + } + utility_funcs.push_back(func); } @@ -571,6 +628,12 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d["is_keyed"] = Variant::is_keyed(type); + DocData::ClassDoc *builtin_doc = nullptr; + if (p_include_docs && d["name"] != "Nil") { + builtin_doc = EditorHelp::get_doc_data()->class_list.getptr(d["name"]); + CRASH_COND_MSG(!builtin_doc, vformat("Could not find '%s' in DocData.", d["name"])); + } + { //members Array members; @@ -581,6 +644,14 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Dictionary d2; d2["name"] = String(member_name); d2["type"] = get_builtin_or_variant_type_name(Variant::get_member_type(type, member_name)); + 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); + break; + } + } + } members.push_back(d2); } if (members.size()) { @@ -599,6 +670,14 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Variant constant = Variant::get_constant_value(type, constant_name); d2["type"] = get_builtin_or_variant_type_name(constant.get_type()); d2["value"] = constant.get_construct_string(); + 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); + break; + } + } + } constants.push_back(d2); } if (constants.size()) { @@ -624,9 +703,24 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Dictionary values_dict; values_dict["name"] = String(enumeration); values_dict["value"] = Variant::get_enum_value(type, enum_name, enumeration); + 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); + break; + } + } + } values.push_back(values_dict); } + 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); + } + } + if (values.size()) { enum_dict["values"] = values; } @@ -646,11 +740,22 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Variant::Type rt = Variant::get_operator_return_type(Variant::Operator(k), type, Variant::Type(j)); if (rt != Variant::NIL) { Dictionary d2; - d2["name"] = Variant::get_operator_name(Variant::Operator(k)); + 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)); } 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); + break; + } + } + } + operators.push_back(d2); } } @@ -697,6 +802,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["arguments"] = arguments; } + 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); + break; + } + } + } + methods.push_back(d2); } if (methods.size()) { @@ -722,6 +836,28 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { if (arguments.size()) { d2["arguments"] = arguments; } + + if (p_include_docs && builtin_doc) { + for (const DocData::MethodDoc &constructor_doc : builtin_doc->constructors) { + if (constructor_doc.arguments.size() != argcount) { + continue; + } + bool constructor_found = true; + for (int k = 0; k < argcount; k++) { + const DocData::ArgumentDoc &argument_doc = constructor_doc.arguments[k]; + const Dictionary &argument_dict = arguments[k]; + const String &argument_string = argument_dict["type"]; + if (argument_doc.type != argument_string) { + constructor_found = false; + break; + } + } + if (constructor_found) { + d2["documentation"] = fix_doc_description(constructor_doc.description); + } + } + } + constructors.push_back(d2); } @@ -734,6 +870,10 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d["has_destructor"] = Variant::has_destructor(type); } + if (p_include_docs && builtin_doc != nullptr) { + d["documentation"] = fix_doc_description(builtin_doc->description); + } + builtins.push_back(d); } @@ -763,6 +903,12 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d["inherits"] = String(parent_class); } + DocData::ClassDoc *class_doc = nullptr; + if (p_include_docs) { + class_doc = EditorHelp::get_doc_data()->class_list.getptr(class_name); + CRASH_COND_MSG(!class_doc, vformat("Could not find '%s' in DocData.", class_name)); + } + { ClassDB::APIType api = ClassDB::get_api_type(class_name); static const char *api_type[5] = { "core", "editor", "extension", "editor_extension" }; @@ -784,6 +930,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["name"] = String(F); d2["value"] = ClassDB::get_integer_constant(class_name, F); + 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); + break; + } + } + } + constants.push_back(d2); } @@ -808,11 +963,28 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { Dictionary d3; d3["name"] = String(G->get()); d3["value"] = ClassDB::get_integer_constant(class_name, G->get()); + + 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); + break; + } + } + } + values.push_back(d3); } d2["values"] = values; + 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); + } + } + enums.push_back(d2); } @@ -864,6 +1036,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["arguments"] = arguments; } + 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); + break; + } + } + } + methods.push_back(d2); } else if (F.name.begins_with("_")) { @@ -932,6 +1113,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["arguments"] = arguments; } + 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); + break; + } + } + } + methods.push_back(d2); } } @@ -966,6 +1156,15 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { d2["arguments"] = arguments; } + 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); + break; + } + } + } + signals.push_back(d2); } @@ -1005,6 +1204,16 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { if (index != -1) { d2["index"] = index; } + + 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); + break; + } + } + } + properties.push_back(d2); } @@ -1013,6 +1222,10 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { } } + if (p_include_docs && class_doc != nullptr) { + d["documentation"] = fix_doc_description(class_doc->description); + } + classes.push_back(d); } @@ -1065,8 +1278,8 @@ Dictionary GDExtensionAPIDump::generate_extension_api() { return api_dump; } -void GDExtensionAPIDump::generate_extension_json_file(const String &p_path) { - Dictionary api = generate_extension_api(); +void GDExtensionAPIDump::generate_extension_json_file(const String &p_path, bool p_include_docs) { + Dictionary api = generate_extension_api(p_include_docs); Ref json; json.instantiate(); diff --git a/core/extension/extension_api_dump.h b/core/extension/extension_api_dump.h index 11ea2cf92300..204a115f841e 100644 --- a/core/extension/extension_api_dump.h +++ b/core/extension/extension_api_dump.h @@ -37,8 +37,8 @@ class GDExtensionAPIDump { public: - static Dictionary generate_extension_api(); - static void generate_extension_json_file(const String &p_path); + static Dictionary generate_extension_api(bool p_include_docs = false); + static void generate_extension_json_file(const String &p_path, bool p_include_docs = false); static Error validate_extension_json_file(const String &p_path); }; #endif diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index bffa0e251f99..28cad12ec931 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -162,6 +162,14 @@ class GDExtensionMethodBind : public MethodBind { List arguments_info; List arguments_metadata; +#ifdef TOOLS_ENABLED + friend class GDExtension; + + StringName name; + bool is_reloading = false; + bool valid = true; +#endif + protected: virtual Variant::Type _gen_argument_type(int p_arg) const override { if (p_arg < 0) { @@ -179,6 +187,10 @@ class GDExtensionMethodBind : public MethodBind { } public: +#ifdef TOOLS_ENABLED + virtual bool is_valid() const override { return valid; } +#endif + #ifdef DEBUG_METHODS_ENABLED virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const override { if (p_arg < 0) { @@ -190,6 +202,9 @@ class GDExtensionMethodBind : public MethodBind { #endif virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override { +#ifdef TOOLS_ENABLED + ERR_FAIL_COND_V_MSG(!valid, Variant(), vformat("Cannot call invalid GDExtension method bind '%s'. It's probably cached - you may need to restart Godot.", name)); +#endif Variant ret; GDExtensionClassInstancePtr extension_instance = is_static() ? nullptr : p_object->_get_extension_instance(); GDExtensionCallError ce{ GDEXTENSION_CALL_OK, 0, 0 }; @@ -200,6 +215,9 @@ class GDExtensionMethodBind : public MethodBind { return ret; } virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { +#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."); GDExtensionClassInstancePtr extension_instance = is_static() ? nullptr : p_object->_get_extension_instance(); @@ -234,6 +252,9 @@ class GDExtensionMethodBind : public MethodBind { } virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { +#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, "Vararg methods don't have ptrcall support. This is most likely an engine bug."); GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance(); ptrcall_func(method_userdata, extension_instance, reinterpret_cast(p_args), (GDExtensionTypePtr)r_ret); @@ -243,7 +264,43 @@ class GDExtensionMethodBind : public MethodBind { return false; } - explicit GDExtensionMethodBind(const GDExtensionClassMethodInfo *p_method_info) { +#ifdef TOOLS_ENABLED + bool try_update(const GDExtensionClassMethodInfo *p_method_info) { + if (is_static() != (bool)(p_method_info->method_flags & GDEXTENSION_METHOD_FLAG_STATIC)) { + return false; + } + + if (vararg != (bool)(p_method_info->method_flags & GDEXTENSION_METHOD_FLAG_VARARG)) { + return false; + } + + if (has_return() != (bool)p_method_info->has_return_value) { + return false; + } + + if (has_return() && return_value_info.type != (Variant::Type)p_method_info->return_value_info->type) { + return false; + } + + if (argument_count != p_method_info->argument_count) { + return false; + } + + for (uint32_t i = 0; i < p_method_info->argument_count; i++) { + if (arguments_info[i].type != (Variant::Type)p_method_info->arguments_info[i].type) { + return false; + } + } + + update(p_method_info); + return true; + } +#endif + + void update(const GDExtensionClassMethodInfo *p_method_info) { +#ifdef TOOLS_ENABLED + name = *reinterpret_cast(p_method_info->name); +#endif method_userdata = p_method_info->method_userdata; call_func = p_method_info->call_func; validated_call_func = nullptr; @@ -255,6 +312,8 @@ class GDExtensionMethodBind : public MethodBind { return_value_metadata = GodotTypeInfo::Metadata(p_method_info->return_value_metadata); } + arguments_info.clear(); + arguments_metadata.clear(); for (uint32_t i = 0; i < p_method_info->argument_count; i++) { arguments_info.push_back(PropertyInfo(p_method_info->arguments_info[i])); arguments_metadata.push_back(GodotTypeInfo::Metadata(p_method_info->arguments_metadata[i])); @@ -279,6 +338,10 @@ class GDExtensionMethodBind : public MethodBind { set_default_arguments(defargs); } + + explicit GDExtensionMethodBind(const GDExtensionClassMethodInfo *p_method_info) { + update(p_method_info); + } }; #ifndef DISABLE_DEPRECATED @@ -300,6 +363,7 @@ void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func; p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */ p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ + nullptr, // GDExtensionClassRecreateInstance recreate_instance_func; p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; nullptr, // GDExtensionClassGetVirtualCallData get_virtual_call_data_func; nullptr, // GDExtensionClassCallVirtualWithData call_virtual_func; @@ -341,15 +405,33 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr ERR_FAIL_MSG("Attempt to register an extension class '" + String(class_name) + "' using non-existing parent class '" + String(parent_class_name) + "'"); } +#ifdef TOOLS_ENABLED + Extension *extension = nullptr; + if (self->is_reloading && self->extension_classes.has(class_name)) { + extension = &self->extension_classes[class_name]; + if (!parent_extension && parent_class_name != extension->gdextension.parent_class_name) { + ERR_FAIL_MSG(vformat("GDExtension class '%s' attempt to change parent type from '%s' to '%s' on hot reload. Restart Godot for this change to take effect.", class_name, extension->gdextension.parent_class_name, parent_class_name)); + } + extension->is_reloading = false; + } else { + self->extension_classes[class_name] = Extension(); + extension = &self->extension_classes[class_name]; + } +#else self->extension_classes[class_name] = Extension(); - Extension *extension = &self->extension_classes[class_name]; +#endif if (parent_extension) { extension->gdextension.parent = &parent_extension->gdextension; parent_extension->gdextension.children.push_back(&extension->gdextension); } + if (self->reloadable && p_extension_funcs->recreate_instance_func == nullptr) { + ERR_PRINT(vformat("Extension marked as reloadable, but attempted to register class '%s' which doesn't support reloading. Perhaps your language binding don't support it? Reloading disabled for this extension.", class_name)); + self->reloadable = false; + } + extension->gdextension.library = self; extension->gdextension.parent_class_name = parent_class_name; extension->gdextension.class_name = class_name; @@ -376,11 +458,25 @@ void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr extension->gdextension.class_userdata = p_extension_funcs->class_userdata; extension->gdextension.create_instance = p_extension_funcs->create_instance_func; extension->gdextension.free_instance = p_extension_funcs->free_instance_func; + extension->gdextension.recreate_instance = p_extension_funcs->recreate_instance_func; extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func; extension->gdextension.get_virtual_call_data = p_extension_funcs->get_virtual_call_data_func; extension->gdextension.call_virtual_with_data = p_extension_funcs->call_virtual_with_data_func; extension->gdextension.get_rid = p_extension_funcs->get_rid_func; + extension->gdextension.reloadable = self->reloadable; +#ifdef TOOLS_ENABLED + if (extension->gdextension.reloadable) { + extension->gdextension.tracking_userdata = extension; + extension->gdextension.track_instance = &GDExtension::_track_instance; + extension->gdextension.untrack_instance = &GDExtension::_untrack_instance; + } else { + extension->gdextension.tracking_userdata = nullptr; + extension->gdextension.track_instance = nullptr; + extension->gdextension.untrack_instance = nullptr; + } +#endif + ClassDB::register_extension_class(&extension->gdextension); } @@ -391,10 +487,39 @@ void GDExtension::_register_extension_class_method(GDExtensionClassLibraryPtr p_ StringName method_name = *reinterpret_cast(p_method_info->name); ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension method '" + String(method_name) + "' for unexisting class '" + class_name + "'."); - //Extension *extension = &self->extension_classes[class_name]; +#ifdef TOOLS_ENABLED + Extension *extension = &self->extension_classes[class_name]; + GDExtensionMethodBind *method = nullptr; + // If the extension is still marked as reloading, that means it failed to register again. + if (extension->is_reloading) { + return; + } + + if (self->is_reloading && extension->methods.has(method_name)) { + method = extension->methods[method_name]; + + // Try to update the method bind. If it doesn't work (because it's incompatible) then + // mark as invalid and create a new one. + if (!method->is_reloading || !method->try_update(p_method_info)) { + method->valid = false; + self->invalid_methods.push_back(method); + + method = nullptr; + } + } + + if (method == nullptr) { + method = memnew(GDExtensionMethodBind(p_method_info)); + method->set_instance_class(class_name); + extension->methods[method_name] = method; + } else { + method->is_reloading = false; + } +#else GDExtensionMethodBind *method = memnew(GDExtensionMethodBind(p_method_info)); method->set_instance_class(class_name); +#endif ClassDB::bind_method_custom(class_name, method); } @@ -406,6 +531,14 @@ void GDExtension::_register_extension_class_integer_constant(GDExtensionClassLib StringName constant_name = *reinterpret_cast(p_constant_name); ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension constant '" + constant_name + "' for unexisting class '" + class_name + "'."); +#ifdef TOOLS_ENABLED + // If the extension is still marked as reloading, that means it failed to register again. + Extension *extension = &self->extension_classes[class_name]; + if (extension->is_reloading) { + return; + } +#endif + ClassDB::bind_integer_constant(class_name, enum_name, constant_name, p_constant_value, p_is_bitfield); } @@ -422,6 +555,14 @@ void GDExtension::_register_extension_class_property_indexed(GDExtensionClassLib String property_name = *reinterpret_cast(p_info->name); ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property '" + property_name + "' for unexisting class '" + class_name + "'."); +#ifdef TOOLS_ENABLED + // If the extension is still marked as reloading, that means it failed to register again. + Extension *extension = &self->extension_classes[class_name]; + if (extension->is_reloading) { + return; + } +#endif + PropertyInfo pinfo(*p_info); ClassDB::add_property(class_name, pinfo, setter, getter, p_index); @@ -435,6 +576,14 @@ void GDExtension::_register_extension_class_property_group(GDExtensionClassLibra String prefix = *reinterpret_cast(p_prefix); ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property group '" + group_name + "' for unexisting class '" + class_name + "'."); +#ifdef TOOLS_ENABLED + // If the extension is still marked as reloading, that means it failed to register again. + Extension *extension = &self->extension_classes[class_name]; + if (extension->is_reloading) { + return; + } +#endif + ClassDB::add_property_group(class_name, group_name, prefix); } @@ -446,6 +595,14 @@ void GDExtension::_register_extension_class_property_subgroup(GDExtensionClassLi String prefix = *reinterpret_cast(p_prefix); ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property subgroup '" + subgroup_name + "' for unexisting class '" + class_name + "'."); +#ifdef TOOLS_ENABLED + // If the extension is still marked as reloading, that means it failed to register again. + Extension *extension = &self->extension_classes[class_name]; + if (extension->is_reloading) { + return; + } +#endif + ClassDB::add_property_subgroup(class_name, subgroup_name, prefix); } @@ -456,6 +613,14 @@ void GDExtension::_register_extension_class_signal(GDExtensionClassLibraryPtr p_ StringName signal_name = *reinterpret_cast(p_signal_name); ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class signal '" + signal_name + "' for unexisting class '" + class_name + "'."); +#ifdef TOOLS_ENABLED + // If the extension is still marked as reloading, that means it failed to register again. + Extension *extension = &self->extension_classes[class_name]; + if (extension->is_reloading) { + return; + } +#endif + MethodInfo s; s.name = signal_name; for (int i = 0; i < p_argument_count; i++) { @@ -470,14 +635,32 @@ void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_libra StringName class_name = *reinterpret_cast(p_class_name); ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to unregister unexisting extension class '" + class_name + "'."); + Extension *ext = &self->extension_classes[class_name]; +#ifdef TOOLS_ENABLED + if (ext->is_reloading) { + self->_clear_extension(ext); + } +#endif ERR_FAIL_COND_MSG(ext->gdextension.children.size(), "Attempt to unregister class '" + class_name + "' while other extension classes inherit from it."); +#ifdef TOOLS_ENABLED + ClassDB::unregister_extension_class(class_name, !ext->is_reloading); +#else ClassDB::unregister_extension_class(class_name); +#endif + if (ext->gdextension.parent != nullptr) { ext->gdextension.parent->children.erase(&ext->gdextension); } + +#ifdef TOOLS_ENABLED + if (!ext->is_reloading) { + self->extension_classes.erase(class_name); + } +#else self->extension_classes.erase(class_name); +#endif } void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringPtr r_path) { @@ -540,6 +723,11 @@ void GDExtension::close_library() { #endif library = nullptr; + class_icon_paths.clear(); + +#ifdef TOOLS_ENABLED + instance_bindings.clear(); +#endif } bool GDExtension::is_library_open() const { @@ -590,6 +778,12 @@ GDExtension::~GDExtension() { if (library != nullptr) { close_library(); } +#ifdef TOOLS_ENABLED + // If we have any invalid method binds still laying around, we can finally free them! + for (GDExtensionMethodBind *E : invalid_methods) { + memdelete(E); + } +#endif } void GDExtension::initialize_gdextensions() { @@ -610,27 +804,22 @@ void GDExtension::initialize_gdextensions() { register_interface_function("get_library_path", (GDExtensionInterfaceFunctionPtr)&GDExtension::_get_library_path); } -Ref 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) { +Error GDExtensionResourceLoader::load_gdextension_resource(const String &p_path, Ref &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."); + Ref config; config.instantiate(); Error err = config->load(p_path); - if (r_error) { - *r_error = err; - } - if (err != OK) { ERR_PRINT("Error loading GDExtension configuration file: " + p_path); - return Ref(); + return err; } if (!config->has_section_key("configuration", "entry_symbol")) { - if (r_error) { - *r_error = ERR_INVALID_DATA; - } ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path); - return Ref(); + return ERR_INVALID_DATA; } String entry_symbol = config->get_value("configuration", "entry_symbol"); @@ -648,19 +837,13 @@ Ref GDExtensionResourceLoader::load(const String &p_path, const String } } } else { - if (r_error) { - *r_error = ERR_INVALID_DATA; - } ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path); - return Ref(); + return ERR_INVALID_DATA; } if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) { - if (r_error) { - *r_error = ERR_INVALID_DATA; - } ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); - return Ref(); + return ERR_INVALID_DATA; } bool compatible = true; @@ -673,42 +856,42 @@ Ref GDExtensionResourceLoader::load(const String &p_path, const String compatible = VERSION_PATCH >= compatibility_minimum[2]; } if (!compatible) { - if (r_error) { - *r_error = ERR_INVALID_DATA; - } ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s", compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); - return Ref(); + return ERR_INVALID_DATA; } String library_path = GDExtension::find_extension_library(p_path, config, [](String p_feature) { return OS::get_singleton()->has_feature(p_feature); }); if (library_path.is_empty()) { - if (r_error) { - *r_error = ERR_FILE_NOT_FOUND; - } const String os_arch = OS::get_singleton()->get_name().to_lower() + "." + Engine::get_singleton()->get_architecture_name(); ERR_PRINT(vformat("No GDExtension library found for current OS and architecture (%s) in configuration file: %s", os_arch, p_path)); - return Ref(); + return ERR_FILE_NOT_FOUND; } if (!library_path.is_resource_file() && !library_path.is_absolute_path()) { library_path = p_path.get_base_dir().path_join(library_path); } - Ref lib; - lib.instantiate(); - String abs_path = ProjectSettings::get_singleton()->globalize_path(library_path); + if (p_extension.is_null()) { + p_extension.instantiate(); + } +#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))); +#endif + + String abs_path = ProjectSettings::get_singleton()->globalize_path(library_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. if (Engine::get_singleton()->is_editor_hint()) { if (!FileAccess::exists(abs_path)) { - if (r_error) { - *r_error = ERR_FILE_NOT_FOUND; - } ERR_PRINT("GDExtension library not found: " + library_path); - return Ref(); + return ERR_FILE_NOT_FOUND; } // Copy the file to the same directory as the original with a prefix in the name. @@ -722,36 +905,29 @@ Ref GDExtensionResourceLoader::load(const String &p_path, const String Error copy_err = DirAccess::copy_absolute(abs_path, copy_path); if (copy_err) { - if (r_error) { - *r_error = ERR_CANT_CREATE; - } ERR_PRINT("Error copying GDExtension library: " + library_path); - return Ref(); + return ERR_CANT_CREATE; } FileAccess::set_hidden_attribute(copy_path, true); // Save the copied path so it can be deleted later. - lib->set_temp_library_path(copy_path); + p_extension->set_temp_library_path(copy_path); // Use the copy to open the library. abs_path = copy_path; } #endif - err = lib->open_library(abs_path, entry_symbol); - - if (r_error) { - *r_error = err; - } + err = p_extension->open_library(abs_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. if (Engine::get_singleton()->is_editor_hint()) { - DirAccess::remove_absolute(lib->get_temp_library_path()); + DirAccess::remove_absolute(p_extension->get_temp_library_path()); } #endif // Errors already logged in open_library() - return Ref(); + return err; } // Handle icons if any are specified. @@ -759,10 +935,20 @@ Ref GDExtensionResourceLoader::load(const String &p_path, const String List keys; config->get_section_keys("icons", &keys); for (const String &key : keys) { - lib->class_icon_paths[key] = config->get_value("icons", key); + p_extension->class_icon_paths[key] = config->get_value("icons", key); } } + return OK; +} + +Ref 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) { + Ref lib; + Error err = load_gdextension_resource(p_path, lib); + if (err != OK && r_error) { + // Errors already logged in load_gdextension_resource(). + *r_error = err; + } return lib; } @@ -783,6 +969,183 @@ 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) { + return true; + } + if (FileAccess::get_modified_time(library_path) > last_modified_time) { + return true; + } + return false; +} + +void GDExtension::prepare_reload() { + is_reloading = true; + + for (KeyValue &E : extension_classes) { + E.value.is_reloading = true; + + for (KeyValue &M : E.value.methods) { + M.value->is_reloading = true; + } + + for (const ObjectID &obj_id : E.value.instances) { + Object *obj = ObjectDB::get_instance(obj_id); + if (!obj) { + continue; + } + + // Store instance state so it can be restored after reload. + List> state; + List prop_list; + obj->get_property_list(&prop_list); + for (const PropertyInfo &P : prop_list) { + if (!(P.usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + + Variant value = obj->get(P.name); + Variant default_value = ClassDB::class_get_default_property_value(obj->get_class_name(), P.name); + + if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) { + continue; + } + + if (P.type == Variant::OBJECT && value.is_zero() && !(P.usage & PROPERTY_USAGE_STORE_IF_NULL)) { + continue; + } + + state.push_back(Pair(P.name, value)); + } + E.value.instance_state[obj_id] = state; + } + } +} + +void GDExtension::_clear_extension(Extension *p_extension) { + // Clear out hierarchy information because it may change. + p_extension->gdextension.parent = nullptr; + p_extension->gdextension.children.clear(); + + // Clear all objects of any GDExtension data. It will become its native parent class + // until the reload can reset the object with the new GDExtension data. + for (const ObjectID &obj_id : p_extension->instances) { + Object *obj = ObjectDB::get_instance(obj_id); + if (!obj) { + continue; + } + + obj->clear_internal_extension(); + } +} + +void GDExtension::track_instance_binding(Object *p_object) { + instance_bindings.push_back(p_object->get_instance_id()); +} + +void GDExtension::untrack_instance_binding(Object *p_object) { + instance_bindings.erase(p_object->get_instance_id()); +} + +void GDExtension::clear_instance_bindings() { + for (ObjectID obj_id : instance_bindings) { + Object *obj = ObjectDB::get_instance(obj_id); + if (!obj) { + continue; + } + + obj->free_instance_binding(this); + } + instance_bindings.clear(); +} + +void GDExtension::finish_reload() { + is_reloading = false; + + // Clean up any classes or methods that didn't get re-added. + Vector classes_to_remove; + for (KeyValue &E : extension_classes) { + if (E.value.is_reloading) { + E.value.is_reloading = false; + classes_to_remove.push_back(E.key); + } + + Vector methods_to_remove; + for (KeyValue &M : E.value.methods) { + if (M.value->is_reloading) { + M.value->valid = false; + invalid_methods.push_back(M.value); + + M.value->is_reloading = false; + methods_to_remove.push_back(M.key); + } + } + for (const StringName &method_name : methods_to_remove) { + E.value.methods.erase(method_name); + } + } + for (const StringName &class_name : classes_to_remove) { + extension_classes.erase(class_name); + } + + // Reset any the extension on instances made from the classes that remain. + for (KeyValue &E : extension_classes) { + // Loop over 'instance_state' rather than 'instance' because new instances + // may have been created when re-initializing the extension. + for (const KeyValue>> &S : E.value.instance_state) { + Object *obj = ObjectDB::get_instance(S.key); + if (!obj) { + continue; + } + + obj->reset_internal_extension(&E.value.gdextension); + } + } + + // Now that all the classes are back, restore the state. + for (KeyValue &E : extension_classes) { + for (const KeyValue>> &S : E.value.instance_state) { + Object *obj = ObjectDB::get_instance(S.key); + if (!obj) { + continue; + } + + for (const Pair &state : S.value) { + obj->set(state.first, state.second); + } + } + } + + // Finally, let the objects know that we are done reloading them. + for (KeyValue &E : extension_classes) { + for (const KeyValue>> &S : E.value.instance_state) { + Object *obj = ObjectDB::get_instance(S.key); + if (!obj) { + continue; + } + + obj->notification(NOTIFICATION_EXTENSION_RELOADED); + } + + // Clear the instance state, we're done looping. + E.value.instance_state.clear(); + } +} + +void GDExtension::_track_instance(void *p_user_data, void *p_instance) { + Extension *extension = reinterpret_cast(p_user_data); + Object *obj = reinterpret_cast(p_instance); + + extension->instances.insert(obj->get_instance_id()); +} + +void GDExtension::_untrack_instance(void *p_user_data, void *p_instance) { + Extension *extension = reinterpret_cast(p_user_data); + Object *obj = reinterpret_cast(p_instance); + + extension->instances.erase(obj->get_instance_id()); +} + Vector GDExtensionEditorPlugins::extension_classes; GDExtensionEditorPlugins::EditorPluginRegisterFunc GDExtensionEditorPlugins::editor_node_add_plugin = nullptr; GDExtensionEditorPlugins::EditorPluginRegisterFunc GDExtensionEditorPlugins::editor_node_remove_plugin = nullptr; diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 628cfae8c08e..2b423464784d 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -38,17 +38,29 @@ #include "core/io/resource_loader.h" #include "core/object/ref_counted.h" +class GDExtensionMethodBind; + class GDExtension : public Resource { GDCLASS(GDExtension, Resource) + friend class GDExtensionManager; + void *library = nullptr; // pointer if valid, String library_path; #if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) String temp_lib_path; #endif + bool reloadable = false; struct Extension { ObjectGDExtension gdextension; + +#ifdef TOOLS_ENABLED + bool is_reloading = false; + HashMap methods; + HashSet instances; + HashMap>> instance_state; +#endif }; HashMap extension_classes; @@ -77,6 +89,23 @@ class GDExtension : public Resource { GDExtensionInitialization initialization; int32_t level_initialized = -1; +#ifdef TOOLS_ENABLED + uint64_t last_modified_time = 0; + bool is_reloading = false; + Vector invalid_methods; + Vector instance_bindings; + + static void _track_instance(void *p_user_data, void *p_instance); + static void _untrack_instance(void *p_user_data, void *p_instance); + + void _clear_extension(Extension *p_extension); + + // Only called by GDExtensionManager during the reload process. + void prepare_reload(); + void finish_reload(); + void clear_instance_bindings(); +#endif + protected: static void _bind_methods(); @@ -103,6 +132,19 @@ class GDExtension : public Resource { bool is_library_open() const; +#ifdef TOOLS_ENABLED + bool is_reloadable() const { return reloadable; } + 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 track_instance_binding(Object *p_object); + void untrack_instance_binding(Object *p_object); +#endif + InitializationLevel get_minimum_library_initialization_level() const; void initialize_library(InitializationLevel p_level); void deinitialize_library(InitializationLevel p_level); @@ -119,6 +161,8 @@ VARIANT_ENUM_CAST(GDExtension::InitializationLevel) class GDExtensionResourceLoader : public ResourceFormatLoader { public: + static Error load_gdextension_resource(const String &p_path, Ref &p_extension); + virtual Ref load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE); virtual void get_recognized_extensions(List *p_extensions) const; virtual bool handles_type(const String &p_type) const; diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h index ed368fefc381..08bdf5558115 100644 --- a/core/extension/gdextension_interface.h +++ b/core/extension/gdextension_interface.h @@ -267,6 +267,7 @@ typedef void (*GDExtensionClassUnreference)(GDExtensionClassInstancePtr p_instan typedef void (*GDExtensionClassCallVirtual)(GDExtensionClassInstancePtr p_instance, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); typedef GDExtensionObjectPtr (*GDExtensionClassCreateInstance)(void *p_class_userdata); typedef void (*GDExtensionClassFreeInstance)(void *p_class_userdata, GDExtensionClassInstancePtr p_instance); +typedef GDExtensionClassInstancePtr (*GDExtensionClassRecreateInstance)(void *p_class_userdata, GDExtensionObjectPtr p_object); typedef GDExtensionClassCallVirtual (*GDExtensionClassGetVirtual)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name); typedef void *(*GDExtensionClassGetVirtualCallData)(void *p_class_userdata, GDExtensionConstStringNamePtr p_name); typedef void (*GDExtensionClassCallVirtualWithData)(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, void *p_virtual_call_userdata, const GDExtensionConstTypePtr *p_args, GDExtensionTypePtr r_ret); @@ -308,6 +309,7 @@ typedef struct { GDExtensionClassUnreference unreference_func; GDExtensionClassCreateInstance create_instance_func; // (Default) constructor; mandatory. If the class is not instantiable, consider making it virtual or abstract. GDExtensionClassFreeInstance free_instance_func; // Destructor; mandatory. + GDExtensionClassRecreateInstance recreate_instance_func; // Queries a virtual function by name and returns a callback to invoke the requested virtual function. GDExtensionClassGetVirtual get_virtual_func; // Paired with `call_virtual_with_data_func`, this is an alternative to `get_virtual_func` for extensions that diff --git a/core/extension/gdextension_manager.cpp b/core/extension/gdextension_manager.cpp index 34b417bc42ac..0dc84f685fae 100644 --- a/core/extension/gdextension_manager.cpp +++ b/core/extension/gdextension_manager.cpp @@ -32,58 +32,117 @@ #include "core/extension/gdextension_compat_hashes.h" #include "core/io/file_access.h" +#include "core/object/script_language.h" -GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) { - if (gdextension_map.has(p_path)) { - return LOAD_STATUS_ALREADY_LOADED; - } - Ref extension = ResourceLoader::load(p_path); - if (extension.is_null()) { - return LOAD_STATUS_FAILED; - } - +GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref &p_extension) { if (level >= 0) { // Already initialized up to some level. - int32_t minimum_level = extension->get_minimum_library_initialization_level(); + int32_t minimum_level = p_extension->get_minimum_library_initialization_level(); if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) { return LOAD_STATUS_NEEDS_RESTART; } // Initialize up to current level. for (int32_t i = minimum_level; i <= level; i++) { - extension->initialize_library(GDExtension::InitializationLevel(i)); + p_extension->initialize_library(GDExtension::InitializationLevel(i)); } } - for (const KeyValue &kv : extension->class_icon_paths) { + for (const KeyValue &kv : p_extension->class_icon_paths) { gdextension_class_icon_paths[kv.key] = kv.value; } + return LOAD_STATUS_OK; +} + +GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref &p_extension) { + if (level >= 0) { // Already initialized up to some level. + // Deinitialize down from current level. + for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) { + p_extension->deinitialize_library(GDExtension::InitializationLevel(i)); + } + } + + for (const KeyValue &kv : p_extension->class_icon_paths) { + gdextension_class_icon_paths.erase(kv.key); + } + + return LOAD_STATUS_OK; +} + +GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) { + if (gdextension_map.has(p_path)) { + return LOAD_STATUS_ALREADY_LOADED; + } + Ref extension = ResourceLoader::load(p_path); + if (extension.is_null()) { + return LOAD_STATUS_FAILED; + } + + LoadStatus status = _load_extension_internal(extension); + if (status != LOAD_STATUS_OK) { + return status; + } + gdextension_map[p_path] = extension; return LOAD_STATUS_OK; } GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String &p_path) { - return LOAD_STATUS_OK; //TODO -} -GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) { +#ifndef TOOLS_ENABLED + ERR_FAIL_V_MSG(LOAD_STATUS_FAILED, "GDExtensions can only be reloaded in an editor build."); +#else + ERR_FAIL_COND_V_MSG(!Engine::get_singleton()->is_extension_reloading_enabled(), LOAD_STATUS_FAILED, "GDExtension reloading is disabled."); + if (!gdextension_map.has(p_path)) { return LOAD_STATUS_NOT_LOADED; } Ref extension = gdextension_map[p_path]; + ERR_FAIL_COND_V_MSG(!extension->is_reloadable(), LOAD_STATUS_FAILED, vformat("This GDExtension is not marked as 'reloadable' or doesn't support reloading: %s.", p_path)); - if (level >= 0) { // Already initialized up to some level. - int32_t minimum_level = extension->get_minimum_library_initialization_level(); - if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) { - return LOAD_STATUS_NEEDS_RESTART; - } - // Deinitialize down to current level. - for (int32_t i = level; i >= minimum_level; i--) { - extension->deinitialize_library(GDExtension::InitializationLevel(i)); + LoadStatus status; + + extension->prepare_reload(); + + // Unload library if it's open. It may not be open if the developer made a + // change that broke loading in a previous hot-reload attempt. + if (extension->is_library_open()) { + status = _unload_extension_internal(extension); + if (status != LOAD_STATUS_OK) { + // We need to clear these no matter what. + extension->clear_instance_bindings(); + return status; } + + extension->clear_instance_bindings(); + extension->close_library(); } - for (const KeyValue &kv : extension->class_icon_paths) { - gdextension_class_icon_paths.erase(kv.key); + Error err = GDExtensionResourceLoader::load_gdextension_resource(p_path, extension); + if (err != OK) { + return LOAD_STATUS_FAILED; + } + + status = _load_extension_internal(extension); + if (status != LOAD_STATUS_OK) { + return status; + } + + extension->finish_reload(); + + return LOAD_STATUS_OK; +#endif +} + +GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) { + if (!gdextension_map.has(p_path)) { + return LOAD_STATUS_NOT_LOADED; + } + + Ref extension = gdextension_map[p_path]; + + LoadStatus status = _unload_extension_internal(extension); + if (status != LOAD_STATUS_OK) { + return status; } gdextension_map.erase(p_path); @@ -136,6 +195,36 @@ void GDExtensionManager::deinitialize_extensions(GDExtension::InitializationLeve level = int32_t(p_level) - 1; } +#ifdef TOOLS_ENABLED +void GDExtensionManager::track_instance_binding(void *p_token, Object *p_object) { + for (KeyValue> &E : gdextension_map) { + if (E.value.ptr() == p_token) { + if (E.value->is_reloadable()) { + E.value->track_instance_binding(p_object); + return; + } + } + } +} + +void GDExtensionManager::untrack_instance_binding(void *p_token, Object *p_object) { + for (KeyValue> &E : gdextension_map) { + if (E.value.ptr() == p_token) { + if (E.value->is_reloadable()) { + E.value->untrack_instance_binding(p_object); + return; + } + } + } +} + +void GDExtensionManager::_reload_all_scripts() { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->reload_all_scripts(); + } +} +#endif // TOOLS_ENABLED + void GDExtensionManager::load_extensions() { Ref f = FileAccess::open(GDExtension::get_extension_list_config_file(), FileAccess::READ); while (f.is_valid() && !f->eof_reached()) { @@ -149,9 +238,33 @@ void GDExtensionManager::load_extensions() { OS::get_singleton()->load_platform_gdextensions(); } +void GDExtensionManager::reload_extensions() { +#ifdef TOOLS_ENABLED + bool reloaded = false; + for (const KeyValue> &E : gdextension_map) { + if (!E.value->is_reloadable()) { + continue; + } + + if (E.value->has_library_changed()) { + reloaded = true; + reload_extension(E.value->get_path()); + } + } + + if (reloaded) { + emit_signal("extensions_reloaded"); + + // Reload all scripts to clear out old references. + callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred(); + } +#endif +} + GDExtensionManager *GDExtensionManager::get_singleton() { return singleton; } + void GDExtensionManager::_bind_methods() { ClassDB::bind_method(D_METHOD("load_extension", "path"), &GDExtensionManager::load_extension); ClassDB::bind_method(D_METHOD("reload_extension", "path"), &GDExtensionManager::reload_extension); @@ -166,6 +279,8 @@ void GDExtensionManager::_bind_methods() { BIND_ENUM_CONSTANT(LOAD_STATUS_ALREADY_LOADED); BIND_ENUM_CONSTANT(LOAD_STATUS_NOT_LOADED); BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART); + + ADD_SIGNAL(MethodInfo("extensions_reloaded")); } GDExtensionManager *GDExtensionManager::singleton = nullptr; diff --git a/core/extension/gdextension_manager.h b/core/extension/gdextension_manager.h index 3643f043d80e..8cd6d5a3e2ad 100644 --- a/core/extension/gdextension_manager.h +++ b/core/extension/gdextension_manager.h @@ -53,6 +53,15 @@ class GDExtensionManager : public Object { LOAD_STATUS_NEEDS_RESTART, }; +private: + LoadStatus _load_extension_internal(const Ref &p_extension); + LoadStatus _unload_extension_internal(const Ref &p_extension); + +#ifdef TOOLS_ENABLED + static void _reload_all_scripts(); +#endif + +public: LoadStatus load_extension(const String &p_path); LoadStatus reload_extension(const String &p_path); LoadStatus unload_extension(const String &p_path); @@ -66,9 +75,15 @@ class GDExtensionManager : public Object { void initialize_extensions(GDExtension::InitializationLevel p_level); void deinitialize_extensions(GDExtension::InitializationLevel p_level); +#ifdef TOOLS_ENABLED + void track_instance_binding(void *p_token, Object *p_object); + void untrack_instance_binding(void *p_token, Object *p_object); +#endif + static GDExtensionManager *get_singleton(); void load_extensions(); + void reload_extensions(); GDExtensionManager(); }; diff --git a/core/io/resource_loader.h b/core/io/resource_loader.h index 0c7d6c0feb2f..3c32a19066b6 100644 --- a/core/io/resource_loader.h +++ b/core/io/resource_loader.h @@ -230,7 +230,11 @@ class ResourceLoader { // Loaders can safely use this regardless which thread they are running on. static void notify_dependency_error(const String &p_path, const String &p_dependency, const String &p_type) { if (dep_err_notify) { - callable_mp_static(dep_err_notify).bind(p_path, p_dependency, p_type).call_deferred(); + if (Thread::get_caller_id() == Thread::get_main_id()) { + dep_err_notify(p_path, p_dependency, p_type); + } else { + callable_mp_static(dep_err_notify).bind(p_path, p_dependency, p_type).call_deferred(); + } } } static void set_dependency_error_notify_func(DependencyErrorNotify p_err_notify) { diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index 9a3744fef839..379d34aa2ad5 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -32,7 +32,7 @@ #include "core/variant/typed_array.h" -static real_t heuristic_euclidian(const Vector2i &p_from, const Vector2i &p_to) { +static real_t heuristic_euclidean(const Vector2i &p_from, const Vector2i &p_to) { real_t dx = (real_t)ABS(p_to.x - p_from.x); real_t dy = (real_t)ABS(p_to.y - p_from.y); return (real_t)Math::sqrt(dx * dx + dy * dy); @@ -57,7 +57,7 @@ static real_t heuristic_chebyshev(const Vector2i &p_from, const Vector2i &p_to) return MAX(dx, dy); } -static real_t (*heuristics[AStarGrid2D::HEURISTIC_MAX])(const Vector2i &, const Vector2i &) = { heuristic_euclidian, heuristic_manhattan, heuristic_octile, heuristic_chebyshev }; +static real_t (*heuristics[AStarGrid2D::HEURISTIC_MAX])(const Vector2i &, const Vector2i &) = { heuristic_euclidean, heuristic_manhattan, heuristic_octile, heuristic_chebyshev }; void AStarGrid2D::set_region(const Rect2i &p_region) { ERR_FAIL_COND(p_region.size.x < 0 || p_region.size.y < 0); diff --git a/core/math/basis.cpp b/core/math/basis.cpp index 6b0ecadc7fdb..9796ac59c22a 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -96,6 +96,14 @@ bool Basis::is_orthogonal() const { return m.is_equal_approx(identity); } +bool Basis::is_conformal() const { + const Vector3 x = get_column(0); + const Vector3 y = get_column(1); + const Vector3 z = get_column(2); + const real_t x_len_sq = x.length_squared(); + 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)); +} + bool Basis::is_diagonal() const { return ( Math::is_zero_approx(rows[0][1]) && Math::is_zero_approx(rows[0][2]) && diff --git a/core/math/basis.h b/core/math/basis.h index 1a68bee6861a..adacd1c21697 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_conformal() const; bool is_diagonal() const; bool is_rotation() const; diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp index a0187e00b1bc..bc4682fd901d 100644 --- a/core/math/transform_2d.cpp +++ b/core/math/transform_2d.cpp @@ -164,6 +164,18 @@ Transform2D Transform2D::orthonormalized() const { return ortho; } +bool Transform2D::is_conformal() const { + // Non-flipped case. + if (Math::is_equal_approx(columns[0][0], columns[1][1]) && Math::is_equal_approx(columns[0][1], -columns[1][0])) { + return true; + } + // Flipped case. + if (Math::is_equal_approx(columns[0][0], -columns[1][1]) && Math::is_equal_approx(columns[0][1], columns[1][0])) { + return true; + } + return false; +} + bool Transform2D::is_equal_approx(const Transform2D &p_transform) const { return columns[0].is_equal_approx(p_transform.columns[0]) && columns[1].is_equal_approx(p_transform.columns[1]) && columns[2].is_equal_approx(p_transform.columns[2]); } diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h index c51103466912..dd1a33c5d5fd 100644 --- a/core/math/transform_2d.h +++ b/core/math/transform_2d.h @@ -96,6 +96,7 @@ struct _NO_DISCARD_ Transform2D { void orthonormalize(); Transform2D orthonormalized() const; + bool is_conformal() const; bool is_equal_approx(const Transform2D &p_transform) const; bool is_finite() const; diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index 6a6043e42d1d..c594f4a9b498 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -31,6 +31,7 @@ #include "class_db.h" #include "core/config/engine.h" +#include "core/io/resource_loader.h" #include "core/object/script_language.h" #include "core/os/mutex.h" #include "core/version.h" @@ -348,7 +349,13 @@ Object *ClassDB::instantiate(const StringName &p_class) { } #endif if (ti->gdextension && ti->gdextension->create_instance) { - return (Object *)ti->gdextension->create_instance(ti->gdextension->class_userdata); + Object *obj = (Object *)ti->gdextension->create_instance(ti->gdextension->class_userdata); +#ifdef TOOLS_ENABLED + if (ti->gdextension->track_instance) { + ti->gdextension->track_instance(ti->gdextension->tracking_userdata, obj); + } +#endif + return obj; } else { return ti->creation_func(); } @@ -378,7 +385,14 @@ bool ClassDB::can_instantiate(const StringName &p_class) { OBJTYPE_RLOCK; ClassInfo *ti = classes.getptr(p_class); - ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'."); + if (!ti) { + if (!ScriptServer::is_global_class(p_class)) { + ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'."); + } + String path = ScriptServer::get_global_class_path(p_class); + Ref