diff options
431 files changed, 24698 insertions, 19856 deletions
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml index 0a0160326c..2999154fd3 100644 --- a/.github/workflows/linux_builds.yml +++ b/.github/workflows/linux_builds.yml @@ -6,6 +6,8 @@ env: # Only used for the cache key. Increment version to force clean build. GODOT_BASE_BRANCH: master-v2 SCONSFLAGS: verbose=yes warnings=extra werror=yes module_text_server_fb_enabled=yes + DOTNET_NOLOGO: true + DOTNET_CLI_TELEMETRY_OPTOUT: false concurrency: group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-linux @@ -99,6 +101,12 @@ jobs: - name: Setup python and scons uses: ./.github/actions/godot-deps + - name: Set up .NET Sdk + uses: actions/setup-dotnet@v1 + if: ${{ matrix.build-mono }} + with: + dotnet-version: '6.0.x' + - name: Compilation uses: ./.github/actions/godot-build with: @@ -108,10 +116,15 @@ jobs: tools: ${{ matrix.tools }} tests: ${{ matrix.tests }} - - name: Generate Mono glue + - name: Generate C# glue + if: ${{ matrix.build-mono }} + run: | + ${{ matrix.bin }} --headless --generate-mono-glue ./modules/mono/glue || true + + - name: Build .NET solutions if: ${{ matrix.build-mono }} run: | - ${{ matrix.bin }} --headless --generate-mono-glue modules/mono/glue || true + ./modules/mono/build_scripts/build_assemblies.py --godot-output-dir=./bin --godot-platform=linuxbsd # Rebuild with mono - name: Compilation (mono_glue=yes) diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 5c4bcc687a..26b683db82 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1070,7 +1070,7 @@ bool ProjectSettings::is_using_datapack() const { return using_datapack; } -bool ProjectSettings::property_can_revert(const String &p_name) { +bool ProjectSettings::_property_can_revert(const StringName &p_name) const { if (!props.has(p_name)) { return false; } @@ -1078,12 +1078,13 @@ bool ProjectSettings::property_can_revert(const String &p_name) { return props[p_name].initial != props[p_name].variant; } -Variant ProjectSettings::property_get_revert(const String &p_name) { +bool ProjectSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const { if (!props.has(p_name)) { - return Variant(); + return false; } - return props[p_name].initial; + r_property = props[p_name].initial; + return true; } void ProjectSettings::set_setting(const String &p_setting, const Variant &p_value) { @@ -1134,8 +1135,6 @@ void ProjectSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("globalize_path", "path"), &ProjectSettings::globalize_path); ClassDB::bind_method(D_METHOD("save"), &ProjectSettings::save); ClassDB::bind_method(D_METHOD("load_resource_pack", "pack", "replace_files", "offset"), &ProjectSettings::_load_resource_pack, DEFVAL(true), DEFVAL(0)); - ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &ProjectSettings::property_can_revert); - ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &ProjectSettings::property_get_revert); ClassDB::bind_method(D_METHOD("save_custom", "file"), &ProjectSettings::_save_custom_bnd); } @@ -1183,10 +1182,14 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("application/config/custom_user_dir_name", ""); GLOBAL_DEF("application/config/project_settings_override", ""); - GLOBAL_DEF_BASIC("display/window/size/viewport_width", 1024); + // The default window size is tuned to: + // - 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("display/window/size/viewport_width", 1152); custom_prop_info["display/window/size/viewport_width"] = PropertyInfo(Variant::INT, "display/window/size/viewport_width", PROPERTY_HINT_RANGE, "0,7680,1,or_greater"); // 8K resolution - GLOBAL_DEF_BASIC("display/window/size/viewport_height", 600); + GLOBAL_DEF_BASIC("display/window/size/viewport_height", 648); custom_prop_info["display/window/size/viewport_height"] = PropertyInfo(Variant::INT, "display/window/size/viewport_height", PROPERTY_HINT_RANGE, "0,4320,1,or_greater"); // 8K resolution GLOBAL_DEF_BASIC("display/window/size/resizable", true); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index c3992a4db2..c845120a26 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -102,6 +102,8 @@ protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; static ProjectSettings *singleton; @@ -147,9 +149,6 @@ public: void set_ignore_value_in_docs(const String &p_name, bool p_ignore); bool get_ignore_value_in_docs(const String &p_name) const; - bool property_can_revert(const String &p_name); - Variant property_get_revert(const String &p_name); - String get_project_data_dir_name() const; String get_project_data_path() const; String get_resource_path() const; diff --git a/core/extension/gdnative_interface.h b/core/extension/gdnative_interface.h index 041a6e5112..cb2adcb562 100644 --- a/core/extension/gdnative_interface.h +++ b/core/extension/gdnative_interface.h @@ -222,6 +222,8 @@ typedef struct { typedef const GDNativePropertyInfo *(*GDNativeExtensionClassGetPropertyList)(GDExtensionClassInstancePtr p_instance, uint32_t *r_count); typedef void (*GDNativeExtensionClassFreePropertyList)(GDExtensionClassInstancePtr p_instance, const GDNativePropertyInfo *p_list); +typedef GDNativeBool (*GDNativeExtensionClassPropertyCanRevert)(GDExtensionClassInstancePtr p_instance, const GDNativeStringNamePtr p_name); +typedef GDNativeBool (*GDNativeExtensionClassPropertyGetRevert)(GDExtensionClassInstancePtr p_instance, const GDNativeStringNamePtr p_name, GDNativeVariantPtr r_ret); typedef void (*GDNativeExtensionClassNotification)(GDExtensionClassInstancePtr p_instance, int32_t p_what); typedef void (*GDNativeExtensionClassToString)(GDExtensionClassInstancePtr p_instance, GDNativeStringPtr p_out); typedef void (*GDNativeExtensionClassReference)(GDExtensionClassInstancePtr p_instance); @@ -237,6 +239,8 @@ typedef struct { GDNativeExtensionClassGet get_func; GDNativeExtensionClassGetPropertyList get_property_list_func; GDNativeExtensionClassFreePropertyList free_property_list_func; + GDNativeExtensionClassPropertyCanRevert property_can_revert_func; + GDNativeExtensionClassPropertyGetRevert property_get_revert_func; GDNativeExtensionClassNotification notification_func; GDNativeExtensionClassToString to_string_func; GDNativeExtensionClassReference reference_func; @@ -309,6 +313,9 @@ typedef const GDNativePropertyInfo *(*GDNativeExtensionScriptInstanceGetProperty typedef void (*GDNativeExtensionScriptInstanceFreePropertyList)(GDNativeExtensionScriptInstanceDataPtr p_instance, const GDNativePropertyInfo *p_list); typedef GDNativeVariantType (*GDNativeExtensionScriptInstanceGetPropertyType)(GDNativeExtensionScriptInstanceDataPtr p_instance, const GDNativeStringNamePtr p_name, GDNativeBool *r_is_valid); +typedef GDNativeBool (*GDNativeExtensionScriptInstancePropertyCanRevert)(GDNativeExtensionScriptInstanceDataPtr p_instance, const GDNativeStringNamePtr p_name); +typedef GDNativeBool (*GDNativeExtensionScriptInstancePropertyGetRevert)(GDNativeExtensionScriptInstanceDataPtr p_instance, const GDNativeStringNamePtr p_name, GDNativeVariantPtr r_ret); + typedef GDNativeObjectPtr (*GDNativeExtensionScriptInstanceGetOwner)(GDNativeExtensionScriptInstanceDataPtr p_instance); typedef void (*GDNativeExtensionScriptInstancePropertyStateAdd)(const GDNativeStringNamePtr p_name, const GDNativeVariantPtr p_value, void *p_userdata); typedef void (*GDNativeExtensionScriptInstanceGetPropertyState)(GDNativeExtensionScriptInstanceDataPtr p_instance, GDNativeExtensionScriptInstancePropertyStateAdd p_add_func, void *p_userdata); @@ -343,6 +350,9 @@ typedef struct { GDNativeExtensionScriptInstanceFreePropertyList free_property_list_func; GDNativeExtensionScriptInstanceGetPropertyType get_property_type_func; + GDNativeExtensionScriptInstancePropertyCanRevert property_can_revert_func; + GDNativeExtensionScriptInstancePropertyGetRevert property_get_revert_func; + GDNativeExtensionScriptInstanceGetOwner get_owner_func; GDNativeExtensionScriptInstanceGetPropertyState get_property_state_func; diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index a085df874e..fdb4e50d90 100644 --- a/core/extension/native_extension.cpp +++ b/core/extension/native_extension.cpp @@ -156,6 +156,8 @@ void NativeExtension::_register_extension_class(const GDNativeExtensionClassLibr extension->native_extension.get = p_extension_funcs->get_func; extension->native_extension.get_property_list = p_extension_funcs->get_property_list_func; extension->native_extension.free_property_list = p_extension_funcs->free_property_list_func; + extension->native_extension.property_can_revert = p_extension_funcs->property_can_revert_func; + extension->native_extension.property_get_revert = p_extension_funcs->property_get_revert_func; extension->native_extension.notification = p_extension_funcs->notification_func; extension->native_extension.to_string = p_extension_funcs->to_string_func; extension->native_extension.reference = p_extension_funcs->reference_func; diff --git a/core/math/basis.cpp b/core/math/basis.cpp index f8e7c47107..bc50d0e64c 100644 --- a/core/math/basis.cpp +++ b/core/math/basis.cpp @@ -749,67 +749,6 @@ Quaternion Basis::get_quaternion() const { return Quaternion(temp[0], temp[1], temp[2], temp[3]); } -static const Basis _ortho_bases[24] = { - Basis(1, 0, 0, 0, 1, 0, 0, 0, 1), - Basis(0, -1, 0, 1, 0, 0, 0, 0, 1), - Basis(-1, 0, 0, 0, -1, 0, 0, 0, 1), - Basis(0, 1, 0, -1, 0, 0, 0, 0, 1), - Basis(1, 0, 0, 0, 0, -1, 0, 1, 0), - Basis(0, 0, 1, 1, 0, 0, 0, 1, 0), - Basis(-1, 0, 0, 0, 0, 1, 0, 1, 0), - Basis(0, 0, -1, -1, 0, 0, 0, 1, 0), - Basis(1, 0, 0, 0, -1, 0, 0, 0, -1), - Basis(0, 1, 0, 1, 0, 0, 0, 0, -1), - Basis(-1, 0, 0, 0, 1, 0, 0, 0, -1), - Basis(0, -1, 0, -1, 0, 0, 0, 0, -1), - Basis(1, 0, 0, 0, 0, 1, 0, -1, 0), - Basis(0, 0, -1, 1, 0, 0, 0, -1, 0), - Basis(-1, 0, 0, 0, 0, -1, 0, -1, 0), - Basis(0, 0, 1, -1, 0, 0, 0, -1, 0), - Basis(0, 0, 1, 0, 1, 0, -1, 0, 0), - Basis(0, -1, 0, 0, 0, 1, -1, 0, 0), - Basis(0, 0, -1, 0, -1, 0, -1, 0, 0), - Basis(0, 1, 0, 0, 0, -1, -1, 0, 0), - Basis(0, 0, 1, 0, -1, 0, 1, 0, 0), - Basis(0, 1, 0, 0, 0, 1, 1, 0, 0), - Basis(0, 0, -1, 0, 1, 0, 1, 0, 0), - Basis(0, -1, 0, 0, 0, -1, 1, 0, 0) -}; - -int Basis::get_orthogonal_index() const { - //could be sped up if i come up with a way - Basis orth = *this; - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - real_t v = orth[i][j]; - if (v > 0.5f) { - v = 1.0f; - } else if (v < -0.5f) { - v = -1.0f; - } else { - v = 0; - } - - orth[i][j] = v; - } - } - - for (int i = 0; i < 24; i++) { - if (_ortho_bases[i] == orth) { - return i; - } - } - - return 0; -} - -void Basis::set_orthogonal_index(int p_index) { - //there only exist 24 orthogonal bases in r3 - ERR_FAIL_INDEX(p_index, 24); - - *this = _ortho_bases[p_index]; -} - void Basis::get_axis_angle(Vector3 &r_axis, real_t &r_angle) const { /* checking this is a bad idea, because obtaining from scaled transform is a valid use case #ifdef MATH_CHECKS diff --git a/core/math/basis.h b/core/math/basis.h index 4be325cdd2..2853947ba7 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -149,9 +149,6 @@ struct _NO_DISCARD_ Basis { _FORCE_INLINE_ void operator*=(const real_t p_val); _FORCE_INLINE_ Basis operator*(const real_t p_val) const; - int get_orthogonal_index() const; - void set_orthogonal_index(int p_index); - bool is_orthogonal() const; bool is_diagonal() const; bool is_rotation() const; diff --git a/core/object/object.cpp b/core/object/object.cpp index 0fcd1c0e40..a95ba7992b 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -518,6 +518,59 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons void Object::_validate_property(PropertyInfo &property) const { } +bool Object::property_can_revert(const String &p_name) const { + if (script_instance) { + if (script_instance->property_can_revert(p_name)) { + return true; + } + } + +// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#endif + if (_extension && _extension->property_can_revert) { + if (_extension->property_can_revert(_extension_instance, (const GDNativeStringNamePtr)&p_name)) { + return true; + } + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + + return _property_can_revertv(p_name); +} + +Variant Object::property_get_revert(const String &p_name) const { + Variant ret; + + if (script_instance) { + if (script_instance->property_get_revert(p_name, ret)) { + return ret; + } + } + +// C style pointer casts should never trigger a compiler warning because the risk is assumed by the user, so GCC should keep quiet about it. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#endif + if (_extension && _extension->property_get_revert) { + if (_extension->property_get_revert(_extension_instance, (const GDNativeStringNamePtr)&p_name, (GDNativeVariantPtr)&ret)) { + return ret; + } + } +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + + if (_property_get_revertv(p_name, ret)) { + return ret; + } + return Variant(); +} + void Object::get_method_list(List<MethodInfo> *p_list) const { ClassDB::get_method_list(get_class_name(), p_list); if (script_instance) { @@ -1499,10 +1552,12 @@ void Object::_bind_methods() { miget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; BIND_OBJ_CORE_METHOD(miget); - MethodInfo plget("_get_property_list"); - - plget.return_val.type = Variant::ARRAY; - BIND_OBJ_CORE_METHOD(plget); + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::ARRAY, "_get_property_list")); + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_property_can_revert", PropertyInfo(Variant::STRING_NAME, "property"))); + MethodInfo mipgr("_property_get_revert", PropertyInfo(Variant::STRING_NAME, "property")); + mipgr.return_val.name = "Variant"; + mipgr.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; + BIND_OBJ_CORE_METHOD(mipgr); #endif BIND_OBJ_CORE_METHOD(MethodInfo("_init")); diff --git a/core/object/object.h b/core/object/object.h index 35d0aaaa7d..154ef176d3 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -294,6 +294,8 @@ struct ObjectNativeExtension { GDNativeExtensionClassGet get; GDNativeExtensionClassGetPropertyList get_property_list; GDNativeExtensionClassFreePropertyList free_property_list; + GDNativeExtensionClassPropertyCanRevert property_can_revert; + GDNativeExtensionClassPropertyGetRevert property_get_revert; GDNativeExtensionClassNotification notification; GDNativeExtensionClassToString to_string; GDNativeExtensionClassReference reference; @@ -469,6 +471,28 @@ protected: m_inherits::_get_property_listv(p_list, p_reversed); \ } \ } \ + _FORCE_INLINE_ bool (Object::*_get_property_can_revert() const)(const StringName &p_name) const { \ + return (bool(Object::*)(const StringName &) const) & m_class::_property_can_revert; \ + } \ + virtual bool _property_can_revertv(const StringName &p_name) const override { \ + if (m_class::_get_property_can_revert() != m_inherits::_get_property_can_revert()) { \ + if (_property_can_revert(p_name)) { \ + return true; \ + } \ + } \ + return m_inherits::_property_can_revertv(p_name); \ + } \ + _FORCE_INLINE_ bool (Object::*_get_property_get_revert() const)(const StringName &p_name, Variant &) const { \ + return (bool(Object::*)(const StringName &, Variant &) const) & m_class::_property_get_revert; \ + } \ + virtual bool _property_get_revertv(const StringName &p_name, Variant &r_ret) const override { \ + if (m_class::_get_property_get_revert() != m_inherits::_get_property_get_revert()) { \ + if (_property_get_revert(p_name, r_ret)) { \ + return true; \ + } \ + } \ + return m_inherits::_property_get_revertv(p_name, r_ret); \ + } \ _FORCE_INLINE_ void (Object::*_get_notification() const)(int) { \ return (void(Object::*)(int)) & m_class::_notification; \ } \ @@ -613,12 +637,16 @@ protected: virtual bool _setv(const StringName &p_name, const Variant &p_property) { return false; }; virtual bool _getv(const StringName &p_name, Variant &r_property) const { return false; }; virtual void _get_property_listv(List<PropertyInfo> *p_list, bool p_reversed) const {}; + virtual bool _property_can_revertv(const StringName &p_name) const { return false; }; + virtual bool _property_get_revertv(const StringName &p_name, Variant &r_property) const { return false; }; virtual void _notificationv(int p_notification, bool p_reversed) {} static void _bind_methods(); bool _set(const StringName &p_name, const Variant &p_property) { return false; }; bool _get(const StringName &p_name, Variant &r_property) const { return false; }; void _get_property_list(List<PropertyInfo> *p_list) const {}; + bool _property_can_revert(const StringName &p_name) const { return false; }; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const { return false; }; void _notification(int p_notification) {} _FORCE_INLINE_ static void (*_get_bind_methods())() { @@ -633,6 +661,12 @@ protected: _FORCE_INLINE_ void (Object::*_get_get_property_list() const)(List<PropertyInfo> *p_list) const { return &Object::_get_property_list; } + _FORCE_INLINE_ bool (Object::*_get_property_can_revert() const)(const StringName &p_name) const { + return &Object::_property_can_revert; + } + _FORCE_INLINE_ bool (Object::*_get_property_get_revert() const)(const StringName &p_name, Variant &) const { + return &Object::_property_get_revert; + } _FORCE_INLINE_ void (Object::*_get_notification() const)(int) { return &Object::_notification; } @@ -757,6 +791,8 @@ public: Variant get_indexed(const Vector<StringName> &p_names, bool *r_valid = nullptr) const; void get_property_list(List<PropertyInfo> *p_list, bool p_reversed = false) const; + bool property_can_revert(const String &p_name) const; + Variant property_get_revert(const String &p_name) const; bool has_method(const StringName &p_method) const; void get_method_list(List<MethodInfo> *p_list) const; diff --git a/core/object/ref_counted.cpp b/core/object/ref_counted.cpp index 726e2c012c..cac2400744 100644 --- a/core/object/ref_counted.cpp +++ b/core/object/ref_counted.cpp @@ -85,7 +85,8 @@ bool RefCounted::unreference() { _get_extension()->unreference(_get_extension_instance()); } - die = die && _instance_binding_reference(false); + bool binding_ret = _instance_binding_reference(false); + die = die && binding_ret; } return die; diff --git a/core/object/script_language.h b/core/object/script_language.h index f5f052b600..bfdedbe4a5 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -171,6 +171,9 @@ public: virtual void get_property_list(List<PropertyInfo> *p_properties) const = 0; virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const = 0; + virtual bool property_can_revert(const StringName &p_name) const = 0; + virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const = 0; + virtual Object *get_owner() { return nullptr; } virtual void get_property_state(List<Pair<StringName, Variant>> &state); @@ -447,6 +450,9 @@ public: virtual void get_property_list(List<PropertyInfo> *p_properties) const override; virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const override; + virtual bool property_can_revert(const StringName &p_name) const override { return false; }; + virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const override { return false; }; + virtual void get_method_list(List<MethodInfo> *p_list) const override; virtual bool has_method(const StringName &p_method) const override; diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index 2869f4ad98..7e74f6a2be 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -692,6 +692,19 @@ public: return Variant::NIL; } + virtual bool property_can_revert(const StringName &p_name) const override { + if (native_info->property_can_revert_func) { + return native_info->property_can_revert_func(instance, (const GDNativeStringNamePtr)&p_name); + } + return false; + } + virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const override { + if (native_info->property_get_revert_func) { + return native_info->property_get_revert_func(instance, (const GDNativeStringNamePtr)&p_name, (GDNativeVariantPtr)&r_ret); + } + return false; + } + virtual Object *get_owner() override { if (native_info->get_owner_func) { return (Object *)native_info->get_owner_func(instance); diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index eba12b68bb..32d10f9ddd 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1916,7 +1916,6 @@ static void _register_variant_builtin_methods() { bind_method(Basis, tdotx, sarray("with"), varray()); bind_method(Basis, tdoty, sarray("with"), varray()); bind_method(Basis, tdotz, sarray("with"), varray()); - bind_method(Basis, get_orthogonal_index, sarray(), varray()); bind_method(Basis, slerp, sarray("to", "weight"), varray()); bind_method(Basis, is_equal_approx, sarray("b"), varray()); bind_method(Basis, get_rotation_quaternion, sarray(), varray()); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index e6901bbe27..5b3f4d4e7b 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2573,7 +2573,7 @@ </constant> <constant name="PROPERTY_HINT_RANGE" value="1" enum="PropertyHint"> Hints that an integer or float property should be within a range specified via the hint string [code]"min,max"[/code] or [code]"min,max,step"[/code]. The hint string can optionally include [code]"or_greater"[/code] and/or [code]"or_lesser"[/code] to allow manual input going respectively above the max or below the min values. Example: [code]"-360,360,1,or_greater,or_lesser"[/code]. - Additionally, other keywords can be included: "exp" for exponential range editing, "radians" for editing radian angles in degrees, "degrees" to hint at an angle and "no_slider" to hide the slider. + Additionally, other keywords can be included: [code]"exp"[/code] for exponential range editing, [code]"radians"[/code] for editing radian angles in degrees, [code]"degrees"[/code] to hint at an angle and [code]"no_slider"[/code] to hide the slider. </constant> <constant name="PROPERTY_HINT_ENUM" value="2" enum="PropertyHint"> Hints that an integer, float or string property is an enumerated value to pick in a list specified via a hint string. @@ -2584,7 +2584,7 @@ Unlike [constant PROPERTY_HINT_ENUM] a property with this hint still accepts arbitrary values and can be empty. The list of values serves to suggest possible values. </constant> <constant name="PROPERTY_HINT_EXP_EASING" value="4" enum="PropertyHint"> - Hints that a float property should be edited via an exponential easing function. The hint string can include [code]"attenuation"[/code] to flip the curve horizontally and/or [code]"inout"[/code] to also include in/out easing. + Hints that a float property should be edited via an exponential easing function. The hint string can include [code]"attenuation"[/code] to flip the curve horizontally and/or [code]"positive_only"[/code] to exclude in/out easing and limit values to be greater than or equal to zero. </constant> <constant name="PROPERTY_HINT_LINK" value="5" enum="PropertyHint"> Hints that a vector property should allow linking values (e.g. to edit both [code]x[/code] and [code]y[/code] together). diff --git a/doc/classes/AABB.xml b/doc/classes/AABB.xml index 2fa191faa6..e2e4e7c61d 100644 --- a/doc/classes/AABB.xml +++ b/doc/classes/AABB.xml @@ -139,7 +139,7 @@ <return type="AABB" /> <param index="0" name="by" type="float" /> <description> - Returns a copy of the [AABB] grown a given amount of units towards all the sides. + Returns a copy of the [AABB] grown a given number of units towards all the sides. </description> </method> <method name="has_no_surface" qualifiers="const"> diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index ac956eacba..859b4a8a5f 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -349,7 +349,7 @@ <return type="int" /> <param index="0" name="track_idx" type="int" /> <description> - Returns the amount of keys in a given track. + Returns the number of keys in a given track. </description> </method> <method name="track_get_key_time" qualifiers="const"> diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index 35656dbd70..f6d926031d 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -576,14 +576,14 @@ <return type="bool" /> <param index="0" name="right" type="Array" /> <description> - Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]false[/code] if the left operand [Array] has less elements, otherwise it returns [code]true[/code]. + Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]false[/code] if the left operand [Array] has fewer elements, otherwise it returns [code]true[/code]. </description> </operator> <operator name="operator <="> <return type="bool" /> <param index="0" name="right" type="Array" /> <description> - Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the left operand [Array] has less or the same number of elements, otherwise it returns [code]false[/code]. + Performs a comparison for each index between the left operand [Array] and the [param right] [Array], considering the highest common index of both arrays for this comparison: Returns [code]true[/code] on the first occurrence of an element that is less, or [code]false[/code] if the element is greater. Note that depending on the type of data stored, this function may be recursive. If all elements are equal, it compares the length of both arrays and returns [code]true[/code] if the left operand [Array] has the same number of elements or fewer, otherwise it returns [code]false[/code]. </description> </operator> <operator name="operator =="> diff --git a/doc/classes/AudioEffectChorus.xml b/doc/classes/AudioEffectChorus.xml index 83cbcff70c..de168cdfb0 100644 --- a/doc/classes/AudioEffectChorus.xml +++ b/doc/classes/AudioEffectChorus.xml @@ -166,7 +166,7 @@ The voice's filter rate. </member> <member name="voice_count" type="int" setter="set_voice_count" getter="get_voice_count" default="2"> - The amount of voices in the effect. + The number of voices in the effect. </member> <member name="wet" type="float" setter="set_wet" getter="get_wet" default="0.5"> The effect's processed signal. diff --git a/doc/classes/AudioServer.xml b/doc/classes/AudioServer.xml index 4ded906dc1..5bd1c82641 100644 --- a/doc/classes/AudioServer.xml +++ b/doc/classes/AudioServer.xml @@ -45,7 +45,7 @@ <return type="int" /> <param index="0" name="bus_idx" type="int" /> <description> - Returns the amount of channels of the bus at index [param bus_idx]. + Returns the number of channels of the bus at index [param bus_idx]. </description> </method> <method name="get_bus_effect"> diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index 8aa6278296..d62f704528 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -87,12 +87,6 @@ Consider using the [method get_rotation_quaternion] method instead, which returns a [Quaternion] quaternion instead of Euler angles. </description> </method> - <method name="get_orthogonal_index" qualifiers="const"> - <return type="int" /> - <description> - This function considers a discretization of rotations into 24 points on unit sphere, lying along the vectors (x,y,z) with each component being either -1, 0, or 1, and returns the index of the point best representing the orientation of the object. It is mainly used by the [GridMap] editor. For further details, refer to the Godot source code. - </description> - </method> <method name="get_rotation_quaternion" qualifiers="const"> <return type="Quaternion" /> <description> diff --git a/doc/classes/BitMap.xml b/doc/classes/BitMap.xml index b286a66f34..53fd9a7b67 100644 --- a/doc/classes/BitMap.xml +++ b/doc/classes/BitMap.xml @@ -46,7 +46,7 @@ <method name="get_true_bit_count" qualifiers="const"> <return type="int" /> <description> - Returns the amount of bitmap elements that are set to [code]true[/code]. + Returns the number of bitmap elements that are set to [code]true[/code]. </description> </method> <method name="grow_mask"> diff --git a/doc/classes/CanvasItem.xml b/doc/classes/CanvasItem.xml index 1423b39e4a..a230806c08 100644 --- a/doc/classes/CanvasItem.xml +++ b/doc/classes/CanvasItem.xml @@ -5,7 +5,7 @@ </brief_description> <description> Base class of anything 2D. Canvas items are laid out in a tree; children inherit and extend their parent's transform. [CanvasItem] is extended by [Control] for anything GUI-related, and by [Node2D] for anything related to the 2D engine. - Any [CanvasItem] can draw. For this, [method update] must be called, then [constant NOTIFICATION_DRAW] will be received on idle time to request redraw. Because of this, canvas items don't need to be redrawn on every frame, improving the performance significantly. Several functions for drawing on the [CanvasItem] are provided (see [code]draw_*[/code] functions). However, they can only be used inside the [method Object._notification], signal or [method _draw] virtual functions. + Any [CanvasItem] can draw. For this, [method update] is called by the engine, then [constant NOTIFICATION_DRAW] will be received on idle time to request redraw. Because of this, canvas items don't need to be redrawn on every frame, improving the performance significantly. Several functions for drawing on the [CanvasItem] are provided (see [code]draw_*[/code] functions). However, they can only be used inside [method _draw], its corresponding [method Object._notification] or methods connected to the [signal draw] signal. Canvas items are drawn in tree order. By default, children are on top of their parents so a root [CanvasItem] will be drawn behind everything. This behavior can be changed on a per-item basis. A [CanvasItem] can also be hidden, which will also hide its children. It provides many ways to change parameters such as modulation (for itself and its children) and self modulation (only for itself), as well as its blend mode. Ultimately, a transform notification can be requested, which will notify the node that its global position changed in case the parent tree changed. @@ -20,7 +20,8 @@ <method name="_draw" qualifiers="virtual"> <return type="void" /> <description> - Overridable function called by the engine (if defined) to draw the canvas item. + Called when [CanvasItem] has been requested to redraw (when [method update] is called, either manually or by the engine). + Corresponds to the [constant NOTIFICATION_DRAW] notification in [method Object._notification]. </description> </method> <method name="draw_animation_slice"> @@ -86,7 +87,7 @@ <param index="2" name="uvs" type="PackedVector2Array" default="PackedVector2Array()" /> <param index="3" name="texture" type="Texture2D" default="null" /> <description> - Draws a colored polygon of any amount of points, convex or concave. Unlike [method draw_polygon], a single color must be specified for the whole polygon. + Draws a colored polygon of any number of points, convex or concave. Unlike [method draw_polygon], a single color must be specified for the whole polygon. </description> </method> <method name="draw_dashed_line"> @@ -211,7 +212,7 @@ <param index="2" name="uvs" type="PackedVector2Array" default="PackedVector2Array()" /> <param index="3" name="texture" type="Texture2D" default="null" /> <description> - Draws a solid polygon of any amount of points, convex or concave. Unlike [method draw_colored_polygon], each point's color can be changed individually. See also [method draw_polyline] and [method draw_polyline_colors]. + Draws a solid polygon of any number of points, convex or concave. Unlike [method draw_colored_polygon], each point's color can be changed individually. See also [method draw_polyline] and [method draw_polyline_colors]. </description> </method> <method name="draw_polyline"> @@ -465,7 +466,7 @@ <method name="is_visible_in_tree" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if the node is present in the [SceneTree], its [member visible] property is [code]true[/code] and all its antecedents are also visible. If any antecedent is hidden, this node will not be visible in the scene tree. + Returns [code]true[/code] if the node is present in the [SceneTree], its [member visible] property is [code]true[/code] and all its antecedents are also visible. If any antecedent is hidden, this node will not be visible in the scene tree, and is consequently not drawn (see [method _draw]). </description> </method> <method name="make_canvas_position_local" qualifiers="const"> @@ -505,7 +506,7 @@ <method name="update"> <return type="void" /> <description> - Queue the [CanvasItem] for update. [constant NOTIFICATION_DRAW] will be called on idle time to request redraw. + Queues the [CanvasItem] to redraw. During idle time, if [CanvasItem] is visible, [constant NOTIFICATION_DRAW] is sent and [method _draw] is called. This only occurs [b]once[/b] per frame, even if this method has been called multiple times. </description> </method> </methods> @@ -547,7 +548,8 @@ <signals> <signal name="draw"> <description> - Emitted when the [CanvasItem] must redraw. This can only be connected realtime, as deferred will not allow drawing. + Emitted when the [CanvasItem] must redraw, [i]after[/i] the related [constant NOTIFICATION_DRAW] notification, and [i]before[/i] [method _draw] is called. + [b]Note:[/b] Deferred connections do not allow drawing through the [code]draw_*[/code] methods. </description> </signal> <signal name="hidden"> @@ -574,7 +576,7 @@ The [CanvasItem]'s local transform has changed. This notification is only received if enabled by [method set_notify_local_transform]. </constant> <constant name="NOTIFICATION_DRAW" value="30"> - The [CanvasItem] is requested to draw. + The [CanvasItem] is requested to draw (see [method _draw]). </constant> <constant name="NOTIFICATION_VISIBILITY_CHANGED" value="31"> The [CanvasItem]'s visibility has changed. diff --git a/doc/classes/CodeEdit.xml b/doc/classes/CodeEdit.xml index 5277df399e..6513b1ee13 100644 --- a/doc/classes/CodeEdit.xml +++ b/doc/classes/CodeEdit.xml @@ -482,7 +482,7 @@ Prefixes to trigger an automatic indent. </member> <member name="indent_size" type="int" setter="set_indent_size" getter="get_indent_size" default="4"> - Size of tabs, if [code]indent_use_spaces[/code] is enabled the amount of spaces to use. + Size of tabs, if [code]indent_use_spaces[/code] is enabled the number of spaces to use. </member> <member name="indent_use_spaces" type="bool" setter="set_indent_using_spaces" getter="is_indent_using_spaces" default="false"> Use spaces instead of tabs for indentation. diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml index cb2474384a..989d0cdb55 100644 --- a/doc/classes/DisplayServer.xml +++ b/doc/classes/DisplayServer.xml @@ -144,7 +144,7 @@ </description> </method> <method name="global_menu_add_check_item"> - <return type="void" /> + <return type="int" /> <param index="0" name="menu_root" type="String" /> <param index="1" name="label" type="String" /> <param index="2" name="callback" type="Callable" /> @@ -153,16 +153,17 @@ <param index="5" name="index" type="int" default="-1" /> <description> Adds a new checkable item with text [param label] to the global menu with ID [param menu_root]. + Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> </method> <method name="global_menu_add_icon_check_item"> - <return type="void" /> + <return type="int" /> <param index="0" name="menu_root" type="String" /> <param index="1" name="icon" type="Texture2D" /> <param index="2" name="label" type="String" /> @@ -172,16 +173,17 @@ <param index="6" name="index" type="int" default="-1" /> <description> Adds a new checkable item with text [param label] and icon [param icon] to the global menu with ID [param menu_root]. + Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> </method> <method name="global_menu_add_icon_item"> - <return type="void" /> + <return type="int" /> <param index="0" name="menu_root" type="String" /> <param index="1" name="icon" type="Texture2D" /> <param index="2" name="label" type="String" /> @@ -191,16 +193,17 @@ <param index="6" name="index" type="int" default="-1" /> <description> Adds a new item with text [param label] and icon [param icon] to the global menu with ID [param menu_root]. + Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> </method> <method name="global_menu_add_icon_radio_check_item"> - <return type="void" /> + <return type="int" /> <param index="0" name="menu_root" type="String" /> <param index="1" name="icon" type="Texture2D" /> <param index="2" name="label" type="String" /> @@ -210,17 +213,18 @@ <param index="6" name="index" type="int" default="-1" /> <description> Adds a new radio-checkable item with text [param label] and icon [param icon] to the global menu with ID [param menu_root]. + Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. [b]Note:[/b] Radio-checkable items just display a checkmark, but don't have any built-in checking behavior and must be checked/unchecked manually. See [method global_menu_set_item_checked] for more info on how to control it. [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> </method> <method name="global_menu_add_item"> - <return type="void" /> + <return type="int" /> <param index="0" name="menu_root" type="String" /> <param index="1" name="label" type="String" /> <param index="2" name="callback" type="Callable" /> @@ -229,16 +233,17 @@ <param index="5" name="index" type="int" default="-1" /> <description> Adds a new item with text [param label] to the global menu with ID [param menu_root]. + Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> </method> <method name="global_menu_add_multistate_item"> - <return type="void" /> + <return type="int" /> <param index="0" name="menu_root" type="String" /> <param index="1" name="labe" type="String" /> <param index="2" name="max_states" type="int" /> @@ -250,16 +255,18 @@ <description> Adds a new item with text [param labe] to the global menu with ID [param menu_root]. Contrarily to normal binary items, multistate items can have more than two states, as defined by [param max_states]. Each press or activate of the item will increase the state by one. The default value is defined by [param default_state]. + Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. + [b]Note:[/b] By default, there's no indication of the current item state, it should be changed manually. [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> </method> <method name="global_menu_add_radio_check_item"> - <return type="void" /> + <return type="int" /> <param index="0" name="menu_root" type="String" /> <param index="1" name="label" type="String" /> <param index="2" name="callback" type="Callable" /> @@ -268,41 +275,44 @@ <param index="5" name="index" type="int" default="-1" /> <description> Adds a new radio-checkable item with text [param label] to the global menu with ID [param menu_root]. + Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. [b]Note:[/b] Radio-checkable items just display a checkmark, but don't have any built-in checking behavior and must be checked/unchecked manually. See [method global_menu_set_item_checked] for more info on how to control it. [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> </method> <method name="global_menu_add_separator"> - <return type="void" /> + <return type="int" /> <param index="0" name="menu_root" type="String" /> <param index="1" name="index" type="int" default="-1" /> <description> Adds a separator between items to the global menu with ID [param menu_root]. Separators also occupy an index. + Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> </method> <method name="global_menu_add_submenu_item"> - <return type="void" /> + <return type="int" /> <param index="0" name="menu_root" type="String" /> <param index="1" name="label" type="String" /> <param index="2" name="submenu" type="String" /> <param index="3" name="index" type="int" default="-1" /> <description> Adds an item that will act as a submenu of the global menu [param menu_root]. The [param submenu] argument is the ID of the global menu root that will be shown when the item is clicked. + Returns index of the inserted item, it's not guaranteed to be the same as [param index] value. [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> @@ -315,7 +325,7 @@ [b]Note:[/b] This method is implemented on macOS. [b]Supported system menu IDs:[/b] [codeblock] - "" - Main menu (macOS). + "_main" - Main menu (macOS). "_dock" - Dock popup menu (macOS). [/codeblock] </description> @@ -347,6 +357,15 @@ [b]Note:[/b] This method is implemented on macOS. </description> </method> + <method name="global_menu_get_item_indentation_level" qualifiers="const"> + <return type="int" /> + <param index="0" name="menu_root" type="String" /> + <param index="1" name="idx" type="int" /> + <description> + Returns the horizontal offset of the item at the given [param idx]. + [b]Note:[/b] This method is implemented on macOS. + </description> + </method> <method name="global_menu_get_item_index_from_tag" qualifiers="const"> <return type="int" /> <param index="0" name="menu_root" type="String" /> @@ -528,6 +547,16 @@ [b]Note:[/b] This method is not supported by macOS "_dock" menu items. </description> </method> + <method name="global_menu_set_item_indentation_level"> + <return type="void" /> + <param index="0" name="menu_root" type="String" /> + <param index="1" name="idx" type="int" /> + <param index="2" name="level" type="int" /> + <description> + Sets the horizontal offset of the item at the given [param idx]. + [b]Note:[/b] This method is implemented on macOS. + </description> + </method> <method name="global_menu_set_item_max_states"> <return type="void" /> <param index="0" name="menu_root" type="String" /> diff --git a/doc/classes/EditorNode3DGizmo.xml b/doc/classes/EditorNode3DGizmo.xml index 74870f34db..9ee21fd63b 100644 --- a/doc/classes/EditorNode3DGizmo.xml +++ b/doc/classes/EditorNode3DGizmo.xml @@ -129,7 +129,7 @@ <param index="4" name="secondary" type="bool" default="false" /> <description> Adds a list of handles (points) which can be used to edit the properties of the gizmo's Node3D. The [param ids] argument can be used to specify a custom identifier for each handle, if an empty [code]Array[/code] is passed, the ids will be assigned automatically from the [param handles] argument order. - The [param secondary] argument marks the added handles as secondary, meaning they will normally have less selection priority than regular handles. When the user is holding the shift key secondary handles will switch to have higher priority than regular handles. This change in priority can be used to place multiple handles at the same point while still giving the user control on their selection. + The [param secondary] argument marks the added handles as secondary, meaning they will normally have lower selection priority than regular handles. When the user is holding the shift key secondary handles will switch to have higher priority than regular handles. This change in priority can be used to place multiple handles at the same point while still giving the user control on their selection. There are virtual methods which will be called upon editing of these handles. Call this method during [method _redraw]. </description> </method> diff --git a/doc/classes/EditorPlugin.xml b/doc/classes/EditorPlugin.xml index a961068b7b..5a1037aebe 100644 --- a/doc/classes/EditorPlugin.xml +++ b/doc/classes/EditorPlugin.xml @@ -729,7 +729,7 @@ </constant> <constant name="CONTAINER_CANVAS_EDITOR_BOTTOM" value="8" enum="CustomControlContainer"> </constant> - <constant name="CONTAINER_PROPERTY_EDITOR_BOTTOM" value="9" enum="CustomControlContainer"> + <constant name="CONTAINER_INSPECTOR_BOTTOM" value="9" enum="CustomControlContainer"> </constant> <constant name="CONTAINER_PROJECT_SETTING_TAB_LEFT" value="10" enum="CustomControlContainer"> </constant> diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 6079cc48c8..033f63c5ce 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -132,20 +132,6 @@ Marks the passed editor setting as being changed, see [method get_changed_settings]. Only settings which exist (see [method has_setting]) will be accepted. </description> </method> - <method name="property_can_revert"> - <return type="bool" /> - <param index="0" name="name" type="String" /> - <description> - Returns [code]true[/code] if the setting specified by [param name] can have its value reverted to the default value, [code]false[/code] otherwise. When this method returns [code]true[/code], a Revert button will display next to the setting in the Editor Settings. - </description> - </method> - <method name="property_get_revert"> - <return type="Variant" /> - <param index="0" name="name" type="String" /> - <description> - Returns the default value of the setting specified by [param name]. This is the value that would be applied when clicking the Revert button in the Editor Settings. - </description> - </method> <method name="set_builtin_action_override"> <return type="void" /> <param index="0" name="name" type="String" /> @@ -553,6 +539,10 @@ <member name="interface/editor/unfocused_low_processor_mode_sleep_usec" type="float" setter="" getter=""> When the editor window is unfocused, the amount of sleeping between frames when the low-processor usage mode is enabled (in microseconds). Higher values will result in lower CPU/GPU usage, which can improve battery life on laptops (in addition to improving the running project's performance if the editor has to redraw continuously). However, higher values will result in a less responsive editor. The default value is set to limit the editor to 20 FPS when the editor window is unfocused. See also [member interface/editor/low_processor_mode_sleep_usec]. </member> + <member name="interface/editor/use_embedded_menu" type="bool" setter="" getter=""> + If [code]true[/code], editor main menu is using embedded [MenuBar] instead of system global menu. + Specific to the macOS platform. + </member> <member name="interface/inspector/max_array_dictionary_items_per_page" type="int" setter="" getter=""> The number of [Array] or [Dictionary] items to display on each "page" in the inspector. Higher values allow viewing more values per page, but take more time to load. This increased load time is noticeable when selecting nodes that have array or dictionary properties in the editor. </member> @@ -710,7 +700,7 @@ If [code]true[/code], draws tab characters as chevrons. </member> <member name="text_editor/appearance/whitespace/line_spacing" type="int" setter="" getter=""> - The space to add between lines (in pixels). Greater line spacing can help improve readability at the cost of displaying less lines on screen. + The space to add between lines (in pixels). Greater line spacing can help improve readability at the cost of displaying fewer lines on screen. </member> <member name="text_editor/behavior/files/auto_reload_scripts_on_external_change" type="bool" setter="" getter=""> If [code]true[/code], automatically reloads scripts in the editor when they have been modified and saved by external editors. @@ -866,7 +856,7 @@ </member> <member name="text_editor/theme/highlighting/function_color" type="Color" setter="" getter=""> The script editor's function call color. - [b]Note:[/b] When using the GDScript syntax highlighter, this is replaced by the function declaration color configured in the syntax theme for function declarations (e.g. [code]func _ready():[/code]). + [b]Note:[/b] When using the GDScript syntax highlighter, this is replaced by the function definition color configured in the syntax theme for function definitions (e.g. [code]func _ready():[/code]). </member> <member name="text_editor/theme/highlighting/keyword_color" type="Color" setter="" getter=""> The script editor's non-control flow keyword color (used for keywords like [code]var[/code], [code]func[/code], some built-in methods, ...). diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index 03206fa8ca..9f9d1a7ed6 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -285,7 +285,7 @@ </signal> <signal name="connection_drag_started"> <param index="0" name="from" type="String" /> - <param index="1" name="slot" type="String" /> + <param index="1" name="slot" type="int" /> <param index="2" name="is_output" type="bool" /> <description> Emitted at the beginning of a connection drag. diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml index 490b918f40..4b098bf585 100644 --- a/doc/classes/HTTPRequest.xml +++ b/doc/classes/HTTPRequest.xml @@ -176,7 +176,7 @@ <method name="get_downloaded_bytes" qualifiers="const"> <return type="int" /> <description> - Returns the amount of bytes this HTTPRequest downloaded. + Returns the number of bytes this HTTPRequest downloaded. </description> </method> <method name="get_http_client_status" qualifiers="const"> diff --git a/doc/classes/ImporterMesh.xml b/doc/classes/ImporterMesh.xml index 7a658cb52a..e15cfcd2c0 100644 --- a/doc/classes/ImporterMesh.xml +++ b/doc/classes/ImporterMesh.xml @@ -101,7 +101,7 @@ <method name="get_surface_count" qualifiers="const"> <return type="int" /> <description> - Returns the amount of surfaces that the mesh holds. + Returns the number of surfaces that the mesh holds. </description> </method> <method name="get_surface_format" qualifiers="const"> @@ -115,7 +115,7 @@ <return type="int" /> <param index="0" name="surface_idx" type="int" /> <description> - Returns the amount of lods that the mesh holds on a given surface. + Returns the number of lods that the mesh holds on a given surface. </description> </method> <method name="get_surface_lod_indices" qualifiers="const"> diff --git a/doc/classes/Label.xml b/doc/classes/Label.xml index 7353e4df9c..239eea099b 100644 --- a/doc/classes/Label.xml +++ b/doc/classes/Label.xml @@ -14,7 +14,7 @@ <method name="get_line_count" qualifiers="const"> <return type="int" /> <description> - Returns the amount of lines of text the Label has. + Returns the number of lines of text the Label has. </description> </method> <method name="get_line_height" qualifiers="const"> @@ -62,7 +62,7 @@ </member> <member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" overrides="Control" enum="Control.MouseFilter" default="2" /> <member name="percent_visible" type="float" setter="set_percent_visible" getter="get_percent_visible" default="1.0"> - Limits the amount of visible characters. If you set [code]percent_visible[/code] to 0.5, only up to half of the text's characters will display on screen. Useful to animate the text in a dialog box. + Limits the number of visible characters. If you set [code]percent_visible[/code] to 0.5, only up to half of the text's characters will display on screen. Useful to animate the text in a dialog box. [b]Note:[/b] Setting this property updates [member visible_characters] based on current [method get_total_character_count]. </member> <member name="size_flags_vertical" type="int" setter="set_v_size_flags" getter="get_v_size_flags" overrides="Control" default="4" /> diff --git a/doc/classes/Label3D.xml b/doc/classes/Label3D.xml index e4dc24d0b5..b741dc6e64 100644 --- a/doc/classes/Label3D.xml +++ b/doc/classes/Label3D.xml @@ -54,7 +54,8 @@ Font configuration used to display text. </member> <member name="font_size" type="int" setter="set_font_size" getter="get_font_size" default="32"> - Font size of the [Label3D]'s text. + Font size of the [Label3D]'s text. To make the font look more detailed when up close, increase [member font_size] while decreasing [member pixel_size] at the same time. + Higher font sizes require more time to render new characters, which can cause stuttering during gameplay. </member> <member name="horizontal_alignment" type="int" setter="set_horizontal_alignment" getter="get_horizontal_alignment" enum="HorizontalAlignment" default="1"> Controls the text's horizontal alignment. Supports left, center, right, and fill, or justify. Set it to one of the [enum HorizontalAlignment] constants. @@ -86,7 +87,7 @@ Text outline size. </member> <member name="pixel_size" type="float" setter="set_pixel_size" getter="get_pixel_size" default="0.005"> - The size of one pixel's width on the label to scale it in 3D. + The size of one pixel's width on the label to scale it in 3D. To make the font look more detailed when up close, increase [member font_size] while decreasing [member pixel_size] at the same time. </member> <member name="render_priority" type="int" setter="set_render_priority" getter="get_render_priority" default="0"> Sets the render priority for the text. Higher priority objects will be sorted in front of lower priority objects. @@ -138,7 +139,7 @@ Represents the size of the [enum DrawFlags] enum. </constant> <constant name="ALPHA_CUT_DISABLED" value="0" enum="AlphaCutMode"> - This mode performs standard alpha blending. It can display translucent areas, but transparency sorting issues may be visible when multiple transparent materials are overlapping. + This mode performs standard alpha blending. It can display translucent areas, but transparency sorting issues may be visible when multiple transparent materials are overlapping. [member GeometryInstance3D.cast_shadow] has no effect when this transparency mode is used; the [Label3D] will never cast shadows. </constant> <constant name="ALPHA_CUT_DISCARD" value="1" enum="AlphaCutMode"> This mode only allows fully transparent or fully opaque pixels. Harsh edges will be visible unless some form of screen-space antialiasing is enabled (see [member ProjectSettings.rendering/anti_aliasing/quality/screen_space_aa]). This mode is also known as [i]alpha testing[/i] or [i]1-bit transparency[/i]. diff --git a/doc/classes/Line2D.xml b/doc/classes/Line2D.xml index f4bf803554..4547c3589f 100644 --- a/doc/classes/Line2D.xml +++ b/doc/classes/Line2D.xml @@ -29,7 +29,7 @@ <method name="get_point_count" qualifiers="const"> <return type="int" /> <description> - Returns the amount of points in the line. + Returns the number of points in the line. </description> </method> <method name="get_point_position" qualifiers="const"> diff --git a/doc/classes/LineEdit.xml b/doc/classes/LineEdit.xml index 9caaed8377..20703f680c 100644 --- a/doc/classes/LineEdit.xml +++ b/doc/classes/LineEdit.xml @@ -183,7 +183,7 @@ Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. </member> <member name="max_length" type="int" setter="set_max_length" getter="get_max_length" default="0"> - Maximum amount of characters that can be entered inside the [LineEdit]. If [code]0[/code], there is no limit. + Maximum number of characters that can be entered inside the [LineEdit]. If [code]0[/code], there is no limit. When a limit is defined, characters that would exceed [member max_length] are truncated. This happens both for existing [member text] contents when setting the max length, or for new text inserted in the [LineEdit], including pasting. If any input text is truncated, the [signal text_change_rejected] signal is emitted with the truncated substring as parameter. [b]Example:[/b] [codeblocks] @@ -417,7 +417,7 @@ The caret's width in pixels. Greater values can be used to improve accessibility by ensuring the caret is easily visible, or to ensure consistency with a large font size. </theme_item> <theme_item name="minimum_character_width" data_type="constant" type="int" default="4"> - Minimum horizontal space for the text (not counting the clear button and content margins). This value is measured in count of 'M' characters (i.e. this amount of 'M' characters can be displayed without scrolling). + Minimum horizontal space for the text (not counting the clear button and content margins). This value is measured in count of 'M' characters (i.e. this number of 'M' characters can be displayed without scrolling). </theme_item> <theme_item name="outline_size" data_type="constant" type="int" default="0"> The size of the text outline. diff --git a/doc/classes/MenuBar.xml b/doc/classes/MenuBar.xml new file mode 100644 index 0000000000..3ef0572e9f --- /dev/null +++ b/doc/classes/MenuBar.xml @@ -0,0 +1,172 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="MenuBar" inherits="Control" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A horizontal menu bar, which displays [PopupMenu]s or system global menu. + </brief_description> + <description> + New items can be created by adding [PopupMenu] nodes to his node. + </description> + <tutorials> + </tutorials> + <methods> + <method name="get_menu_count" qualifiers="const"> + <return type="int" /> + <description> + Returns number of menu items. + </description> + </method> + <method name="get_menu_popup" qualifiers="const"> + <return type="PopupMenu" /> + <param index="0" name="menu" type="int" /> + <description> + Returns [PopupMenu] associated with menu item. + </description> + </method> + <method name="get_menu_title" qualifiers="const"> + <return type="String" /> + <param index="0" name="menu" type="int" /> + <description> + Returns menu item title. + </description> + </method> + <method name="get_menu_tooltip" qualifiers="const"> + <return type="String" /> + <param index="0" name="menu" type="int" /> + <description> + Returns menu item tooltip. + </description> + </method> + <method name="is_menu_disabled" qualifiers="const"> + <return type="bool" /> + <param index="0" name="menu" type="int" /> + <description> + Returns [code]true[/code], if menu item is disabled. + </description> + </method> + <method name="is_menu_hidden" qualifiers="const"> + <return type="bool" /> + <param index="0" name="menu" type="int" /> + <description> + Returns [code]true[/code], if menu item is hidden. + </description> + </method> + <method name="is_native_menu" qualifiers="const"> + <return type="bool" /> + <description> + Returns [code]true[/code], if system global menu is supported and used by this [MenuBar]. + </description> + </method> + <method name="set_disable_shortcuts"> + <return type="void" /> + <param index="0" name="disabled" type="bool" /> + <description> + If [code]true[/code], shortcuts are disabled and cannot be used to trigger the button. + </description> + </method> + <method name="set_menu_disabled"> + <return type="void" /> + <param index="0" name="menu" type="int" /> + <param index="1" name="disabled" type="bool" /> + <description> + If [code]true[/code], menu item is disabled. + </description> + </method> + <method name="set_menu_hidden"> + <return type="void" /> + <param index="0" name="menu" type="int" /> + <param index="1" name="hidden" type="bool" /> + <description> + If [code]true[/code], menu item is hidden. + </description> + </method> + <method name="set_menu_title"> + <return type="void" /> + <param index="0" name="menu" type="int" /> + <param index="1" name="title" type="String" /> + <description> + Sets menu item title. + </description> + </method> + <method name="set_menu_tooltip"> + <return type="void" /> + <param index="0" name="menu" type="int" /> + <param index="1" name="tooltip" type="String" /> + <description> + Sets menu item tooltip. + </description> + </method> + </methods> + <members> + <member name="flat" type="bool" setter="set_flat" getter="is_flat" default="false"> + Flat [MenuBar] don't display item decoration. + </member> + <member name="language" type="String" setter="set_language" getter="get_language" default=""""> + Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. + </member> + <member name="prefer_global_menu" type="bool" setter="set_prefer_global_menu" getter="is_prefer_global_menu" default="true"> + If [code]true[/code], [MenuBar] will use system global menu when supported. + </member> + <member name="shortcut_context" type="Node" setter="set_shortcut_context" getter="get_shortcut_context"> + The [Node] which must be a parent of the focused GUI [Control] for the shortcut to be activated. If [code]null[/code], the shortcut can be activated when any control is focused (a global shortcut). This allows shortcuts to be accepted only when the user has a certain area of the GUI focused. + </member> + <member name="start_index" type="int" setter="set_start_index" getter="get_start_index" default="-1"> + Position in the global menu to insert first [MenuBar] item at. + </member> + <member name="switch_on_hover" type="bool" setter="set_switch_on_hover" getter="is_switch_on_hover" default="true"> + If [code]true[/code], when the cursor hovers above menu item, it will close the current [PopupMenu] and open the other one. + </member> + <member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0"> + Base text writing direction. + </member> + </members> + <theme_items> + <theme_item name="font_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> + Default text [Color] of the menu item. + </theme_item> + <theme_item name="font_disabled_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 0.5)"> + Text [Color] used when the menu item is disabled. + </theme_item> + <theme_item name="font_focus_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> + Text [Color] used when the menu item is focused. Only replaces the normal text color of the menu item. Disabled, hovered, and pressed states take precedence over this color. + </theme_item> + <theme_item name="font_hover_color" data_type="color" type="Color" default="Color(0.95, 0.95, 0.95, 1)"> + Text [Color] used when the menu item is being hovered. + </theme_item> + <theme_item name="font_hover_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + Text [Color] used when the menu item is being hovered and pressed. + </theme_item> + <theme_item name="font_outline_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + The tint of text outline of the menu item. + </theme_item> + <theme_item name="font_pressed_color" data_type="color" type="Color" default="Color(1, 1, 1, 1)"> + Text [Color] used when the menu item is being pressed. + </theme_item> + <theme_item name="h_separation" data_type="constant" type="int" default="4"> + The horizontal space between menu items. + </theme_item> + <theme_item name="outline_size" data_type="constant" type="int" default="0"> + The size of the text outline. + </theme_item> + <theme_item name="font" data_type="font" type="Font"> + [Font] of the menu item's text. + </theme_item> + <theme_item name="font_size" data_type="font_size" type="int"> + Font size of the menu item's text. + </theme_item> + <theme_item name="disabled" data_type="style" type="StyleBox"> + [StyleBox] used when the menu item is disabled. + </theme_item> + <theme_item name="focus" data_type="style" type="StyleBox"> + [StyleBox] used when the menu item is focused. The [code]focus[/code] [StyleBox] is displayed [i]over[/i] the base [StyleBox], so a partially transparent [StyleBox] should be used to ensure the base [StyleBox] remains visible. A [StyleBox] that represents an outline or an underline works well for this purpose. To disable the focus visual effect, assign a [StyleBoxEmpty] resource. Note that disabling the focus visual effect will harm keyboard/controller navigation usability, so this is not recommended for accessibility reasons. + </theme_item> + <theme_item name="hover" data_type="style" type="StyleBox"> + [StyleBox] used when the menu item is being hovered. + </theme_item> + <theme_item name="normal" data_type="style" type="StyleBox"> + Default [StyleBox] for the menu item. + </theme_item> + <theme_item name="pressed" data_type="style" type="StyleBox"> + [StyleBox] used when the menu item is being pressed. + </theme_item> + </theme_items> +</class> diff --git a/doc/classes/Mesh.xml b/doc/classes/Mesh.xml index 0de42b64af..8e98efa6fc 100644 --- a/doc/classes/Mesh.xml +++ b/doc/classes/Mesh.xml @@ -103,7 +103,7 @@ <description> Calculate a [ConvexPolygonShape3D] from the mesh. If [param clean] is [code]true[/code] (default), duplicate and interior vertices are removed automatically. You can set it to [code]false[/code] to make the process faster if not needed. - If [param simplify] is [code]true[/code], the geometry can be further simplified to reduce the amount of vertices. Disabled by default. + If [param simplify] is [code]true[/code], the geometry can be further simplified to reduce the number of vertices. Disabled by default. </description> </method> <method name="create_outline" qualifiers="const"> @@ -142,7 +142,7 @@ <method name="get_surface_count" qualifiers="const"> <return type="int" /> <description> - Returns the amount of surfaces that the [Mesh] holds. + Returns the number of surfaces that the [Mesh] holds. </description> </method> <method name="surface_get_arrays" qualifiers="const"> diff --git a/doc/classes/MeshInstance3D.xml b/doc/classes/MeshInstance3D.xml index 7dfee420d4..618503c8df 100644 --- a/doc/classes/MeshInstance3D.xml +++ b/doc/classes/MeshInstance3D.xml @@ -20,7 +20,7 @@ <description> This helper creates a [StaticBody3D] child node with a [ConvexPolygonShape3D] collision shape calculated from the mesh geometry. It's mainly used for testing. If [param clean] is [code]true[/code] (default), duplicate and interior vertices are removed automatically. You can set it to [code]false[/code] to make the process faster if not needed. - If [param simplify] is [code]true[/code], the geometry can be further simplified to reduce the amount of vertices. Disabled by default. + If [param simplify] is [code]true[/code], the geometry can be further simplified to reduce the number of vertices. Disabled by default. </description> </method> <method name="create_debug_tangents"> diff --git a/doc/classes/MultiMeshInstance3D.xml b/doc/classes/MultiMeshInstance3D.xml index 52cc9cb65f..70fbf235e2 100644 --- a/doc/classes/MultiMeshInstance3D.xml +++ b/doc/classes/MultiMeshInstance3D.xml @@ -5,7 +5,7 @@ </brief_description> <description> [MultiMeshInstance3D] is a specialized node to instance [GeometryInstance3D]s based on a [MultiMesh] resource. - This is useful to optimize the rendering of a high amount of instances of a given mesh (for example trees in a forest or grass strands). + This is useful to optimize the rendering of a high number of instances of a given mesh (for example trees in a forest or grass strands). </description> <tutorials> <link title="Animating thousands of fish with MultiMeshInstance">$DOCS_URL/tutorials/performance/vertex_animation/animating_thousands_of_fish.html</link> diff --git a/doc/classes/NavigationMeshGenerator.xml b/doc/classes/NavigationMeshGenerator.xml index faf777bbc9..4c337db90f 100644 --- a/doc/classes/NavigationMeshGenerator.xml +++ b/doc/classes/NavigationMeshGenerator.xml @@ -6,7 +6,7 @@ <description> This class is responsible for creating and clearing 3D navigation meshes used as [NavigationMesh] resources inside [NavigationRegion3D]. The [NavigationMeshGenerator] has very limited to no use for 2D as the navigation mesh baking process expects 3D node types and 3D source geometry to parse. The entire navigation mesh baking is best done in a separate thread as the voxelization, collision tests and mesh optimization steps involved are very performance and time hungry operations. - Navigation mesh baking happens in multiple steps and the result depends on 3D source geometry and properties of the [NavigationMesh] resource. In the first step, starting from a root node and depending on [NavigationMesh] properties all valid 3D source geometry nodes are collected from the [SceneTree]. Second, all collected nodes are parsed for their relevant 3D geometry data and a combined 3D mesh is build. Due to the many different types of parsable objects, from normal [MeshInstance3D]s to [CSGShape3D]s or various [CollisionObject3D]s, some operations to collect geometry data can trigger [RenderingServer] and [PhysicsServer3D] synchronizations. Server synchronization can have a negative effect on baking time or framerate as it often involves [Mutex] locking for thread security. Many parsable objects and the continuous synchronization with other threaded Servers can increase the baking time significantly. On the other hand only a few but very large and complex objects will take some time to prepare for the Servers which can noticeably stall the next frame render. As a general rule the total amount of parsable objects and their individual size and complexity should be balanced to avoid framerate issues or very long baking times. The combined mesh is then passed to the Recast Navigation Object to test the source geometry for walkable terrain suitable to [NavigationMesh] agent properties by creating a voxel world around the meshes bounding area. + Navigation mesh baking happens in multiple steps and the result depends on 3D source geometry and properties of the [NavigationMesh] resource. In the first step, starting from a root node and depending on [NavigationMesh] properties all valid 3D source geometry nodes are collected from the [SceneTree]. Second, all collected nodes are parsed for their relevant 3D geometry data and a combined 3D mesh is build. Due to the many different types of parsable objects, from normal [MeshInstance3D]s to [CSGShape3D]s or various [CollisionObject3D]s, some operations to collect geometry data can trigger [RenderingServer] and [PhysicsServer3D] synchronizations. Server synchronization can have a negative effect on baking time or framerate as it often involves [Mutex] locking for thread security. Many parsable objects and the continuous synchronization with other threaded Servers can increase the baking time significantly. On the other hand only a few but very large and complex objects will take some time to prepare for the Servers which can noticeably stall the next frame render. As a general rule the total number of parsable objects and their individual size and complexity should be balanced to avoid framerate issues or very long baking times. The combined mesh is then passed to the Recast Navigation Object to test the source geometry for walkable terrain suitable to [NavigationMesh] agent properties by creating a voxel world around the meshes bounding area. The finalized navigation mesh is then returned and stored inside the [NavigationMesh] for use as a resource inside [NavigationRegion3D] nodes. [b]Note:[/b] Using meshes to not only define walkable surfaces but also obstruct navigation baking does not always work. The navigation baking has no concept of what is a geometry "inside" when dealing with mesh source geometry and this is intentional. Depending on current baking parameters, as soon as the obstructing mesh is large enough to fit a navigation mesh area inside, the baking will generate navigation mesh areas that are inside the obstructing source geometry mesh. </description> diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml index 43457e7d24..e4e9a7fea9 100644 --- a/doc/classes/NavigationServer2D.xml +++ b/doc/classes/NavigationServer2D.xml @@ -206,7 +206,7 @@ </method> <method name="map_is_active" qualifiers="const"> <return type="bool" /> - <param index="0" name="nap" type="RID" /> + <param index="0" name="map" type="RID" /> <description> Returns true if the map is active. </description> diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index e5eee4827b..7c6b828aa9 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -231,7 +231,7 @@ </method> <method name="map_is_active" qualifiers="const"> <return type="bool" /> - <param index="0" name="nap" type="RID" /> + <param index="0" name="map" type="RID" /> <description> Returns true if the map is active. </description> diff --git a/doc/classes/Node3D.xml b/doc/classes/Node3D.xml index 8a8a68ec35..e9f1f995a5 100644 --- a/doc/classes/Node3D.xml +++ b/doc/classes/Node3D.xml @@ -134,20 +134,6 @@ Resets this node's transformations (like scale, skew and taper) preserving its rotation and translation by performing Gram-Schmidt orthonormalization on this node's [Transform3D]. </description> </method> - <method name="property_can_revert"> - <return type="bool" /> - <param index="0" name="name" type="String" /> - <description> - Returns [code]true[/code] if the property identified by [param name] can be reverted to a default value. - </description> - </method> - <method name="property_get_revert"> - <return type="Variant" /> - <param index="0" name="name" type="String" /> - <description> - Returns the default value of the Node3D property with given [param name]. - </description> - </method> <method name="rotate"> <return type="void" /> <param index="0" name="axis" type="Vector3" /> diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml index ba219d8603..2e03ac5291 100644 --- a/doc/classes/Object.xml +++ b/doc/classes/Object.xml @@ -64,6 +64,22 @@ Called whenever the object receives a notification, which is identified in [param what] by a constant. The base [Object] has two constants [constant NOTIFICATION_POSTINITIALIZE] and [constant NOTIFICATION_PREDELETE], but subclasses such as [Node] define a lot more notifications which are also received by this method. </description> </method> + <method name="_property_can_revert" qualifiers="virtual"> + <return type="bool" /> + <param index="0" name="property" type="StringName" /> + <description> + Virtual methods that can be overridden to customize the property revert behavior in the editor. + Returns [code]true[/code] if the property identified by [code]name[/code] can be reverted to a default value. Override [method _property_get_revert] to return the actual value. + </description> + </method> + <method name="_property_get_revert" qualifiers="virtual"> + <return type="Variant" /> + <param index="0" name="property" type="StringName" /> + <description> + Virtual methods that can be overridden to customize the property revert behavior in the editor. + Returns the default value of the property identified by [code]name[/code]. [method _property_can_revert] must be overridden as well for this method to be called. + </description> + </method> <method name="_set" qualifiers="virtual"> <return type="bool" /> <param index="0" name="property" type="StringName" /> @@ -367,7 +383,8 @@ <param index="1" name="default" type="Variant" default="null" /> <description> Returns the object's metadata entry for the given [param name]. - Throws error if the entry does not exist, unless [param default] is not [code]null[/code] (in which case the default value will be returned). + Throws error if the entry does not exist, unless [param default] is not [code]null[/code] (in which case the default value will be returned). See also [method has_meta], [method set_meta] and [method remove_meta]. + [b]Note:[/b] Metadata that has a [param name] starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the inspector and should not be edited. </description> </method> <method name="get_meta_list" qualifiers="const"> @@ -412,7 +429,8 @@ <return type="bool" /> <param index="0" name="name" type="StringName" /> <description> - Returns [code]true[/code] if a metadata entry is found with the given [param name]. + Returns [code]true[/code] if a metadata entry is found with the given [param name]. See also [method get_meta], [method set_meta] and [method remove_meta]. + [b]Note:[/b] Metadata that has a [param name] starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the inspector and should not be edited. </description> </method> <method name="has_method" qualifiers="const"> @@ -483,7 +501,8 @@ <return type="void" /> <param index="0" name="name" type="StringName" /> <description> - Removes a given entry from the object's metadata. See also [method set_meta]. + Removes a given entry from the object's metadata. See also [method has_meta], [method get_meta] and [method set_meta]. + [b]Note:[/b] Metadata that has a [param name] starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the inspector and should not be edited. </description> </method> <method name="set"> @@ -546,7 +565,8 @@ <param index="1" name="value" type="Variant" /> <description> Adds, changes or removes a given entry in the object's metadata. Metadata are serialized and can take any [Variant] value. - To remove a given entry from the object's metadata, use [method remove_meta]. Metadata is also removed if its value is set to [code]null[/code]. This means you can also use [code]set_meta("name", null)[/code] to remove metadata for [code]"name"[/code]. + To remove a given entry from the object's metadata, use [method remove_meta]. Metadata is also removed if its value is set to [code]null[/code]. This means you can also use [code]set_meta("name", null)[/code] to remove metadata for [code]"name"[/code]. See also [method has_meta] and [method get_meta]. + [b]Note:[/b] Metadata that has a [param name] starting with an underscore ([code]_[/code]) is considered editor-only. Editor-only metadata is not displayed in the inspector and should not be edited. </description> </method> <method name="set_script"> diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml index b02333667d..efb559522a 100644 --- a/doc/classes/PackedByteArray.xml +++ b/doc/classes/PackedByteArray.xml @@ -5,6 +5,7 @@ </brief_description> <description> An array specifically designed to hold bytes. Packs data tightly, so it saves memory for large array sizes. + [PackedByteArray] also provides methods to encode/decode various types to/from bytes. The way values are encoded is an implementation detail and shouldn't be relied upon when interacting with external apps. </description> <tutorials> </tutorials> @@ -78,66 +79,77 @@ <return type="float" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 64-bit floating point number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0.0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_float" qualifiers="const"> <return type="float" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 32-bit floating point number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0.0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_half" qualifiers="const"> <return type="float" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 16-bit floating point number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0.0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_s16" qualifiers="const"> <return type="int" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 16-bit signed integer number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_s32" qualifiers="const"> <return type="int" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 32-bit signed integer number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_s64" qualifiers="const"> <return type="int" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 64-bit signed integer number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_s8" qualifiers="const"> <return type="int" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 8-bit signed integer number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_u16" qualifiers="const"> <return type="int" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 16-bit unsigned integer number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_u32" qualifiers="const"> <return type="int" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 32-bit unsigned integer number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_u64" qualifiers="const"> <return type="int" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 64-bit unsigned integer number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_u8" qualifiers="const"> <return type="int" /> <param index="0" name="byte_offset" type="int" /> <description> + Decodes a 8-bit unsigned integer number from the bytes starting at [param byte_offset]. Fails if the byte count is insufficient. Returns [code]0[/code] if a valid number can't be decoded. </description> </method> <method name="decode_var" qualifiers="const"> @@ -145,6 +157,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="allow_objects" type="bool" default="false" /> <description> + Decodes a [Variant] from the bytes starting at [param byte_offset]. Returns [code]null[/code] if a valid variant can't be decoded or the value is [Object]-derived and [param allow_objects] is [code]false[/code]. </description> </method> <method name="decode_var_size" qualifiers="const"> @@ -152,6 +165,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="allow_objects" type="bool" default="false" /> <description> + Decodes a size of a [Variant] from the bytes starting at [param byte_offset]. Requires at least 4 bytes of data starting at the offset, otherwise fails. </description> </method> <method name="decompress" qualifiers="const"> @@ -183,6 +197,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="float" /> <description> + Encodes a 64-bit floating point number as bytes at the index of [param byte_offset] bytes. The array must have at least 8 bytes of allocated space, starting at the offset. </description> </method> <method name="encode_float"> @@ -190,6 +205,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="float" /> <description> + Encodes a 32-bit floating point number as bytes at the index of [param byte_offset] bytes. The array must have at least 4 bytes of space, starting at the offset. </description> </method> <method name="encode_half"> @@ -197,6 +213,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="float" /> <description> + Encodes a 16-bit floating point number as bytes at the index of [param byte_offset] bytes. The array must have at least 2 bytes of space, starting at the offset. </description> </method> <method name="encode_s16"> @@ -204,6 +221,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="int" /> <description> + Encodes a 16-bit signed integer number as bytes at the index of [param byte_offset] bytes. The array must have at least 2 bytes of space, starting at the offset. </description> </method> <method name="encode_s32"> @@ -211,6 +229,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="int" /> <description> + Encodes a 32-bit signed integer number as bytes at the index of [param byte_offset] bytes. The array must have at least 2 bytes of space, starting at the offset. </description> </method> <method name="encode_s64"> @@ -218,6 +237,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="int" /> <description> + Encodes a 64-bit signed integer number as bytes at the index of [param byte_offset] bytes. The array must have at least 2 bytes of space, starting at the offset. </description> </method> <method name="encode_s8"> @@ -225,6 +245,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="int" /> <description> + Encodes a 8-bit signed integer number (signed byte) at the index of [param byte_offset] bytes. The array must have at least 1 byte of space, starting at the offset. </description> </method> <method name="encode_u16"> @@ -232,6 +253,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="int" /> <description> + Encodes a 16-bit unsigned integer number as bytes at the index of [param byte_offset] bytes. The array must have at least 2 bytes of space, starting at the offset. </description> </method> <method name="encode_u32"> @@ -239,6 +261,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="int" /> <description> + Encodes a 32-bit unsigned integer number as bytes at the index of [param byte_offset] bytes. The array must have at least 4 bytes of space, starting at the offset. </description> </method> <method name="encode_u64"> @@ -246,6 +269,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="int" /> <description> + Encodes a 64-bit unsigned integer number as bytes at the index of [param byte_offset] bytes. The array must have at least 8 bytes of space, starting at the offset. </description> </method> <method name="encode_u8"> @@ -253,6 +277,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="value" type="int" /> <description> + Encodes a 8-bit unsigned integer number (byte) at the index of [param byte_offset] bytes. The array must have at least 1 byte of space, starting at the offset. </description> </method> <method name="encode_var"> @@ -261,6 +286,7 @@ <param index="1" name="value" type="Variant" /> <param index="2" name="allow_objects" type="bool" default="false" /> <description> + Encodes a [Variant] at the index of [param byte_offset] bytes. A sufficient space must be allocated, depending on the encoded variant's size. If [param allow_objects] is [code]false[/code], [Object]-derived values are not permitted and will instead be serialized as ID-only. </description> </method> <method name="fill"> @@ -314,6 +340,7 @@ <param index="0" name="byte_offset" type="int" /> <param index="1" name="allow_objects" type="bool" default="false" /> <description> + Returns [code]true[/code] if a valid [Variant] value can be decoded at the [param byte_offset]. Returns [code]false[/code] othewrise or when the value is [Object]-derived and [param allow_objects] is [code]false[/code]. </description> </method> <method name="hex_encode" qualifiers="const"> @@ -449,24 +476,29 @@ <return type="bool" /> <param index="0" name="right" type="PackedByteArray" /> <description> + Returns [code]true[/code] if contents of the arrays differ. </description> </operator> <operator name="operator +"> <return type="PackedByteArray" /> <param index="0" name="right" type="PackedByteArray" /> <description> + Returns a new [PackedByteArray] with contents of [param right] added at the end of this array. For better performance, consider using [method append_array] instead. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="PackedByteArray" /> <description> + Returns [code]true[/code] if contents of both arrays are the same, i.e. they have all equal bytes at the corresponding indices. </description> </operator> <operator name="operator []"> <return type="int" /> <param index="0" name="index" type="int" /> <description> + Returns the byte at index [param index]. Negative indices can be used to access the elements starting from the end. Using index out of array's bounds will result in an error. + Note that the byte is returned as a 64-bit [int]. </description> </operator> </operators> diff --git a/doc/classes/PackedColorArray.xml b/doc/classes/PackedColorArray.xml index c694927175..a2dc8e8f1d 100644 --- a/doc/classes/PackedColorArray.xml +++ b/doc/classes/PackedColorArray.xml @@ -177,6 +177,7 @@ <method name="to_byte_array" qualifiers="const"> <return type="PackedByteArray" /> <description> + Returns a [PackedByteArray] with each color encoded as bytes. </description> </method> </methods> @@ -185,24 +186,28 @@ <return type="bool" /> <param index="0" name="right" type="PackedColorArray" /> <description> + Returns [code]true[/code] if contents of the arrays differ. </description> </operator> <operator name="operator +"> <return type="PackedColorArray" /> <param index="0" name="right" type="PackedColorArray" /> <description> + Returns a new [PackedColorArray] with contents of [param right] added at the end of this array. For better performance, consider using [method append_array] instead. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="PackedColorArray" /> <description> + Returns [code]true[/code] if contents of both arrays are the same, i.e. they have all equal [Color]s at the corresponding indices. </description> </operator> <operator name="operator []"> <return type="Color" /> <param index="0" name="index" type="int" /> <description> + Returns the [Color] at index [param index]. Negative indices can be used to access the elements starting from the end. Using index out of array's bounds will result in an error. </description> </operator> </operators> diff --git a/doc/classes/PackedFloat32Array.xml b/doc/classes/PackedFloat32Array.xml index 41d0679099..d350d64f38 100644 --- a/doc/classes/PackedFloat32Array.xml +++ b/doc/classes/PackedFloat32Array.xml @@ -4,7 +4,7 @@ A packed array of 32-bit floating-point values. </brief_description> <description> - An array specifically designed to hold 32-bit floating-point values. Packs data tightly, so it saves memory for large array sizes. + An array specifically designed to hold 32-bit floating-point values (float). Packs data tightly, so it saves memory for large array sizes. If you need to pack 64-bit floats tightly, see [PackedFloat64Array]. </description> <tutorials> @@ -188,24 +188,29 @@ <return type="bool" /> <param index="0" name="right" type="PackedFloat32Array" /> <description> + Returns [code]true[/code] if contents of the arrays differ. </description> </operator> <operator name="operator +"> <return type="PackedFloat32Array" /> <param index="0" name="right" type="PackedFloat32Array" /> <description> + Returns a new [PackedFloat32Array] with contents of [param right] added at the end of this array. For better performance, consider using [method append_array] instead. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="PackedFloat32Array" /> <description> + Returns [code]true[/code] if contents of both arrays are the same, i.e. they have all equal floats at the corresponding indices. </description> </operator> <operator name="operator []"> <return type="float" /> <param index="0" name="index" type="int" /> <description> + Returns the [float] at index [param index]. Negative indices can be used to access the elements starting from the end. Using index out of array's bounds will result in an error. + Note that [float] type is 64-bit, unlike the values stored in the array. </description> </operator> </operators> diff --git a/doc/classes/PackedFloat64Array.xml b/doc/classes/PackedFloat64Array.xml index bedbe3603c..690cb15fa7 100644 --- a/doc/classes/PackedFloat64Array.xml +++ b/doc/classes/PackedFloat64Array.xml @@ -4,7 +4,7 @@ A packed array of 64-bit floating-point values. </brief_description> <description> - An array specifically designed to hold 64-bit floating-point values. Packs data tightly, so it saves memory for large array sizes. + An array specifically designed to hold 64-bit floating-point values (double). Packs data tightly, so it saves memory for large array sizes. If you only need to pack 32-bit floats tightly, see [PackedFloat32Array] for a more memory-friendly alternative. </description> <tutorials> @@ -188,24 +188,28 @@ <return type="bool" /> <param index="0" name="right" type="PackedFloat64Array" /> <description> + Returns [code]true[/code] if contents of the arrays differ. </description> </operator> <operator name="operator +"> <return type="PackedFloat64Array" /> <param index="0" name="right" type="PackedFloat64Array" /> <description> + Returns a new [PackedFloat64Array] with contents of [param right] added at the end of this array. For better performance, consider using [method append_array] instead. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="PackedFloat64Array" /> <description> + Returns [code]true[/code] if contents of both arrays are the same, i.e. they have all equal doubles at the corresponding indices. </description> </operator> <operator name="operator []"> <return type="float" /> <param index="0" name="index" type="int" /> <description> + Returns the [float] at index [param index]. Negative indices can be used to access the elements starting from the end. Using index out of array's bounds will result in an error. </description> </operator> </operators> diff --git a/doc/classes/PackedInt32Array.xml b/doc/classes/PackedInt32Array.xml index db4c8c0937..2f9032e214 100644 --- a/doc/classes/PackedInt32Array.xml +++ b/doc/classes/PackedInt32Array.xml @@ -188,24 +188,29 @@ <return type="bool" /> <param index="0" name="right" type="PackedInt32Array" /> <description> + Returns [code]true[/code] if contents of the arrays differ. </description> </operator> <operator name="operator +"> <return type="PackedInt32Array" /> <param index="0" name="right" type="PackedInt32Array" /> <description> + Returns a new [PackedInt32Array] with contents of [param right] added at the end of this array. For better performance, consider using [method append_array] instead. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="PackedInt32Array" /> <description> + Returns [code]true[/code] if contents of both arrays are the same, i.e. they have all equal ints at the corresponding indices. </description> </operator> <operator name="operator []"> <return type="int" /> <param index="0" name="index" type="int" /> <description> + Returns the [int] at index [param index]. Negative indices can be used to access the elements starting from the end. Using index out of array's bounds will result in an error. + Note that [int] type is 64-bit, unlike the values stored in the array. </description> </operator> </operators> diff --git a/doc/classes/PackedInt64Array.xml b/doc/classes/PackedInt64Array.xml index 56d72692a2..5f762cde3e 100644 --- a/doc/classes/PackedInt64Array.xml +++ b/doc/classes/PackedInt64Array.xml @@ -188,24 +188,28 @@ <return type="bool" /> <param index="0" name="right" type="PackedInt64Array" /> <description> + Returns [code]true[/code] if contents of the arrays differ. </description> </operator> <operator name="operator +"> <return type="PackedInt64Array" /> <param index="0" name="right" type="PackedInt64Array" /> <description> + Returns a new [PackedInt64Array] with contents of [param right] added at the end of this array. For better performance, consider using [method append_array] instead. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="PackedInt64Array" /> <description> + Returns [code]true[/code] if contents of both arrays are the same, i.e. they have all equal ints at the corresponding indices. </description> </operator> <operator name="operator []"> <return type="int" /> <param index="0" name="index" type="int" /> <description> + Returns the [int] at index [param index]. Negative indices can be used to access the elements starting from the end. Using index out of array's bounds will result in an error. </description> </operator> </operators> diff --git a/doc/classes/PackedStringArray.xml b/doc/classes/PackedStringArray.xml index b58a3b2553..2c3376d659 100644 --- a/doc/classes/PackedStringArray.xml +++ b/doc/classes/PackedStringArray.xml @@ -184,6 +184,7 @@ <method name="to_byte_array" qualifiers="const"> <return type="PackedByteArray" /> <description> + Returns a [PackedByteArray] with each string encoded as bytes. </description> </method> </methods> @@ -192,24 +193,28 @@ <return type="bool" /> <param index="0" name="right" type="PackedStringArray" /> <description> + Returns [code]true[/code] if contents of the arrays differ. </description> </operator> <operator name="operator +"> <return type="PackedStringArray" /> <param index="0" name="right" type="PackedStringArray" /> <description> + Returns a new [PackedStringArray] with contents of [param right] added at the end of this array. For better performance, consider using [method append_array] instead. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="PackedStringArray" /> <description> + Returns [code]true[/code] if contents of both arrays are the same, i.e. they have all equal [String]s at the corresponding indices. </description> </operator> <operator name="operator []"> <return type="String" /> <param index="0" name="index" type="int" /> <description> + Returns the [String] at index [param index]. Negative indices can be used to access the elements starting from the end. Using index out of array's bounds will result in an error. </description> </operator> </operators> diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml index 7cf63d3d5e..25650ef40a 100644 --- a/doc/classes/PackedVector2Array.xml +++ b/doc/classes/PackedVector2Array.xml @@ -178,6 +178,7 @@ <method name="to_byte_array" qualifiers="const"> <return type="PackedByteArray" /> <description> + Returns a [PackedByteArray] with each vector encoded as bytes. </description> </method> </methods> @@ -186,30 +187,35 @@ <return type="bool" /> <param index="0" name="right" type="PackedVector2Array" /> <description> + Returns [code]true[/code] if contents of the arrays differ. </description> </operator> <operator name="operator *"> <return type="PackedVector2Array" /> <param index="0" name="right" type="Transform2D" /> <description> + Transforms (multiplies) all vectors in the array by the [Transform2D] matrix. </description> </operator> <operator name="operator +"> <return type="PackedVector2Array" /> <param index="0" name="right" type="PackedVector2Array" /> <description> + Returns a new [PackedVector2Array] with contents of [param right] added at the end of this array. For better performance, consider using [method append_array] instead. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="PackedVector2Array" /> <description> + Returns [code]true[/code] if contents of both arrays are the same, i.e. they have all equal [Vector2]s at the corresponding indices. </description> </operator> <operator name="operator []"> <return type="Vector2" /> <param index="0" name="index" type="int" /> <description> + Returns the [Vector2] at index [param index]. Negative indices can be used to access the elements starting from the end. Using index out of array's bounds will result in an error. </description> </operator> </operators> diff --git a/doc/classes/PackedVector3Array.xml b/doc/classes/PackedVector3Array.xml index 5ab42474f0..22979a5a37 100644 --- a/doc/classes/PackedVector3Array.xml +++ b/doc/classes/PackedVector3Array.xml @@ -177,6 +177,7 @@ <method name="to_byte_array" qualifiers="const"> <return type="PackedByteArray" /> <description> + Returns a [PackedByteArray] with each vector encoded as bytes. </description> </method> </methods> @@ -185,30 +186,35 @@ <return type="bool" /> <param index="0" name="right" type="PackedVector3Array" /> <description> + Returns [code]true[/code] if contents of the arrays differ. </description> </operator> <operator name="operator *"> <return type="PackedVector3Array" /> <param index="0" name="right" type="Transform3D" /> <description> + Transforms (multiplies) all vectors in the array by the [Transform3D] matrix. </description> </operator> <operator name="operator +"> <return type="PackedVector3Array" /> <param index="0" name="right" type="PackedVector3Array" /> <description> + Returns a new [PackedVector3Array] with contents of [param right] added at the end of this array. For better performance, consider using [method append_array] instead. </description> </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="PackedVector3Array" /> <description> + Returns [code]true[/code] if contents of both arrays are the same, i.e. they have all equal [Vector3]s at the corresponding indices. </description> </operator> <operator name="operator []"> <return type="Vector3" /> <param index="0" name="index" type="int" /> <description> + Returns the [Vector3] at index [param index]. Negative indices can be used to access the elements starting from the end. Using index out of array's bounds will result in an error. </description> </operator> </operators> diff --git a/doc/classes/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml index d7b5f24f4a..920b56c8c6 100644 --- a/doc/classes/PhysicsServer2D.xml +++ b/doc/classes/PhysicsServer2D.xml @@ -553,7 +553,7 @@ <param index="0" name="body" type="RID" /> <param index="1" name="amount" type="int" /> <description> - Sets the maximum contacts to report. Bodies can keep a log of the contacts with other bodies, this is enabled by setting the maximum amount of contacts reported to a number greater than 0. + Sets the maximum contacts to report. Bodies can keep a log of the contacts with other bodies. This is enabled by setting the maximum number of contacts reported to a number greater than 0. </description> </method> <method name="body_set_mode"> @@ -882,7 +882,7 @@ Constant to set/get the default solver bias for all physics constraints. A solver bias is a factor controlling how much two objects "rebound", after violating a constraint, to avoid leaving them in that state because of numerical imprecision. </constant> <constant name="SPACE_PARAM_SOLVER_ITERATIONS" value="8" enum="SpaceParameter"> - Constant to set/get the number of solver iterations for all contacts and constraints. The greater the amount of iterations, the more accurate the collisions will be. However, a greater amount of iterations requires more CPU power, which can decrease performance. + Constant to set/get the number of solver iterations for all contacts and constraints. The greater the number of iterations, the more accurate the collisions will be. However, a greater number of iterations requires more CPU power, which can decrease performance. </constant> <constant name="SHAPE_WORLD_BOUNDARY" value="0" enum="ShapeType"> This is the constant for creating world boundary shapes. A world boundary shape is an [i]infinite[/i] line with an origin point, and a normal. Thus, it can be used for front/behind checks. diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index c5456f7536..b2456c69ec 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -549,7 +549,7 @@ <param index="0" name="body" type="RID" /> <param index="1" name="amount" type="int" /> <description> - Sets the maximum contacts to report. Bodies can keep a log of the contacts with other bodies, this is enabled by setting the maximum amount of contacts reported to a number greater than 0. + Sets the maximum contacts to report. Bodies can keep a log of the contacts with other bodies. This is enabled by setting the maximum number of contacts reported to a number greater than 0. </description> </method> <method name="body_set_mode"> @@ -1422,7 +1422,7 @@ Constant to set/get the maximum time of activity. A body marked as potentially inactive for both linear and angular velocity will be put to sleep after this time. </constant> <constant name="SPACE_PARAM_SOLVER_ITERATIONS" value="7" enum="SpaceParameter"> - Constant to set/get the number of solver iterations for contacts and constraints. The greater the amount of iterations, the more accurate the collisions and constraints will be. However, a greater amount of iterations requires more CPU power, which can decrease performance. + Constant to set/get the number of solver iterations for contacts and constraints. The greater the number of iterations, the more accurate the collisions and constraints will be. However, a greater number of iterations requires more CPU power, which can decrease performance. </constant> <constant name="BODY_AXIS_LINEAR_X" value="1" enum="BodyAxis"> </constant> diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml index 26bc765ef4..0f47bea5df 100644 --- a/doc/classes/PopupMenu.xml +++ b/doc/classes/PopupMenu.xml @@ -195,13 +195,6 @@ Returns the accelerator of the item at the given [param index]. Accelerators are special combinations of keys that activate the item, no matter which control is focused. </description> </method> - <method name="get_item_horizontal_offset" qualifiers="const"> - <return type="int" /> - <param index="0" name="index" type="int" /> - <description> - Returns the horizontal offset of the item at the given [param index]. - </description> - </method> <method name="get_item_icon" qualifiers="const"> <return type="Texture2D" /> <param index="0" name="index" type="int" /> @@ -216,6 +209,13 @@ Returns the id of the item at the given [param index]. [code]id[/code] can be manually assigned, while index can not. </description> </method> + <method name="get_item_indent" qualifiers="const"> + <return type="int" /> + <param index="0" name="index" type="int" /> + <description> + Returns the horizontal offset of the item at the given [param index]. + </description> + </method> <method name="get_item_index" qualifiers="const"> <return type="int" /> <param index="0" name="id" type="int" /> @@ -388,14 +388,6 @@ Enables/disables the item at the given [param index]. When it is disabled, it can't be selected and its action can't be invoked. </description> </method> - <method name="set_item_horizontal_offset"> - <return type="void" /> - <param index="0" name="index" type="int" /> - <param index="1" name="offset" type="int" /> - <description> - Sets the horizontal offset of the item at the given [param index]. - </description> - </method> <method name="set_item_icon"> <return type="void" /> <param index="0" name="index" type="int" /> @@ -413,6 +405,14 @@ The [param id] is used in [signal id_pressed] and [signal id_focused] signals. </description> </method> + <method name="set_item_indent"> + <return type="void" /> + <param index="0" name="index" type="int" /> + <param index="1" name="indent" type="int" /> + <description> + Sets the horizontal offset of the item at the given [param index]. + </description> + </method> <method name="set_item_language"> <return type="void" /> <param index="0" name="index" type="int" /> @@ -540,6 +540,10 @@ Emitted when an item of some [param index] is pressed or its accelerator is activated. </description> </signal> + <signal name="menu_changed"> + <description> + </description> + </signal> </signals> <theme_items> <theme_item name="font_accelerator_color" data_type="color" type="Color" default="Color(0.7, 0.7, 0.7, 0.8)"> @@ -566,6 +570,9 @@ <theme_item name="h_separation" data_type="constant" type="int" default="4"> The horizontal space between the item's elements. </theme_item> + <theme_item name="indent" data_type="constant" type="int" default="10"> + Width of the single indentation level. + </theme_item> <theme_item name="item_end_padding" data_type="constant" type="int" default="2"> </theme_item> <theme_item name="item_start_padding" data_type="constant" type="int" default="2"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 9dd41d270e..9e2c3440c7 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -129,20 +129,6 @@ Returns the localized path (starting with [code]res://[/code]) corresponding to the absolute, native OS [param path]. See also [method globalize_path]. </description> </method> - <method name="property_can_revert"> - <return type="bool" /> - <param index="0" name="name" type="String" /> - <description> - Returns [code]true[/code] if the specified property exists and its initial value differs from the current value. - </description> - </method> - <method name="property_get_revert"> - <return type="Variant" /> - <param index="0" name="name" type="String" /> - <description> - Returns the specified property's initial value. Returns [code]null[/code] if the property does not exist. - </description> - </method> <method name="save"> <return type="int" enum="Error" /> <description> @@ -292,6 +278,8 @@ </member> <member name="audio/driver/driver" type="String" setter="" getter=""> Specifies the audio driver to use. This setting is platform-dependent as each platform supports different audio drivers. If left empty, the default audio driver will be used. + The [code]Dummy[/code] audio driver disables all audio playback and recording, which is useful for non-game applications as it reduces CPU usage. It also prevents the engine from appearing as an application playing audio in the OS' audio mixer. + [b]Note:[/b] The driver in use can be overridden at runtime via the [code]--audio-driver[/code] command line argument. </member> <member name="audio/driver/enable_input" type="bool" setter="" getter="" default="false"> If [code]true[/code], microphone input will be allowed. This requires appropriate permissions to be set when exporting to Android or iOS. @@ -344,7 +332,7 @@ Path to logs within the project. Using an [code]user://[/code] path is recommended. </member> <member name="debug/file_logging/max_log_files" type="int" setter="" getter="" default="5"> - Specifies the maximum amount of log files allowed (used for rotation). + Specifies the maximum number of log files allowed (used for rotation). </member> <member name="debug/gdscript/warnings/assert_always_false" type="int" setter="" getter="" default="1"> If [code]enabled[/code], prints a warning or an error when an [code]assert[/code] call always returns false. @@ -466,7 +454,7 @@ Maximum call stack allowed for debugging GDScript. </member> <member name="debug/settings/profiler/max_functions" type="int" setter="" getter="" default="16384"> - Maximum amount of functions per frame allowed when profiling. + Maximum number of functions per frame allowed when profiling. </member> <member name="debug/settings/stdout/print_fps" type="bool" setter="" getter="" default="false"> Print frames per second to standard output every second. @@ -576,19 +564,19 @@ Allows the window to be resizable by default. [b]Note:[/b] This setting is ignored on iOS. </member> - <member name="display/window/size/viewport_height" type="int" setter="" getter="" default="600"> - Sets the game's main viewport height. On desktop platforms, this is also the initial window height. + <member name="display/window/size/viewport_height" type="int" setter="" getter="" default="648"> + Sets the game's main viewport height. On desktop platforms, this is also the initial window height, represented by an indigo-colored rectangle in the 2D editor. Stretch mode settings also use this as a reference when using the [code]canvas_items[/code] or [code]viewport[/code] stretch modes. See also [member display/window/size/viewport_width], [member display/window/size/window_width_override] and [member display/window/size/window_height_override]. </member> - <member name="display/window/size/viewport_width" type="int" setter="" getter="" default="1024"> - Sets the game's main viewport width. On desktop platforms, this is also the initial window width. + <member name="display/window/size/viewport_width" type="int" setter="" getter="" default="1152"> + Sets the game's main viewport width. On desktop platforms, this is also the initial window width, represented by an indigo-colored rectangle in the 2D editor. Stretch mode settings also use this as a reference when using the [code]canvas_items[/code] or [code]viewport[/code] stretch modes. See also [member display/window/size/viewport_height], [member display/window/size/window_width_override] and [member display/window/size/window_height_override]. </member> <member name="display/window/size/window_height_override" type="int" setter="" getter="" default="0"> - On desktop platforms, sets the game's initial window height. - [b]Note:[/b] By default, or when set to 0, the initial window height is the [member display/window/size/viewport_height]. This setting is ignored on iOS, Android, and HTML5. + On desktop platforms, overrides the game's initial window height. See also [member display/window/size/window_width_override], [member display/window/size/viewport_width] and [member display/window/size/viewport_height]. + [b]Note:[/b] By default, or when set to [code]0[/code], the initial window height is the [member display/window/size/viewport_height]. This setting is ignored on iOS, Android, and HTML5. </member> <member name="display/window/size/window_width_override" type="int" setter="" getter="" default="0"> - On desktop platforms, sets the game's initial window width. - [b]Note:[/b] By default, or when set to 0, the initial window width is the viewport [member display/window/size/viewport_width]. This setting is ignored on iOS, Android, and HTML5. + On desktop platforms, overrides the game's initial window width. See also [member display/window/size/window_height_override], [member display/window/size/viewport_width] and [member display/window/size/viewport_height]. + [b]Note:[/b] By default, or when set to [code]0[/code], the initial window width is the viewport [member display/window/size/viewport_width]. This setting is ignored on iOS, Android, and HTML5. </member> <member name="display/window/vsync/vsync_mode" type="int" setter="" getter="" default="1"> Sets the V-Sync mode for the main game window. @@ -867,6 +855,7 @@ </member> <member name="input_devices/pen_tablet/driver" type="String" setter="" getter=""> Specifies the tablet driver to use. If left empty, the default driver will be used. + [b]Note:[/b] The driver in use can be overridden at runtime via the [code]--tablet-driver[/code] command line argument. </member> <member name="input_devices/pen_tablet/driver.windows" type="String" setter="" getter=""> Override for [member input_devices/pen_tablet/driver] on Windows. @@ -930,6 +919,9 @@ </member> <member name="internationalization/rendering/text_driver" type="String" setter="" getter="" default=""""> Specifies the [TextServer] to use. If left empty, the default will be used. + "ICU / HarfBuzz / Graphite" is the most advanced text driver, supporting right-to-left typesetting and complex scripts (for languages like Arabic, Hebrew, etc). The "Fallback" text driver does not support right-to-left typesetting and complex scripts. + [b]Note:[/b] The driver in use can be overridden at runtime via the [code]--text-driver[/code] command line argument. + [b]Note:[/b] There is an additional [code]Dummy[/code] text driver available, which disables all text rendering and font-related functionality. This driver is not listed in the project settings, but it can be enabled when running the editor or project using the [code]--text-driver Dummy[/code] command line argument. </member> <member name="layer_names/2d_navigation/layer_1" type="String" setter="" getter="" default=""""> Optional name for the 2D navigation layer 1. If left empty, the layer will display as "Layer 1". @@ -1441,20 +1433,6 @@ <member name="memory/limits/multithreaded_server/rid_pool_prealloc" type="int" setter="" getter="" default="60"> This is used by servers when used in multi-threading mode (servers and visual). RIDs are preallocated to avoid stalling the server requesting them on threads. If servers get stalled too often when loading resources in a thread, increase this number. </member> - <member name="mono/debugger_agent/port" type="int" setter="" getter="" default="23685"> - </member> - <member name="mono/debugger_agent/wait_for_debugger" type="bool" setter="" getter="" default="false"> - </member> - <member name="mono/debugger_agent/wait_timeout" type="int" setter="" getter="" default="3000"> - </member> - <member name="mono/profiler/args" type="String" setter="" getter="" default=""log:calls,alloc,sample,output=output.mlpd""> - </member> - <member name="mono/profiler/enabled" type="bool" setter="" getter="" default="false"> - </member> - <member name="mono/runtime/unhandled_exception_policy" type="int" setter="" getter="" default="0"> - The policy to use for unhandled Mono (C#) exceptions. The default "Terminate Application" exits the project as soon as an unhandled exception is thrown. "Log Error" logs an error message to the console instead, and will not interrupt the project execution when an unhandled exception is thrown. - [b]Note:[/b] The unhandled exception policy is always set to "Log Error" in the editor, which also includes C# [code]tool[/code] scripts running within the editor as well as editor plugin code. - </member> <member name="navigation/2d/default_cell_size" type="int" setter="" getter="" default="1"> Default cell size for 2D navigation maps. See [method NavigationServer2D.map_set_cell_size]. </member> @@ -1468,13 +1446,13 @@ Default edge connection margin for 3D navigation maps. See [method NavigationServer3D.map_set_edge_connection_margin]. </member> <member name="network/limits/debugger/max_chars_per_second" type="int" setter="" getter="" default="32768"> - Maximum amount of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. + Maximum number of characters allowed to send as output from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. </member> <member name="network/limits/debugger/max_errors_per_second" type="int" setter="" getter="" default="400"> Maximum number of errors allowed to be sent from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. </member> <member name="network/limits/debugger/max_queued_messages" type="int" setter="" getter="" default="2048"> - Maximum amount of messages in the debugger queue. Over this value, content is dropped. This helps to limit the debugger memory usage. + Maximum number of messages in the debugger queue. Over this value, content is dropped. This helps to limit the debugger memory usage. </member> <member name="network/limits/debugger/max_warnings_per_second" type="int" setter="" getter="" default="400"> Maximum number of warnings allowed to be sent from the debugger. Over this value, content is dropped. This helps not to stall the debugger connection. @@ -1565,7 +1543,7 @@ Individual shapes can have a specific bias value (see [member Shape2D.custom_solver_bias]). </member> <member name="physics/2d/solver/solver_iterations" type="int" setter="" getter="" default="16"> - Number of solver iterations for all contacts and constraints. The greater the amount of iterations, the more accurate the collisions will be. However, a greater amount of iterations requires more CPU power, which can decrease performance. See [constant PhysicsServer2D.SPACE_PARAM_SOLVER_ITERATIONS]. + Number of solver iterations for all contacts and constraints. The greater the number of iterations, the more accurate the collisions will be. However, a greater number of iterations requires more CPU power, which can decrease performance. See [constant PhysicsServer2D.SPACE_PARAM_SOLVER_ITERATIONS]. </member> <member name="physics/2d/time_before_sleep" type="float" setter="" getter="" default="0.5"> Time (in seconds) of inactivity before which a 2D physics body will put to sleep. See [constant PhysicsServer2D.SPACE_PARAM_BODY_TIME_TO_SLEEP]. @@ -1633,7 +1611,7 @@ Individual shapes can have a specific bias value (see [member Shape3D.custom_solver_bias]). </member> <member name="physics/3d/solver/solver_iterations" type="int" setter="" getter="" default="16"> - Number of solver iterations for all contacts and constraints. The greater the amount of iterations, the more accurate the collisions will be. However, a greater amount of iterations requires more CPU power, which can decrease performance. See [constant PhysicsServer3D.SPACE_PARAM_SOLVER_ITERATIONS]. + Number of solver iterations for all contacts and constraints. The greater the number of iterations, the more accurate the collisions will be. However, a greater number of iterations requires more CPU power, which can decrease performance. See [constant PhysicsServer3D.SPACE_PARAM_SOLVER_ITERATIONS]. </member> <member name="physics/3d/time_before_sleep" type="float" setter="" getter="" default="0.5"> Time (in seconds) of inactivity before which a 3D physics body will put to sleep. See [constant PhysicsServer3D.SPACE_PARAM_BODY_TIME_TO_SLEEP]. @@ -1836,7 +1814,7 @@ Max number of omnilights and spotlights renderable per object. At the default value of 8, this means that each surface can be affected by up to 8 omnilights and 8 spotlights. This is further limited by hardware support and [member rendering/limits/opengl/max_renderable_lights]. Setting this low will slightly reduce memory usage, may decrease shader compile times, and may result in faster rendering on low-end, mobile, or web devices. </member> <member name="rendering/limits/opengl/max_renderable_elements" type="int" setter="" getter="" default="65536"> - Max amount of elements renderable in a frame. If more elements than this are visible per frame, they will not be drawn. Keep in mind elements refer to mesh surfaces and not meshes themselves. Setting this low will slightly reduce memory usage and may decrease shader compile times, particularly on web. For most uses, the default value is suitable, but consider lowering as much as possible on web export. + Max number of elements renderable in a frame. If more elements than this are visible per frame, they will not be drawn. Keep in mind elements refer to mesh surfaces and not meshes themselves. Setting this low will slightly reduce memory usage and may decrease shader compile times, particularly on web. For most uses, the default value is suitable, but consider lowering as much as possible on web export. </member> <member name="rendering/limits/opengl/max_renderable_lights" type="int" setter="" getter="" default="32"> Max number of positional lights renderable in a frame. If more lights than this number are used, they will be ignored. Setting this low will slightly reduce memory usage and may decrease shader compile times, particularly on web. For most uses, the default value is suitable, but consider lowering as much as possible on web export. diff --git a/doc/classes/ProxyTexture.xml b/doc/classes/ProxyTexture.xml deleted file mode 100644 index 778e3f3f69..0000000000 --- a/doc/classes/ProxyTexture.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="ProxyTexture" inherits="Texture2D" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> - <brief_description> - </brief_description> - <description> - </description> - <tutorials> - </tutorials> - <members> - <member name="base" type="Texture2D" setter="set_base" getter="get_base"> - </member> - </members> -</class> diff --git a/doc/classes/Quaternion.xml b/doc/classes/Quaternion.xml index 6fb5b6d181..bc0ffbefe2 100644 --- a/doc/classes/Quaternion.xml +++ b/doc/classes/Quaternion.xml @@ -71,7 +71,7 @@ <param index="0" name="to" type="Quaternion" /> <description> Returns the angle between this quaternion and [param to]. This is the magnitude of the angle you would need to rotate by to get from one to the other. - [b]Note:[/b] This method has an abnormally high amount of floating-point error, so methods such as [code]is_zero_approx[/code] will not work reliably. + [b]Note:[/b] This method has an abnormally high number of floating-point errors, so methods such as [code]is_zero_approx[/code] will not work reliably. </description> </method> <method name="dot" qualifiers="const"> diff --git a/doc/classes/RayCast2D.xml b/doc/classes/RayCast2D.xml index 4f4395a433..08627b0bd5 100644 --- a/doc/classes/RayCast2D.xml +++ b/doc/classes/RayCast2D.xml @@ -47,6 +47,12 @@ Returns the first object that the ray intersects, or [code]null[/code] if no object is intersecting the ray (i.e. [method is_colliding] returns [code]false[/code]). </description> </method> + <method name="get_collider_rid" qualifiers="const"> + <return type="RID" /> + <description> + Returns the [RID] of the first object that the ray intersects, or an empty [RID] if no object is intersecting the ray (i.e. [method is_colliding] returns [code]false[/code]). + </description> + </method> <method name="get_collider_shape" qualifiers="const"> <return type="int" /> <description> diff --git a/doc/classes/RayCast3D.xml b/doc/classes/RayCast3D.xml index 7cc6fc55cd..1bcd6f1f31 100644 --- a/doc/classes/RayCast3D.xml +++ b/doc/classes/RayCast3D.xml @@ -48,6 +48,12 @@ Returns the first object that the ray intersects, or [code]null[/code] if no object is intersecting the ray (i.e. [method is_colliding] returns [code]false[/code]). </description> </method> + <method name="get_collider_rid" qualifiers="const"> + <return type="RID" /> + <description> + Returns the [RID] of the first object that the ray intersects, or an empty [RID] if no object is intersecting the ray (i.e. [method is_colliding] returns [code]false[/code]). + </description> + </method> <method name="get_collider_shape" qualifiers="const"> <return type="int" /> <description> diff --git a/doc/classes/ReflectionProbe.xml b/doc/classes/ReflectionProbe.xml index ff66a89cb7..fee48dd246 100644 --- a/doc/classes/ReflectionProbe.xml +++ b/doc/classes/ReflectionProbe.xml @@ -50,7 +50,7 @@ [b]Note:[/b] [member mesh_lod_threshold] does not affect [GeometryInstance3D] visibility ranges (also known as "manual" LOD or hierarchical LOD). </member> <member name="origin_offset" type="Vector3" setter="set_origin_offset" getter="get_origin_offset" default="Vector3(0, 0, 0)"> - Sets the origin offset to be used when this [ReflectionProbe] is in [member box_projection] mode. This can be set to a non-zero value to ensure a reflection fits a rectangle-shaped room, while reducing the amount of objects that "get in the way" of the reflection. + Sets the origin offset to be used when this [ReflectionProbe] is in [member box_projection] mode. This can be set to a non-zero value to ensure a reflection fits a rectangle-shaped room, while reducing the number of objects that "get in the way" of the reflection. </member> <member name="update_mode" type="int" setter="set_update_mode" getter="get_update_mode" enum="ReflectionProbe.UpdateMode" default="0"> Sets how frequently the [ReflectionProbe] is updated. Can be [constant UPDATE_ONCE] or [constant UPDATE_ALWAYS]. diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index bd930bf93f..62351ea9ec 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -1801,7 +1801,7 @@ <param index="1" name="longitudes" type="int" /> <param index="2" name="radius" type="float" /> <description> - Returns a mesh of a sphere with the given amount of horizontal and vertical subdivisions. + Returns a mesh of a sphere with the given number of horizontal and vertical subdivisions. </description> </method> <method name="material_create"> diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 62142fce8b..1d4304bd99 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -481,7 +481,7 @@ If [code]true[/code], the label uses the custom font color. </member> <member name="percent_visible" type="float" setter="set_percent_visible" getter="get_percent_visible" default="1.0"> - The range of characters to display, as a [float] between 0.0 and 1.0. When assigned an out of range value, it's the same as assigning 1.0. + The range of characters to display, as a [float] between 0.0 and 1.0. [b]Note:[/b] Setting this property updates [member visible_characters] based on current [method get_total_character_count]. </member> <member name="progress_bar_delay" type="int" setter="set_progress_bar_delay" getter="get_progress_bar_delay" default="1000"> diff --git a/doc/classes/ShaderMaterial.xml b/doc/classes/ShaderMaterial.xml index 92df3255b1..8d4df87b39 100644 --- a/doc/classes/ShaderMaterial.xml +++ b/doc/classes/ShaderMaterial.xml @@ -17,20 +17,6 @@ Returns the current value set for this material of a uniform in the shader. </description> </method> - <method name="property_can_revert"> - <return type="bool" /> - <param index="0" name="name" type="String" /> - <description> - Returns [code]true[/code] if the property identified by [param name] can be reverted to a default value. - </description> - </method> - <method name="property_get_revert"> - <return type="Variant" /> - <param index="0" name="name" type="String" /> - <description> - Returns the default value of the material property with given [param name]. - </description> - </method> <method name="set_shader_uniform"> <return type="void" /> <param index="0" name="param" type="StringName" /> diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml index 7f2a41e00c..e332618e9f 100644 --- a/doc/classes/Skeleton3D.xml +++ b/doc/classes/Skeleton3D.xml @@ -90,7 +90,7 @@ <method name="get_bone_count" qualifiers="const"> <return type="int" /> <description> - Returns the amount of bones in the skeleton. + Returns the number of bones in the skeleton. </description> </method> <method name="get_bone_global_pose" qualifiers="const"> diff --git a/doc/classes/SkeletonModification2DCCDIK.xml b/doc/classes/SkeletonModification2DCCDIK.xml index e37dc62131..c8fee3f94d 100644 --- a/doc/classes/SkeletonModification2DCCDIK.xml +++ b/doc/classes/SkeletonModification2DCCDIK.xml @@ -121,7 +121,7 @@ </methods> <members> <member name="ccdik_data_chain_length" type="int" setter="set_ccdik_data_chain_length" getter="get_ccdik_data_chain_length" default="0"> - The amount of CCDIK joints in the CCDIK modification. + The number of CCDIK joints in the CCDIK modification. </member> <member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")"> The NodePath to the node that is the target for the CCDIK modification. This node is what the CCDIK chain will attempt to rotate the bone chain to. diff --git a/doc/classes/SkeletonModification2DFABRIK.xml b/doc/classes/SkeletonModification2DFABRIK.xml index 1240174946..ff3a65fe1a 100644 --- a/doc/classes/SkeletonModification2DFABRIK.xml +++ b/doc/classes/SkeletonModification2DFABRIK.xml @@ -77,7 +77,7 @@ </methods> <members> <member name="fabrik_data_chain_length" type="int" setter="set_fabrik_data_chain_length" getter="get_fabrik_data_chain_length" default="0"> - The amount of FABRIK joints in the FABRIK modification. + The number of FABRIK joints in the FABRIK modification. </member> <member name="target_nodepath" type="NodePath" setter="set_target_node" getter="get_target_node" default="NodePath("")"> The NodePath to the node that is the target for the FABRIK modification. This node is what the FABRIK chain will attempt to rotate the bone chain to. diff --git a/doc/classes/SkeletonModification2DPhysicalBones.xml b/doc/classes/SkeletonModification2DPhysicalBones.xml index 66ff160ab1..d5f46b2ea0 100644 --- a/doc/classes/SkeletonModification2DPhysicalBones.xml +++ b/doc/classes/SkeletonModification2DPhysicalBones.xml @@ -50,7 +50,7 @@ </methods> <members> <member name="physical_bone_chain_length" type="int" setter="set_physical_bone_chain_length" getter="get_physical_bone_chain_length" default="0"> - The amount of [PhysicalBone2D] nodes linked in this modification. + The number of [PhysicalBone2D] nodes linked in this modification. </member> </members> </class> diff --git a/doc/classes/SkeletonModification3DCCDIK.xml b/doc/classes/SkeletonModification3DCCDIK.xml index 207896776f..dec0fbe99f 100644 --- a/doc/classes/SkeletonModification3DCCDIK.xml +++ b/doc/classes/SkeletonModification3DCCDIK.xml @@ -121,7 +121,7 @@ </methods> <members> <member name="ccdik_data_chain_length" type="int" setter="set_ccdik_data_chain_length" getter="get_ccdik_data_chain_length" default="0"> - The amount of CCDIK joints in the CCDIK modification. + The number of CCDIK joints in the CCDIK modification. </member> <member name="high_quality_solve" type="bool" setter="set_use_high_quality_solve" getter="get_use_high_quality_solve" default="true"> When true, the CCDIK algorithm will perform a higher quality solve that returns more natural results. A high quality solve requires more computation power to solve though, and therefore can be disabled to save performance. diff --git a/doc/classes/SkeletonModificationStack3D.xml b/doc/classes/SkeletonModificationStack3D.xml index fd4a8d462a..34c7099bca 100644 --- a/doc/classes/SkeletonModificationStack3D.xml +++ b/doc/classes/SkeletonModificationStack3D.xml @@ -79,7 +79,7 @@ When true, the modification's in the stack will be called. This is handled automatically through the [Skeleton3D] node. </member> <member name="modification_count" type="int" setter="set_modification_count" getter="get_modification_count" default="0"> - The amount of modifications in the stack. + The number of modifications in the stack. </member> <member name="strength" type="float" setter="set_strength" getter="get_strength" default="1.0"> The interpolation strength of the modifications in stack. A value of [code]0[/code] will make it where the modifications are not applied, a strength of [code]0.5[/code] will be half applied, and a strength of [code]1[/code] will allow the modifications to be fully applied and override the skeleton bone poses. diff --git a/doc/classes/StreamPeer.xml b/doc/classes/StreamPeer.xml index 5bad9796b4..4188563695 100644 --- a/doc/classes/StreamPeer.xml +++ b/doc/classes/StreamPeer.xml @@ -37,14 +37,14 @@ <method name="get_available_bytes" qualifiers="const"> <return type="int" /> <description> - Returns the amount of bytes this [StreamPeer] has available. + Returns the number of bytes this [StreamPeer] has available. </description> </method> <method name="get_data"> <return type="Array" /> <param index="0" name="bytes" type="int" /> <description> - Returns a chunk data with the received bytes. The amount of bytes to be received can be requested in the [param bytes] argument. If not enough bytes are available, the function will block until the desired amount is received. This function returns two values, an [enum @GlobalScope.Error] code and a data array. + Returns a chunk data with the received bytes. The number of bytes to be received can be requested in the [param bytes] argument. If not enough bytes are available, the function will block until the desired amount is received. This function returns two values, an [enum @GlobalScope.Error] code and a data array. </description> </method> <method name="get_double"> @@ -63,7 +63,7 @@ <return type="Array" /> <param index="0" name="bytes" type="int" /> <description> - Returns a chunk data with the received bytes. The amount of bytes to be received can be requested in the "bytes" argument. If not enough bytes are available, the function will return how many were actually received. This function returns two values, an [enum @GlobalScope.Error] code, and a data array. + Returns a chunk data with the received bytes. The number of bytes to be received can be requested in the "bytes" argument. If not enough bytes are available, the function will return how many were actually received. This function returns two values, an [enum @GlobalScope.Error] code, and a data array. </description> </method> <method name="get_string"> diff --git a/doc/classes/String.xml b/doc/classes/String.xml index ee508cf70a..f1cd4d72f7 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -440,7 +440,7 @@ <method name="length" qualifiers="const"> <return type="int" /> <description> - Returns the string's amount of characters. + Returns the number of characters in the string. </description> </method> <method name="lpad" qualifiers="const"> @@ -522,7 +522,7 @@ # Last digit will be rounded up here, which reduces total digit count since # trailing zeros are removed: String.num(42.129999, 5) # "42.13" - # If `decimals` is not specified, the total amount of significant digits is 14: + # If `decimals` is not specified, the total number of significant digits is 14: String.num(-0.0000012345432123454321) # "-0.00000123454321" String.num(-10000.0000012345432123454321) # "-10000.0000012345" [/codeblock] diff --git a/doc/classes/SystemFont.xml b/doc/classes/SystemFont.xml index b1b78f1705..b7454cc7d2 100644 --- a/doc/classes/SystemFont.xml +++ b/doc/classes/SystemFont.xml @@ -34,6 +34,9 @@ <member name="hinting" type="int" setter="set_hinting" getter="get_hinting" enum="TextServer.Hinting" default="1"> Font hinting mode. </member> + <member name="multichannel_signed_distance_field" type="bool" setter="set_multichannel_signed_distance_field" getter="is_multichannel_signed_distance_field" default="false"> + If set to [code]true[/code], glyphs of all sizes are rendered using single multichannel signed distance field generated from the dynamic font vector data. + </member> <member name="oversampling" type="float" setter="set_oversampling" getter="get_oversampling" default="0.0"> Font oversampling factor, if set to [code]0.0[/code] global oversampling factor is used instead. </member> diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml index 40d6d67f4c..aa7ce85f3a 100644 --- a/doc/classes/TextEdit.xml +++ b/doc/classes/TextEdit.xml @@ -161,7 +161,7 @@ <method name="get_gutter_count" qualifiers="const"> <return type="int" /> <description> - Returns the total amount of gutters registered. + Returns the number of gutters registered. </description> </method> <method name="get_gutter_name" qualifiers="const"> @@ -189,7 +189,7 @@ <return type="int" /> <param index="0" name="line" type="int" /> <description> - Returns the amount of spaces and [code]tab * tab_size[/code] before the first char. + Returns the number of spaces and [code]tab * tab_size[/code] before the first char. </description> </method> <method name="get_last_full_visible_line" qualifiers="const"> @@ -235,7 +235,7 @@ <method name="get_line_count" qualifiers="const"> <return type="int" /> <description> - Returns the amount of total lines in the text. + Returns the number of lines in the text. </description> </method> <method name="get_line_gutter_icon" qualifiers="const"> @@ -329,7 +329,7 @@ <method name="get_minimap_visible_lines" qualifiers="const"> <return type="int" /> <description> - Returns the total amount of lines that can be draw on the minimap. + Returns the number of lines that may be drawn on the minimap. </description> </method> <method name="get_next_visible_line_index_offset_from" qualifiers="const"> @@ -444,7 +444,7 @@ <method name="get_total_visible_line_count" qualifiers="const"> <return type="int" /> <description> - Returns the total amount of lines that could be draw. + Returns the number of lines that may be drawn. </description> </method> <method name="get_version" qualifiers="const"> diff --git a/doc/classes/TileMap.xml b/doc/classes/TileMap.xml index 4266a414ce..5530759628 100644 --- a/doc/classes/TileMap.xml +++ b/doc/classes/TileMap.xml @@ -77,14 +77,14 @@ <description> Triggers an update of the TileMap. If [param layer] is provided, only updates the given layer. [b]Note:[/b] The TileMap node updates automatically when one of its properties is modified. A manual update is only needed if runtime modifications (implemented in [method _tile_data_runtime_update]) need to be applied. - [b]Warning:[/b] Updating the TileMap is a performance demanding task. Limit occurrences of those updates to the minimum and limit the amount tiles they impact (by segregating tiles updated often to a dedicated layer for example). + [b]Warning:[/b] Updating the TileMap is computationally expensive and may impact performance. Try to limit the number of updates and the tiles they impact (by placing frequently updated tiles in a dedicated layer for example). </description> </method> <method name="get_cell_alternative_tile" qualifiers="const"> <return type="int" /> <param index="0" name="layer" type="int" /> <param index="1" name="coords" type="Vector2i" /> - <param index="2" name="use_proxies" type="bool" /> + <param index="2" name="use_proxies" type="bool" default="false" /> <description> Returns the tile alternative ID of the cell on layer [param layer] at [param coords]. If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. </description> @@ -93,7 +93,7 @@ <return type="Vector2i" /> <param index="0" name="layer" type="int" /> <param index="1" name="coords" type="Vector2i" /> - <param index="2" name="use_proxies" type="bool" /> + <param index="2" name="use_proxies" type="bool" default="false" /> <description> Returns the tile atlas coordinates ID of the cell on layer [param layer] at coordinates [param coords]. If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. </description> @@ -102,11 +102,21 @@ <return type="int" /> <param index="0" name="layer" type="int" /> <param index="1" name="coords" type="Vector2i" /> - <param index="2" name="use_proxies" type="bool" /> + <param index="2" name="use_proxies" type="bool" default="false" /> <description> Returns the tile source ID of the cell on layer [param layer] at coordinates [param coords]. If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. </description> </method> + <method name="get_cell_tile_data" qualifiers="const"> + <return type="TileData" /> + <param index="0" name="layer" type="int" /> + <param index="1" name="coords" type="Vector2i" /> + <param index="2" name="use_proxies" type="bool" default="false" /> + <description> + Returns the [TileData] object associated with the given cell, or [code]null[/code] if the cell is not a [TileSetAtlasSource]. + If [param use_proxies] is [code]false[/code], ignores the [TileSet]'s tile proxies, returning the raw alternative identifier. See [method TileSet.map_tile_proxy]. + </description> + </method> <method name="get_coords_for_body_rid"> <return type="Vector2i" /> <param index="0" name="body" type="RID" /> @@ -253,7 +263,6 @@ <description> Update all the cells in the [param cells] coordinates array so that they use the given [param terrain] for the given [param terrain_set]. If an updated cell has the same terrain as one of its neighboring cells, this function tries to join the two. This function might update neighboring tiles if needed to create correct terrain transitions. If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints. - If [param ignore_empty_terrains] is true, empty terrains will be ignored when trying to find the best fitting tile for the given terrain constraints. [b]Note:[/b] To work correctly, [code]set_cells_terrain_connect[/code] requires the TileMap's TileSet to have terrains set up with all required terrain combinations. Otherwise, it may produce unexpected results. </description> </method> diff --git a/doc/classes/VelocityTracker3D.xml b/doc/classes/VelocityTracker3D.xml deleted file mode 100644 index 56b60ba13c..0000000000 --- a/doc/classes/VelocityTracker3D.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="VelocityTracker3D" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> - <brief_description> - </brief_description> - <description> - </description> - <tutorials> - </tutorials> - <methods> - <method name="get_tracked_linear_velocity" qualifiers="const"> - <return type="Vector3" /> - <description> - </description> - </method> - <method name="reset"> - <return type="void" /> - <param index="0" name="position" type="Vector3" /> - <description> - </description> - </method> - <method name="update_position"> - <return type="void" /> - <param index="0" name="position" type="Vector3" /> - <description> - </description> - </method> - </methods> - <members> - <member name="track_physics_step" type="bool" setter="set_track_physics_step" getter="is_tracking_physics_step" default="false"> - </member> - </members> -</class> diff --git a/doc/classes/VisualShaderNodeCustom.xml b/doc/classes/VisualShaderNodeCustom.xml index 9813b4778d..d96969b383 100644 --- a/doc/classes/VisualShaderNodeCustom.xml +++ b/doc/classes/VisualShaderNodeCustom.xml @@ -68,7 +68,7 @@ <method name="_get_input_port_count" qualifiers="virtual const"> <return type="int" /> <description> - Override this method to define the amount of input ports of the associated custom node. + Override this method to define the number of input ports of the associated custom node. Defining this method is [b]required[/b]. If not overridden, the node has no input ports. </description> </method> @@ -98,7 +98,7 @@ <method name="_get_output_port_count" qualifiers="virtual const"> <return type="int" /> <description> - Override this method to define the amount of output ports of the associated custom node. + Override this method to define the number of output ports of the associated custom node. Defining this method is [b]required[/b]. If not overridden, the node has no output ports. </description> </method> diff --git a/doc/classes/VisualShaderNodeExpression.xml b/doc/classes/VisualShaderNodeExpression.xml index c4f010f3c0..6b2dc2f2cb 100644 --- a/doc/classes/VisualShaderNodeExpression.xml +++ b/doc/classes/VisualShaderNodeExpression.xml @@ -4,7 +4,7 @@ A custom visual shader graph expression written in Godot Shading Language. </brief_description> <description> - Custom Godot Shading Language expression, with a custom amount of input and output ports. + Custom Godot Shading Language expression, with a custom number of input and output ports. The provided code is directly injected into the graph's matching shader function ([code]vertex[/code], [code]fragment[/code], or [code]light[/code]), so it cannot be used to declare functions, varyings, uniforms, or global constants. See [VisualShaderNodeGlobalExpression] for such global definitions. </description> <tutorials> diff --git a/doc/classes/VisualShaderNodeGroupBase.xml b/doc/classes/VisualShaderNodeGroupBase.xml index 450629a73f..dcc94f0b24 100644 --- a/doc/classes/VisualShaderNodeGroupBase.xml +++ b/doc/classes/VisualShaderNodeGroupBase.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="VisualShaderNodeGroupBase" inherits="VisualShaderNodeResizableBase" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - Base class for a family of nodes with variable amount of input and output ports within the visual shader graph. + Base class for a family of nodes with variable number of input and output ports within the visual shader graph. </brief_description> <description> Currently, has no direct usage, use the derived classes instead. diff --git a/doc/classes/XMLParser.xml b/doc/classes/XMLParser.xml index 69544f4895..26480f0c18 100644 --- a/doc/classes/XMLParser.xml +++ b/doc/classes/XMLParser.xml @@ -12,7 +12,7 @@ <method name="get_attribute_count" qualifiers="const"> <return type="int" /> <description> - Gets the amount of attributes in the current element. + Gets the number of attributes in the current element. </description> </method> <method name="get_attribute_name" qualifiers="const"> diff --git a/doc/tools/doc_status.py b/doc/tools/doc_status.py index cc0733cab2..376addcff0 100755 --- a/doc/tools/doc_status.py +++ b/doc/tools/doc_status.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import fnmatch import os @@ -7,6 +7,7 @@ import re import math import platform import xml.etree.ElementTree as ET +from typing import Dict, List, Set ################################################################################ # Config # @@ -103,13 +104,13 @@ overall_progress_description_weight = 10 ################################################################################ -def validate_tag(elem, tag): +def validate_tag(elem: ET.Element, tag: str) -> None: if elem.tag != tag: print('Tag mismatch, expected "' + tag + '", got ' + elem.tag) sys.exit(255) -def color(color, string): +def color(color: str, string: str) -> str: if flags["c"] and terminal_supports_color(): color_format = "" for code in colors[color]: @@ -122,7 +123,7 @@ def color(color, string): ansi_escape = re.compile(r"\x1b[^m]*m") -def nonescape_len(s): +def nonescape_len(s: str) -> int: return len(ansi_escape.sub("", s)) @@ -142,14 +143,14 @@ def terminal_supports_color(): class ClassStatusProgress: - def __init__(self, described=0, total=0): - self.described = described - self.total = total + def __init__(self, described: int = 0, total: int = 0): + self.described: int = described + self.total: int = total - def __add__(self, other): + def __add__(self, other: "ClassStatusProgress"): return ClassStatusProgress(self.described + other.described, self.total + other.total) - def increment(self, described): + def increment(self, described: bool): if described: self.described += 1 self.total += 1 @@ -163,7 +164,7 @@ class ClassStatusProgress: else: return self.to_colored_string() - def to_colored_string(self, format="{has}/{total}", pad_format="{pad_described}{s}{pad_total}"): + def to_colored_string(self, format: str = "{has}/{total}", pad_format: str = "{pad_described}{s}{pad_total}"): ratio = float(self.described) / float(self.total) if self.total != 0 else 1 percent = int(round(100 * ratio)) s = format.format(has=str(self.described), total=str(self.total), percent=str(percent)) @@ -183,11 +184,11 @@ class ClassStatusProgress: class ClassStatus: - def __init__(self, name=""): - self.name = name - self.has_brief_description = True - self.has_description = True - self.progresses = { + def __init__(self, name: str = ""): + self.name: str = name + self.has_brief_description: bool = True + self.has_description: bool = True + self.progresses: Dict[str, ClassStatusProgress] = { "methods": ClassStatusProgress(), "constants": ClassStatusProgress(), "members": ClassStatusProgress(), @@ -197,7 +198,7 @@ class ClassStatus: "constructors": ClassStatusProgress(), } - def __add__(self, other): + def __add__(self, other: "ClassStatus"): new_status = ClassStatus() new_status.name = self.name new_status.has_brief_description = self.has_brief_description and other.has_brief_description @@ -222,8 +223,8 @@ class ClassStatus: sum += self.progresses[k].total return sum < 1 - def make_output(self): - output = {} + def make_output(self) -> Dict[str, str]: + output: Dict[str, str] = {} output["name"] = color("name", self.name) ok_string = color("part_good", "OK") @@ -263,22 +264,24 @@ class ClassStatus: return output @staticmethod - def generate_for_class(c): + def generate_for_class(c: ET.Element): status = ClassStatus() status.name = c.attrib["name"] for tag in list(c): + len_tag_text = 0 if (tag.text is None) else len(tag.text.strip()) if tag.tag == "brief_description": - status.has_brief_description = len(tag.text.strip()) > 0 + status.has_brief_description = len_tag_text > 0 elif tag.tag == "description": - status.has_description = len(tag.text.strip()) > 0 + status.has_description = len_tag_text > 0 elif tag.tag in ["methods", "signals", "operators", "constructors"]: for sub_tag in list(tag): descr = sub_tag.find("description") - status.progresses[tag.tag].increment(len(descr.text.strip()) > 0) + increment = (descr is not None) and (descr.text is not None) and len(descr.text.strip()) > 0 + status.progresses[tag.tag].increment(increment) elif tag.tag in ["constants", "members", "theme_items"]: for sub_tag in list(tag): if not sub_tag.text is None: @@ -297,9 +300,9 @@ class ClassStatus: # Arguments # ################################################################################ -input_file_list = [] -input_class_list = [] -merged_file = "" +input_file_list: List[str] = [] +input_class_list: List[str] = [] +merged_file: str = "" for arg in sys.argv[1:]: try: @@ -373,8 +376,8 @@ if len(input_file_list) < 1 or flags["h"]: # Parse class list # ################################################################################ -class_names = [] -classes = {} +class_names: List[str] = [] +classes: Dict[str, ET.Element] = {} for file in input_file_list: tree = ET.parse(file) @@ -396,10 +399,10 @@ class_names.sort() if len(input_class_list) < 1: input_class_list = ["*"] -filtered_classes = set() +filtered_classes_set: Set[str] = set() for pattern in input_class_list: - filtered_classes |= set(fnmatch.filter(class_names, pattern)) -filtered_classes = list(filtered_classes) + filtered_classes_set |= set(fnmatch.filter(class_names, pattern)) +filtered_classes = list(filtered_classes_set) filtered_classes.sort() ################################################################################ @@ -413,7 +416,6 @@ table_column_chars = "|" total_status = ClassStatus("Total") for cn in filtered_classes: - c = classes[cn] validate_tag(c, "class") status = ClassStatus.generate_for_class(c) @@ -427,7 +429,7 @@ for cn in filtered_classes: continue out = status.make_output() - row = [] + row: List[str] = [] for column in table_columns: if column in out: row.append(out[column]) @@ -464,7 +466,7 @@ if flags["a"]: # without having to scroll back to the top. table.append(table_column_names) -table_column_sizes = [] +table_column_sizes: List[int] = [] for row in table: for cell_i, cell in enumerate(row): if cell_i >= len(table_column_sizes): @@ -477,7 +479,6 @@ for cell_i in range(len(table[0])): divider_string += ( table_row_chars[1] + table_row_chars[2] * (table_column_sizes[cell_i]) + table_row_chars[1] + table_row_chars[0] ) -print(divider_string) for row_i, row in enumerate(table): row_string = table_column_chars diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp index 83154acd51..28802f571c 100644 --- a/drivers/gles3/rasterizer_canvas_gles3.cpp +++ b/drivers/gles3/rasterizer_canvas_gles3.cpp @@ -201,6 +201,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ bool material_screen_texture_found = false; Rect2 back_buffer_rect; bool backbuffer_copy = false; + bool backbuffer_gen_mipmaps = false; Item *ci = p_item_list; Item *canvas_group_owner = nullptr; @@ -225,6 +226,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ if (!material_screen_texture_found) { backbuffer_copy = true; back_buffer_rect = Rect2(); + backbuffer_gen_mipmaps = md->shader_data->uses_screen_texture_mipmaps; } } @@ -282,7 +284,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_ _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list); item_count = 0; - texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, true); + texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps); backbuffer_copy = false; material_screen_texture_found = true; //after a backbuffer copy, screen texture makes no further copies diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 3e416bc615..9bbe960d86 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -867,7 +867,6 @@ void RasterizerSceneGLES3::_update_sky_radiance(RID p_env, const Projection &p_p GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::TIME, time, shader_data->version, SkyShaderGLES3::MODE_CUBEMAP); GLES3::MaterialStorage::get_singleton()->shaders.sky_shader.version_set_uniform(SkyShaderGLES3::PROJECTION, cm.matrix[2][0], cm.matrix[0][0], cm.matrix[2][1], cm.matrix[1][1], shader_data->version, SkyShaderGLES3::MODE_CUBEMAP); - // Bind a vertex array or else OpenGL complains. We won't actually use it glBindVertexArray(sky_globals.screen_triangle_array); glViewport(0, 0, sky->radiance_size, sky->radiance_size); @@ -2150,7 +2149,9 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, index_array_gl = mesh_storage->mesh_surface_get_index_buffer(mesh_surface, surf->lod_index); if (prev_vertex_array_gl != vertex_array_gl) { - glBindVertexArray(vertex_array_gl); + if (vertex_array_gl != 0) { + glBindVertexArray(vertex_array_gl); + } prev_vertex_array_gl = vertex_array_gl; } diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp index 26441fc726..a64c7f7200 100644 --- a/drivers/gles3/storage/material_storage.cpp +++ b/drivers/gles3/storage/material_storage.cpp @@ -1083,6 +1083,12 @@ void MaterialData::update_textures(const HashMap<StringName, Variant> &p_paramet Vector<RID> textures; + if (p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { + continue; + } + if (p_texture_uniforms[i].global) { uses_global_textures = true; @@ -1450,8 +1456,8 @@ MaterialStorage::MaterialStorage() { actions.renames["UV2"] = "uv2_interp"; actions.renames["COLOR"] = "color_interp"; actions.renames["POINT_SIZE"] = "gl_PointSize"; - actions.renames["INSTANCE_ID"] = "gl_InstanceIndex"; - actions.renames["VERTEX_ID"] = "gl_VertexIndex"; + actions.renames["INSTANCE_ID"] = "gl_InstanceID"; + actions.renames["VERTEX_ID"] = "gl_VertexID"; actions.renames["ALPHA_SCISSOR_THRESHOLD"] = "alpha_scissor_threshold"; actions.renames["ALPHA_HASH_SCALE"] = "alpha_hash_scale"; @@ -1492,9 +1498,9 @@ MaterialStorage::MaterialStorage() { actions.renames["POINT_COORD"] = "gl_PointCoord"; actions.renames["INSTANCE_CUSTOM"] = "instance_custom"; actions.renames["SCREEN_UV"] = "screen_uv"; - actions.renames["SCREEN_TEXTURE"] = "color_buffer"; - actions.renames["DEPTH_TEXTURE"] = "depth_buffer"; - actions.renames["NORMAL_ROUGHNESS_TEXTURE"] = "normal_roughness_buffer"; + //actions.renames["SCREEN_TEXTURE"] = "color_buffer"; //Not implemented in 3D yet. + //actions.renames["DEPTH_TEXTURE"] = "depth_buffer"; // Not implemented in 3D yet. + //actions.renames["NORMAL_ROUGHNESS_TEXTURE"] = "normal_roughness_buffer"; // Not implemented in 3D yet actions.renames["DEPTH"] = "gl_FragDepth"; actions.renames["OUTPUT_IS_SRGB"] = "true"; actions.renames["FOG"] = "fog"; @@ -2789,6 +2795,7 @@ void CanvasShaderData::set_code(const String &p_code) { ubo_size = 0; uniforms.clear(); uses_screen_texture = false; + uses_screen_texture_mipmaps = false; uses_sdf = false; uses_time = false; @@ -2799,7 +2806,6 @@ void CanvasShaderData::set_code(const String &p_code) { ShaderCompiler::GeneratedCode gen_code; int blend_modei = BLEND_MODE_MIX; - uses_screen_texture = false; ShaderCompiler::IdentifierActions actions; actions.entry_point_stages["vertex"] = ShaderCompiler::STAGE_VERTEX; @@ -2826,6 +2832,7 @@ void CanvasShaderData::set_code(const String &p_code) { } blend_mode = BlendMode(blend_modei); + uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps; #if 0 print_line("**compiling shader:"); @@ -2833,12 +2840,16 @@ void CanvasShaderData::set_code(const String &p_code) { for (int i = 0; i < gen_code.defines.size(); i++) { print_line(gen_code.defines[i]); } + + HashMap<String, String>::Iterator el = gen_code.code.begin(); + while (el) { + print_line("\n**code " + el->key + ":\n" + el->value); + ++el; + } + print_line("\n**uniforms:\n" + gen_code.uniforms); - print_line("\n**vertex_globals:\n" + gen_code.vertex_global); - print_line("\n**vertex_code:\n" + gen_code.vertex); - print_line("\n**fragment_globals:\n" + gen_code.fragment_global); - print_line("\n**fragment_code:\n" + gen_code.fragment); - print_line("\n**light_code:\n" + gen_code.light); + print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX]); + print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]); #endif Vector<StringName> texture_uniform_names; @@ -2877,7 +2888,10 @@ void CanvasShaderData::get_shader_uniform_list(List<PropertyInfo> *p_param_list) HashMap<int, StringName> order; for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) { - if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) { + if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { continue; } if (E.value.texture_order >= 0) { @@ -3070,12 +3084,16 @@ void SkyShaderData::set_code(const String &p_code) { for (int i = 0; i < gen_code.defines.size(); i++) { print_line(gen_code.defines[i]); } + + HashMap<String, String>::Iterator el = gen_code.code.begin(); + while (el) { + print_line("\n**code " + el->key + ":\n" + el->value); + ++el; + } + print_line("\n**uniforms:\n" + gen_code.uniforms); - // print_line("\n**vertex_globals:\n" + gen_code.vertex_global); - // print_line("\n**vertex_code:\n" + gen_code.vertex); - print_line("\n**fragment_globals:\n" + gen_code.fragment_global); - print_line("\n**fragment_code:\n" + gen_code.fragment); - print_line("\n**light_code:\n" + gen_code.light); + print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX]); + print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]); #endif Vector<StringName> texture_uniform_names; @@ -3253,7 +3271,6 @@ void SceneShaderData::set_code(const String &p_code) { valid = false; ubo_size = 0; uniforms.clear(); - uses_screen_texture = false; if (code.is_empty()) { return; //just invalid, but no error @@ -3378,6 +3395,7 @@ void SceneShaderData::set_code(const String &p_code) { vertex_input_mask |= uses_custom3 << 8; vertex_input_mask |= uses_bones << 9; vertex_input_mask |= uses_weights << 10; + uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps; #if 0 print_line("**compiling shader:"); @@ -3386,11 +3404,10 @@ void SceneShaderData::set_code(const String &p_code) { print_line(gen_code.defines[i]); } - Map<String, String>::Element *el = gen_code.code.front(); + HashMap<String, String>::Iterator el = gen_code.code.begin(); while (el) { - print_line("\n**code " + el->key() + ":\n" + el->value()); - - el = el->next(); + print_line("\n**code " + el->key + ":\n" + el->value); + ++el; } print_line("\n**uniforms:\n" + gen_code.uniforms); @@ -3439,7 +3456,10 @@ void SceneShaderData::get_shader_uniform_list(List<PropertyInfo> *p_param_list) RBMap<int, StringName> order; for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) { - if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) { + if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { continue; } diff --git a/drivers/gles3/storage/material_storage.h b/drivers/gles3/storage/material_storage.h index 2ca47351a4..d135357f6a 100644 --- a/drivers/gles3/storage/material_storage.h +++ b/drivers/gles3/storage/material_storage.h @@ -159,6 +159,7 @@ struct CanvasShaderData : public ShaderData { HashMap<StringName, HashMap<int, RID>> default_texture_params; bool uses_screen_texture = false; + bool uses_screen_texture_mipmaps = false; bool uses_sdf = false; bool uses_time = false; @@ -312,6 +313,7 @@ struct SceneShaderData : public ShaderData { bool uses_sss; bool uses_transmittance; bool uses_screen_texture; + bool uses_screen_texture_mipmaps; bool uses_depth_texture; bool uses_normal_texture; bool uses_time; diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp index 667ba4f5e6..38de0220e3 100644 --- a/drivers/gles3/storage/mesh_storage.cpp +++ b/drivers/gles3/storage/mesh_storage.cpp @@ -186,11 +186,13 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) s->format = p_surface.format; s->primitive = p_surface.primitive; - glGenBuffers(1, &s->vertex_buffer); - glBindBuffer(GL_ARRAY_BUFFER, s->vertex_buffer); - glBufferData(GL_ARRAY_BUFFER, p_surface.vertex_data.size(), p_surface.vertex_data.ptr(), (s->format & RS::ARRAY_FLAG_USE_DYNAMIC_UPDATE) ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind - s->vertex_buffer_size = p_surface.vertex_data.size(); + if (p_surface.vertex_data.size()) { + glGenBuffers(1, &s->vertex_buffer); + glBindBuffer(GL_ARRAY_BUFFER, s->vertex_buffer); + glBufferData(GL_ARRAY_BUFFER, p_surface.vertex_data.size(), p_surface.vertex_data.ptr(), (s->format & RS::ARRAY_FLAG_USE_DYNAMIC_UPDATE) ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); //unbind + s->vertex_buffer_size = p_surface.vertex_data.size(); + } if (p_surface.attribute_data.size()) { glGenBuffers(1, &s->attribute_buffer); @@ -214,7 +216,7 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) } if (p_surface.index_count) { - bool is_index_16 = p_surface.vertex_count <= 65536; + bool is_index_16 = p_surface.vertex_count <= 65536 && p_surface.vertex_count > 0; glGenBuffers(1, &s->index_buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->index_buffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, p_surface.index_data.size(), p_surface.index_data.ptr(), GL_STATIC_DRAW); @@ -238,6 +240,8 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) } } + ERR_FAIL_COND_MSG(!p_surface.index_count && !p_surface.vertex_count, "Meshes must contain a vertex array, an index array, or both"); + s->aabb = p_surface.aabb; s->bone_aabbs = p_surface.bone_aabbs; //only really useful for returning them. @@ -255,7 +259,10 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) mesh->bone_aabbs.resize(p_surface.bone_aabbs.size()); } for (int i = 0; i < p_surface.bone_aabbs.size(); i++) { - mesh->bone_aabbs.write[i].merge_with(p_surface.bone_aabbs[i]); + const AABB &bone = p_surface.bone_aabbs[i]; + if (!bone.has_no_volume()) { + mesh->bone_aabbs.write[i].merge_with(bone); + } } mesh->aabb.merge_with(p_surface.aabb); } @@ -337,7 +344,9 @@ RS::SurfaceData MeshStorage::mesh_get_surface(RID p_mesh, int p_surface) const { RS::SurfaceData sd; sd.format = s.format; - sd.vertex_data = Utilities::buffer_get_data(GL_ARRAY_BUFFER, s.vertex_buffer, s.vertex_buffer_size); + if (s.vertex_buffer != 0) { + sd.vertex_data = Utilities::buffer_get_data(GL_ARRAY_BUFFER, s.vertex_buffer, s.vertex_buffer_size); + } if (s.attribute_buffer != 0) { sd.attribute_data = Utilities::buffer_get_data(GL_ARRAY_BUFFER, s.attribute_buffer, s.attribute_buffer_size); diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h index 339380b3b0..74f5800795 100644 --- a/drivers/gles3/storage/mesh_storage.h +++ b/drivers/gles3/storage/mesh_storage.h @@ -75,7 +75,7 @@ struct Mesh { // Cache vertex arrays so they can be created struct Version { uint32_t input_mask = 0; - GLuint vertex_array; + GLuint vertex_array = 0; Attrib attribs[RS::ARRAY_MAX]; }; @@ -92,7 +92,7 @@ struct Mesh { float edge_length = 0.0; uint32_t index_count = 0; uint32_t index_buffer_size = 0; - GLuint index_buffer; + GLuint index_buffer = 0; }; LOD *lods = nullptr; @@ -175,7 +175,7 @@ struct MultiMesh { bool *data_cache_dirty_regions = nullptr; uint32_t data_cache_used_dirty_regions = 0; - GLuint buffer; + GLuint buffer = 0; bool dirty = false; MultiMesh *dirty_list = nullptr; @@ -362,7 +362,7 @@ public: _FORCE_INLINE_ GLenum mesh_surface_get_index_type(void *p_surface) const { Mesh::Surface *s = reinterpret_cast<Mesh::Surface *>(p_surface); - return s->vertex_count <= 65536 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; + return (s->vertex_count <= 65536 && s->vertex_count > 0) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; } // Use this to cache Vertex Array Objects so they are only generated once diff --git a/drivers/vulkan/rendering_device_vulkan.cpp b/drivers/vulkan/rendering_device_vulkan.cpp index bcb6092d87..2debba1b83 100644 --- a/drivers/vulkan/rendering_device_vulkan.cpp +++ b/drivers/vulkan/rendering_device_vulkan.cpp @@ -7805,12 +7805,6 @@ void RenderingDeviceVulkan::draw_list_draw(DrawListID p_list, bool p_use_indices ERR_FAIL_COND_MSG(!dl->validation.index_array_size, "Draw command requested indices, but no index buffer was set."); - if (dl->validation.pipeline_vertex_format != INVALID_ID) { - // Uses vertices, do some vertex validations. - ERR_FAIL_COND_MSG(dl->validation.vertex_array_size < dl->validation.index_array_max_index, - "Index array references (max index: " + itos(dl->validation.index_array_max_index) + ") indices beyond the vertex array size (" + itos(dl->validation.vertex_array_size) + ")."); - } - ERR_FAIL_COND_MSG(dl->validation.pipeline_uses_restart_indices != dl->validation.index_buffer_uses_restart_indices, "The usage of restart indices in index buffer does not match the render primitive in the pipeline."); #endif diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index 881575d245..2125709b32 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -402,6 +402,8 @@ DirAccessWindows::DirAccessWindows() { } DirAccessWindows::~DirAccessWindows() { + list_dir_end(); + memdelete(p); } diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index e6168f4924..c84e8ec48f 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -38,7 +38,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" -void CreateDialog::popup_create(bool p_dont_clear, bool p_replace_mode, const String &p_select_type) { +void CreateDialog::popup_create(bool p_dont_clear, bool p_replace_mode, const String &p_select_type, const String &p_select_name) { _fill_type_list(); icon_fallback = search_options->has_theme_icon(base_type, SNAME("EditorIcons")) ? base_type : "Object"; @@ -57,7 +57,11 @@ void CreateDialog::popup_create(bool p_dont_clear, bool p_replace_mode, const St _update_search(); if (p_replace_mode) { - set_title(vformat(TTR("Change %s Type"), base_type)); + if (!p_select_name.is_empty()) { + set_title(vformat(TTR("Convert %s from %s"), p_select_name, p_select_type)); + } else { + set_title(vformat(TTR("Convert %s"), p_select_type)); + } set_ok_button_text(TTR("Change")); } else { set_title(vformat(TTR("Create New %s"), base_type)); diff --git a/editor/create_dialog.h b/editor/create_dialog.h index 04094108ad..f7731d2726 100644 --- a/editor/create_dialog.h +++ b/editor/create_dialog.h @@ -120,7 +120,7 @@ public: void set_preferred_search_result_type(const String &p_preferred_type) { preferred_search_result_type = p_preferred_type; } String get_preferred_search_result_type() { return preferred_search_result_type; } - void popup_create(bool p_dont_clear, bool p_replace_mode = false, const String &p_select_type = "Node"); + void popup_create(bool p_dont_clear, bool p_replace_mode = false, const String &p_select_type = "Node", const String &p_select_name = ""); CreateDialog(); }; diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 8d58469684..7fa4303145 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -1785,7 +1785,7 @@ static void _add_text_to_rt(const String &p_bbcode, RichTextLabel *p_rt) { p_rt->add_text("["); pos = brk_pos + 1; - } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("theme_item ")) { + } else if (tag.begins_with("method ") || tag.begins_with("member ") || tag.begins_with("signal ") || tag.begins_with("enum ") || tag.begins_with("constant ") || tag.begins_with("annotation ") || tag.begins_with("theme_item ")) { const int tag_end = tag.find(" "); const String link_tag = tag.substr(0, tag_end); const String link_target = tag.substr(tag_end + 1, tag.length()).lstrip(" "); diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index b4e36d568d..e954f06f08 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -432,11 +432,11 @@ bool EditorProperty::is_read_only() const { } Variant EditorPropertyRevert::get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid) { - if (p_object->has_method("property_can_revert") && p_object->call("property_can_revert", p_property)) { + if (p_object->property_can_revert(p_property)) { if (r_is_valid) { *r_is_valid = true; } - return p_object->call("property_get_revert", p_property); + return p_object->property_get_revert(p_property); } return PropertyUtils::get_property_default_value(p_object, p_property, r_is_valid); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index e7946f56da..2941ae6695 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -54,6 +54,7 @@ #include "scene/gui/dialogs.h" #include "scene/gui/file_dialog.h" #include "scene/gui/link_button.h" +#include "scene/gui/menu_bar.h" #include "scene/gui/menu_button.h" #include "scene/gui/panel.h" #include "scene/gui/panel_container.h" @@ -751,11 +752,7 @@ void EditorNode::_notification(int p_what) { scene_tabs->add_theme_style_override("tab_selected", gui_base->get_theme_stylebox(SNAME("SceneTabFG"), SNAME("EditorStyles"))); scene_tabs->add_theme_style_override("tab_unselected", gui_base->get_theme_stylebox(SNAME("SceneTabBG"), SNAME("EditorStyles"))); - file_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); - project_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); - debug_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); - settings_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); - help_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); + main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); } scene_tabs->set_max_tab_width(int(EDITOR_GET("interface/scene_tabs/maximum_width")) * EDSCALE); @@ -803,16 +800,15 @@ void EditorNode::_notification(int p_what) { dock_tab_move_right->set_icon(theme->get_icon(SNAME("Forward"), SNAME("EditorIcons"))); } - PopupMenu *p = help_menu->get_popup(); - p->set_item_icon(p->get_item_index(HELP_SEARCH), gui_base->get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons"))); - p->set_item_icon(p->get_item_index(HELP_DOCS), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); - p->set_item_icon(p->get_item_index(HELP_QA), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); - p->set_item_icon(p->get_item_index(HELP_REPORT_A_BUG), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); - p->set_item_icon(p->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); - p->set_item_icon(p->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); - p->set_item_icon(p->get_item_index(HELP_COMMUNITY), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); - p->set_item_icon(p->get_item_index(HELP_ABOUT), gui_base->get_theme_icon(SNAME("Godot"), SNAME("EditorIcons"))); - p->set_item_icon(p->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), gui_base->get_theme_icon(SNAME("Heart"), SNAME("EditorIcons"))); + help_menu->set_item_icon(help_menu->get_item_index(HELP_SEARCH), gui_base->get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons"))); + help_menu->set_item_icon(help_menu->get_item_index(HELP_DOCS), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); + help_menu->set_item_icon(help_menu->get_item_index(HELP_QA), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); + help_menu->set_item_icon(help_menu->get_item_index(HELP_REPORT_A_BUG), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); + help_menu->set_item_icon(help_menu->get_item_index(HELP_SUGGEST_A_FEATURE), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); + help_menu->set_item_icon(help_menu->get_item_index(HELP_SEND_DOCS_FEEDBACK), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); + help_menu->set_item_icon(help_menu->get_item_index(HELP_COMMUNITY), gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); + help_menu->set_item_icon(help_menu->get_item_index(HELP_ABOUT), gui_base->get_theme_icon(SNAME("Godot"), SNAME("EditorIcons"))); + help_menu->set_item_icon(help_menu->get_item_index(HELP_SUPPORT_GODOT_DEVELOPMENT), gui_base->get_theme_icon(SNAME("Heart"), SNAME("EditorIcons"))); for (int i = 0; i < main_editor_buttons.size(); i++) { main_editor_buttons.write[i]->add_theme_font_override("font", gui_base->get_theme_font(SNAME("main_button_font"), SNAME("EditorFonts"))); @@ -1942,6 +1938,21 @@ void EditorNode::_dialog_action(String p_file) { } } break; + case FILE_SAVE_AND_RUN_MAIN_SCENE: { + ProjectSettings::get_singleton()->set("application/run/main_scene", p_file); + ProjectSettings::get_singleton()->save(); + + if (file->get_file_mode() == EditorFileDialog::FILE_MODE_SAVE_FILE) { + _save_default_environment(); + _save_scene_with_preview(p_file); + if ((bool)pick_main_scene->get_meta("from_native", false)) { + run_native->resume_run_native(); + } else { + _run(false, p_file); + } + } + } break; + case FILE_EXPORT_MESH_LIBRARY: { Ref<MeshLibrary> ml; if (file_export_lib_merge->is_pressed() && FileAccess::exists(p_file)) { @@ -2396,10 +2407,8 @@ void EditorNode::_run(bool p_current, const String &p_custom) { } if (scene->get_scene_file_path().is_empty()) { - current_menu_option = -1; - _menu_option(FILE_SAVE_AS_SCENE); - // Set the option to save and run so when the dialog is accepted, the scene runs. current_menu_option = FILE_SAVE_AND_RUN; + _menu_option_confirm(FILE_SAVE_AS_SCENE, true); file->set_title(TTR("Save scene before running...")); return; } @@ -2414,6 +2423,7 @@ void EditorNode::_run(bool p_current, const String &p_custom) { if (!ensure_main_scene(false)) { return; } + run_filename = GLOBAL_DEF_BASIC("application/run/main_scene", ""); } if (bool(EDITOR_GET("run/auto_save/save_before_running"))) { @@ -3181,17 +3191,15 @@ void EditorNode::_update_file_menu_opened() { Ref<Shortcut> reopen_closed_scene_sc = ED_GET_SHORTCUT("editor/reopen_closed_scene"); reopen_closed_scene_sc->set_name(TTR("Reopen Closed Scene")); - PopupMenu *pop = file_menu->get_popup(); - pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), previous_scenes.is_empty()); + file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), previous_scenes.is_empty()); const UndoRedo &undo_redo = editor_data.get_undo_redo(); - pop->set_item_disabled(pop->get_item_index(EDIT_UNDO), !undo_redo.has_undo()); - pop->set_item_disabled(pop->get_item_index(EDIT_REDO), !undo_redo.has_redo()); + file_menu->set_item_disabled(file_menu->get_item_index(EDIT_UNDO), !undo_redo.has_undo()); + file_menu->set_item_disabled(file_menu->get_item_index(EDIT_REDO), !undo_redo.has_redo()); } void EditorNode::_update_file_menu_closed() { - PopupMenu *pop = file_menu->get_popup(); - pop->set_item_disabled(pop->get_item_index(FILE_OPEN_PREV), false); + file_menu->set_item_disabled(file_menu->get_item_index(FILE_OPEN_PREV), false); } Control *EditorNode::get_main_control() { @@ -4102,8 +4110,15 @@ void EditorNode::_pick_main_scene_custom_action(const String &p_custom_action_na } pick_main_scene->hide(); - current_menu_option = SETTINGS_PICK_MAIN_SCENE; - _dialog_action(scene->get_scene_file_path()); + + if (!FileAccess::exists(scene->get_scene_file_path())) { + current_menu_option = FILE_SAVE_AND_RUN_MAIN_SCENE; + _menu_option_confirm(FILE_SAVE_AS_SCENE, true); + file->set_title(TTR("Save scene before running...")); + } else { + current_menu_option = SETTINGS_PICK_MAIN_SCENE; + _dialog_action(scene->get_scene_file_path()); + } } } @@ -6130,7 +6145,7 @@ EditorNode::EditorNode() { rmp.instantiate(); EditorInspector::add_inspector_plugin(rmp); - Ref<EditorInspectorShaderModePlugin> smp; + Ref<EditorInspectorVisualShaderModePlugin> smp; smp.instantiate(); EditorInspector::add_inspector_plugin(smp); } @@ -6450,15 +6465,20 @@ EditorNode::EditorNode() { main_control->add_theme_constant_override("separation", 0); scene_root_parent->add_child(main_control); - HBoxContainer *left_menu_hb = memnew(HBoxContainer); - menu_hb->add_child(left_menu_hb); + bool global_menu = !bool(EDITOR_GET("interface/editor/use_embedded_menu")) && DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU); - file_menu = memnew(MenuButton); - file_menu->set_flat(false); - file_menu->set_switch_on_hover(true); - file_menu->set_text(TTR("Scene")); - file_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); - left_menu_hb->add_child(file_menu); + main_menu = memnew(MenuBar); + menu_hb->add_child(main_menu); + main_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); + main_menu->set_flat(true); + main_menu->set_start_index(0); // Main menu, add to the start of global menu. + main_menu->set_prefer_global_menu(global_menu); + main_menu->set_switch_on_hover(true); + + file_menu = memnew(PopupMenu); + file_menu->set_name(TTR("Scene")); + main_menu->add_child(file_menu); + main_menu->set_menu_tooltip(0, TTR("Operations with scene files.")); prev_scene = memnew(Button); prev_scene->set_flat(true); @@ -6528,84 +6548,75 @@ EditorNode::EditorNode() { command_palette->set_title(TTR("Command Palette")); gui_base->add_child(command_palette); - PopupMenu *p; - - file_menu->set_tooltip(TTR("Operations with scene files.")); - - p = file_menu->get_popup(); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_scene", TTR("New Scene"), KeyModifierMask::CMD + Key::N), FILE_NEW_SCENE); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_inherited_scene", TTR("New Inherited Scene..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::N), FILE_NEW_INHERITED_SCENE); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/open_scene", TTR("Open Scene..."), KeyModifierMask::CMD + Key::O), FILE_OPEN_SCENE); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reopen_closed_scene", TTR("Reopen Closed Scene"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::T), FILE_OPEN_PREV); + file_menu->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_scene", TTR("New Scene"), KeyModifierMask::CMD + Key::N), FILE_NEW_SCENE); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/new_inherited_scene", TTR("New Inherited Scene..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::N), FILE_NEW_INHERITED_SCENE); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/open_scene", TTR("Open Scene..."), KeyModifierMask::CMD + Key::O), FILE_OPEN_SCENE); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reopen_closed_scene", TTR("Reopen Closed Scene"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::T), FILE_OPEN_PREV); - p->add_submenu_item(TTR("Open Recent"), "RecentScenes", FILE_OPEN_RECENT); - - p->add_separator(); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene", TTR("Save Scene"), KeyModifierMask::CMD + Key::S), FILE_SAVE_SCENE); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene_as", TTR("Save Scene As..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::S), FILE_SAVE_AS_SCENE); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_all_scenes", TTR("Save All Scenes"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::S), FILE_SAVE_ALL_SCENES); + file_menu->add_separator(); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene", TTR("Save Scene"), KeyModifierMask::CMD + Key::S), FILE_SAVE_SCENE); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_scene_as", TTR("Save Scene As..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::S), FILE_SAVE_AS_SCENE); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/save_all_scenes", TTR("Save All Scenes"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::S), FILE_SAVE_ALL_SCENES); - p->add_separator(); + file_menu->add_separator(); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_scene", TTR("Quick Open Scene..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::O), FILE_QUICK_OPEN_SCENE); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_script", TTR("Quick Open Script..."), KeyModifierMask::CMD + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN_SCRIPT); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open", TTR("Quick Open..."), KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_scene", TTR("Quick Open Scene..."), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::O), FILE_QUICK_OPEN_SCENE); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/quick_open_script", TTR("Quick Open Script..."), KeyModifierMask::CMD + KeyModifierMask::ALT + Key::O), FILE_QUICK_OPEN_SCRIPT); - p->add_separator(); + file_menu->add_separator(); export_as_menu = memnew(PopupMenu); export_as_menu->set_name("Export"); - p->add_child(export_as_menu); - p->add_submenu_item(TTR("Export As..."), "Export"); + file_menu->add_child(export_as_menu); + file_menu->add_submenu_item(TTR("Export As..."), "Export"); export_as_menu->add_shortcut(ED_SHORTCUT("editor/export_as_mesh_library", TTR("MeshLibrary...")), FILE_EXPORT_MESH_LIBRARY); export_as_menu->connect("index_pressed", callable_mp(this, &EditorNode::_export_as_menu_option)); - p->add_separator(); - p->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, true); - p->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, true); + file_menu->add_separator(); + file_menu->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO, true); + file_menu->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO, true); - p->add_separator(); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reload_saved_scene", TTR("Reload Saved Scene")), EDIT_RELOAD_SAVED_SCENE); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/close_scene", TTR("Close Scene"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::W), FILE_CLOSE); + file_menu->add_separator(); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reload_saved_scene", TTR("Reload Saved Scene")), EDIT_RELOAD_SAVED_SCENE); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/close_scene", TTR("Close Scene"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::W), FILE_CLOSE); recent_scenes = memnew(PopupMenu); recent_scenes->set_name("RecentScenes"); - p->add_child(recent_scenes); + file_menu->add_child(recent_scenes); recent_scenes->connect("id_pressed", callable_mp(this, &EditorNode::_open_recent_scene)); - p->add_separator(); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/file_quit", TTR("Quit"), KeyModifierMask::CMD + Key::Q), FILE_QUIT, true); - - project_menu = memnew(MenuButton); - project_menu->set_flat(false); - project_menu->set_switch_on_hover(true); - project_menu->set_tooltip(TTR("Miscellaneous project or scene-wide tools.")); - project_menu->set_text(TTR("Project")); - project_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); - left_menu_hb->add_child(project_menu); + if (!global_menu || !OS::get_singleton()->has_feature("macos")) { + // On macOS "Quit" and "About" options are in the "app" menu. + file_menu->add_separator(); + file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/file_quit", TTR("Quit"), KeyModifierMask::CMD + Key::Q), FILE_QUIT, true); + } - p = project_menu->get_popup(); + project_menu = memnew(PopupMenu); + project_menu->set_name(TTR("Project")); + main_menu->add_child(project_menu); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/project_settings", TTR("Project Settings..."), Key::NONE, TTR("Project Settings")), RUN_SETTINGS); - p->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); + project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/project_settings", TTR("Project Settings..."), Key::NONE, TTR("Project Settings")), RUN_SETTINGS); + project_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); vcs_actions_menu = VersionControlEditorPlugin::get_singleton()->get_version_control_actions_panel(); vcs_actions_menu->set_name("Version Control"); vcs_actions_menu->connect("index_pressed", callable_mp(this, &EditorNode::_version_control_menu_option)); - p->add_separator(); - p->add_child(vcs_actions_menu); - p->add_submenu_item(TTR("Version Control"), "Version Control"); + project_menu->add_separator(); + project_menu->add_child(vcs_actions_menu); + project_menu->add_submenu_item(TTR("Version Control"), "Version Control"); vcs_actions_menu->add_item(TTR("Create Version Control Metadata"), RUN_VCS_METADATA); vcs_actions_menu->add_item(TTR("Set Up Version Control"), RUN_VCS_SETTINGS); vcs_actions_menu->add_item(TTR("Shut Down Version Control"), RUN_VCS_SHUT_DOWN); - p->add_separator(); - p->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTR("Export..."), Key::NONE, TTR("Export")), FILE_EXPORT_PROJECT); - p->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE); - p->add_item(TTR("Open User Data Folder"), RUN_USER_DATA_FOLDER); + project_menu->add_separator(); + project_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/export", TTR("Export..."), Key::NONE, TTR("Export")), FILE_EXPORT_PROJECT); + project_menu->add_item(TTR("Install Android Build Template..."), FILE_INSTALL_ANDROID_SOURCE); + project_menu->add_item(TTR("Open User Data Folder"), RUN_USER_DATA_FOLDER); - p->add_separator(); - p->add_item(TTR("Customize Engine Build Configuration..."), TOOLS_BUILD_PROFILE_MANAGER); - p->add_separator(); + project_menu->add_separator(); + project_menu->add_item(TTR("Customize Engine Build Configuration..."), TOOLS_BUILD_PROFILE_MANAGER); + project_menu->add_separator(); plugin_config_dialog = memnew(PluginConfigDialog); plugin_config_dialog->connect("plugin_ready", callable_mp(this, &EditorNode::_on_plugin_ready)); @@ -6614,15 +6625,15 @@ EditorNode::EditorNode() { tool_menu = memnew(PopupMenu); tool_menu->set_name("Tools"); tool_menu->connect("index_pressed", callable_mp(this, &EditorNode::_tool_menu_option)); - p->add_child(tool_menu); - p->add_submenu_item(TTR("Tools"), "Tools"); + project_menu->add_child(tool_menu); + project_menu->add_submenu_item(TTR("Tools"), "Tools"); tool_menu->add_item(TTR("Orphan Resource Explorer..."), TOOLS_ORPHAN_RESOURCES); - p->add_separator(); - p->add_shortcut(ED_SHORTCUT("editor/reload_current_project", TTR("Reload Current Project")), RELOAD_CURRENT_PROJECT); + project_menu->add_separator(); + project_menu->add_shortcut(ED_SHORTCUT("editor/reload_current_project", TTR("Reload Current Project")), RELOAD_CURRENT_PROJECT); ED_SHORTCUT_AND_COMMAND("editor/quit_to_project_list", TTR("Quit to Project List"), KeyModifierMask::CMD + KeyModifierMask::SHIFT + Key::Q); ED_SHORTCUT_OVERRIDE("editor/quit_to_project_list", "macos", KeyModifierMask::SHIFT + KeyModifierMask::ALT + Key::Q); - p->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), RUN_PROJECT_MANAGER, true); + project_menu->add_shortcut(ED_GET_SHORTCUT("editor/quit_to_project_list"), RUN_PROJECT_MANAGER, true); menu_hb->add_spacer(); @@ -6630,85 +6641,79 @@ EditorNode::EditorNode() { menu_hb->add_child(main_editor_button_vb); // Options are added and handled by DebuggerEditorPlugin. - debug_menu = memnew(MenuButton); - debug_menu->set_flat(false); - debug_menu->set_switch_on_hover(true); - debug_menu->set_text(TTR("Debug")); - debug_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); - left_menu_hb->add_child(debug_menu); + debug_menu = memnew(PopupMenu); + debug_menu->set_name(TTR("Debug")); + main_menu->add_child(debug_menu); menu_hb->add_spacer(); - settings_menu = memnew(MenuButton); - settings_menu->set_flat(false); - settings_menu->set_switch_on_hover(true); - settings_menu->set_text(TTR("Editor")); - settings_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); - left_menu_hb->add_child(settings_menu); - - p = settings_menu->get_popup(); + settings_menu = memnew(PopupMenu); + settings_menu->set_name(TTR("Editor")); + main_menu->add_child(settings_menu); ED_SHORTCUT_AND_COMMAND("editor/editor_settings", TTR("Editor Settings...")); ED_SHORTCUT_OVERRIDE("editor/editor_settings", "macos", KeyModifierMask::CMD + Key::COMMA); - p->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES); - p->add_shortcut(ED_SHORTCUT("editor/command_palette", TTR("Command Palette..."), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::P), HELP_COMMAND_PALETTE); - p->add_separator(); + settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/editor_settings"), SETTINGS_PREFERENCES); + settings_menu->add_shortcut(ED_SHORTCUT("editor/command_palette", TTR("Command Palette..."), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::P), HELP_COMMAND_PALETTE); + settings_menu->add_separator(); editor_layouts = memnew(PopupMenu); editor_layouts->set_name("Layouts"); - p->add_child(editor_layouts); + settings_menu->add_child(editor_layouts); editor_layouts->connect("id_pressed", callable_mp(this, &EditorNode::_layout_menu_option)); - p->add_submenu_item(TTR("Editor Layout"), "Layouts"); - p->add_separator(); + settings_menu->add_submenu_item(TTR("Editor Layout"), "Layouts"); + settings_menu->add_separator(); ED_SHORTCUT_AND_COMMAND("editor/take_screenshot", TTR("Take Screenshot"), KeyModifierMask::CTRL | Key::F12); ED_SHORTCUT_OVERRIDE("editor/take_screenshot", "macos", KeyModifierMask::CMD | Key::F12); - p->add_shortcut(ED_GET_SHORTCUT("editor/take_screenshot"), EDITOR_SCREENSHOT); + settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/take_screenshot"), EDITOR_SCREENSHOT); - p->set_item_tooltip(-1, TTR("Screenshots are stored in the Editor Data/Settings Folder.")); + settings_menu->set_item_tooltip(-1, TTR("Screenshots are stored in the Editor Data/Settings Folder.")); ED_SHORTCUT_AND_COMMAND("editor/fullscreen_mode", TTR("Toggle Fullscreen"), KeyModifierMask::SHIFT | Key::F11); ED_SHORTCUT_OVERRIDE("editor/fullscreen_mode", "macos", KeyModifierMask::CMD | KeyModifierMask::CTRL | Key::F); - p->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN); + settings_menu->add_shortcut(ED_GET_SHORTCUT("editor/fullscreen_mode"), SETTINGS_TOGGLE_FULLSCREEN); - p->add_separator(); + settings_menu->add_separator(); if (OS::get_singleton()->get_data_path() == OS::get_singleton()->get_config_path()) { // Configuration and data folders are located in the same place (Windows/MacOS). - p->add_item(TTR("Open Editor Data/Settings Folder"), SETTINGS_EDITOR_DATA_FOLDER); + settings_menu->add_item(TTR("Open Editor Data/Settings Folder"), SETTINGS_EDITOR_DATA_FOLDER); } else { // Separate configuration and data folders (Linux). - p->add_item(TTR("Open Editor Data Folder"), SETTINGS_EDITOR_DATA_FOLDER); - p->add_item(TTR("Open Editor Settings Folder"), SETTINGS_EDITOR_CONFIG_FOLDER); + settings_menu->add_item(TTR("Open Editor Data Folder"), SETTINGS_EDITOR_DATA_FOLDER); + settings_menu->add_item(TTR("Open Editor Settings Folder"), SETTINGS_EDITOR_CONFIG_FOLDER); } - p->add_separator(); + settings_menu->add_separator(); - p->add_item(TTR("Manage Editor Features..."), SETTINGS_MANAGE_FEATURE_PROFILES); - p->add_item(TTR("Manage Export Templates..."), SETTINGS_MANAGE_EXPORT_TEMPLATES); + settings_menu->add_item(TTR("Manage Editor Features..."), SETTINGS_MANAGE_FEATURE_PROFILES); + settings_menu->add_item(TTR("Manage Export Templates..."), SETTINGS_MANAGE_EXPORT_TEMPLATES); - help_menu = memnew(MenuButton); - help_menu->set_flat(false); - help_menu->set_switch_on_hover(true); - help_menu->set_text(TTR("Help")); - help_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("MenuHover"), SNAME("EditorStyles"))); - left_menu_hb->add_child(help_menu); + help_menu = memnew(PopupMenu); + help_menu->set_name(TTR("Help")); + main_menu->add_child(help_menu); - p = help_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); + help_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); ED_SHORTCUT_AND_COMMAND("editor/editor_help", TTR("Search Help"), Key::F1); ED_SHORTCUT_OVERRIDE("editor/editor_help", "macos", KeyModifierMask::ALT | Key::SPACE); - p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons")), ED_GET_SHORTCUT("editor/editor_help"), HELP_SEARCH); - p->add_separator(); - p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/online_docs", TTR("Online Documentation")), HELP_DOCS); - p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/q&a", TTR("Questions & Answers")), HELP_QA); - p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG); - p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE); - p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK); - p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/community", TTR("Community")), HELP_COMMUNITY); - p->add_separator(); - p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("Godot"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/about", TTR("About Godot")), HELP_ABOUT); - p->add_icon_shortcut(gui_base->get_theme_icon(SNAME("Heart"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT); + help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons")), ED_GET_SHORTCUT("editor/editor_help"), HELP_SEARCH); + help_menu->add_separator(); + help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/online_docs", TTR("Online Documentation")), HELP_DOCS); + help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/q&a", TTR("Questions & Answers")), HELP_QA); + help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/report_a_bug", TTR("Report a Bug")), HELP_REPORT_A_BUG); + help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/suggest_a_feature", TTR("Suggest a Feature")), HELP_SUGGEST_A_FEATURE); + help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/send_docs_feedback", TTR("Send Docs Feedback")), HELP_SEND_DOCS_FEEDBACK); + help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/community", TTR("Community")), HELP_COMMUNITY); + help_menu->add_separator(); + if (!global_menu || !OS::get_singleton()->has_feature("macos")) { + // On macOS "Quit" and "About" options are in the "app" menu. + help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("Godot"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/about", TTR("About Godot")), HELP_ABOUT); + } + help_menu->add_icon_shortcut(gui_base->get_theme_icon(SNAME("Heart"), SNAME("EditorIcons")), ED_SHORTCUT_AND_COMMAND("editor/support_development", TTR("Support Godot Development")), HELP_SUPPORT_GODOT_DEVELOPMENT); + + Control *right_spacer = memnew(Control); + menu_hb->add_child(right_spacer); HBoxContainer *play_hb = memnew(HBoxContainer); menu_hb->add_child(play_hb); @@ -6857,7 +6862,7 @@ EditorNode::EditorNode() { right_menu_hb->add_child(update_spinner); update_spinner->set_icon(gui_base->get_theme_icon(SNAME("Progress1"), SNAME("EditorIcons"))); update_spinner->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); - p = update_spinner->get_popup(); + PopupMenu *p = update_spinner->get_popup(); p->add_radio_check_item(TTR("Update Continuously"), SETTINGS_UPDATE_CONTINUOUSLY); p->add_radio_check_item(TTR("Update When Changed"), SETTINGS_UPDATE_WHEN_CHANGED); p->add_separator(); @@ -7077,11 +7082,11 @@ EditorNode::EditorNode() { gui_base->add_child(file_script); file_script->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action)); - file_menu->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); + file_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); file_menu->connect("about_to_popup", callable_mp(this, &EditorNode::_update_file_menu_opened)); - file_menu->get_popup()->connect("popup_hide", callable_mp(this, &EditorNode::_update_file_menu_closed)); + file_menu->connect("popup_hide", callable_mp(this, &EditorNode::_update_file_menu_closed)); - settings_menu->get_popup()->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); + settings_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option)); file->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action)); file_templates->connect("file_selected", callable_mp(this, &EditorNode::_dialog_action)); @@ -7364,11 +7369,14 @@ EditorNode::EditorNode() { screenshot_timer = memnew(Timer); screenshot_timer->set_one_shot(true); - screenshot_timer->set_wait_time(settings_menu->get_popup()->get_submenu_popup_delay() + 0.1f); + screenshot_timer->set_wait_time(settings_menu->get_submenu_popup_delay() + 0.1f); screenshot_timer->connect("timeout", callable_mp(this, &EditorNode::_request_screenshot)); add_child(screenshot_timer); screenshot_timer->set_owner(get_owner()); + main_menu->set_custom_minimum_size(Size2(MAX(main_menu->get_minimum_size().x, play_hb->get_minimum_size().x + right_menu_hb->get_minimum_size().x), 0)); + right_spacer->set_custom_minimum_size(Size2(MAX(0, main_menu->get_minimum_size().x - play_hb->get_minimum_size().x - right_menu_hb->get_minimum_size().x), 0)); + String exec = OS::get_singleton()->get_executable_path(); // Save editor executable path for third-party tools. EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "executable_path", exec); diff --git a/editor/editor_node.h b/editor/editor_node.h index 0201e84eaf..f7a102b4c7 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -78,6 +78,7 @@ class FileSystemDock; class HSplitContainer; class ImportDock; class LinkButton; +class MenuBar; class MenuButton; class NodeDock; class OrphanResourcesDialog; @@ -141,6 +142,7 @@ private: FILE_SAVE_AS_SCENE, FILE_SAVE_ALL_SCENES, FILE_SAVE_AND_RUN, + FILE_SAVE_AND_RUN_MAIN_SCENE, FILE_SHOW_IN_FILESYSTEM, FILE_EXPORT_PROJECT, FILE_EXPORT_MESH_LIBRARY, @@ -321,11 +323,12 @@ private: HBoxContainer *menu_hb = nullptr; Control *main_control = nullptr; - MenuButton *file_menu = nullptr; - MenuButton *project_menu = nullptr; - MenuButton *debug_menu = nullptr; - MenuButton *settings_menu = nullptr; - MenuButton *help_menu = nullptr; + MenuBar *main_menu = nullptr; + PopupMenu *file_menu = nullptr; + PopupMenu *project_menu = nullptr; + PopupMenu *debug_menu = nullptr; + PopupMenu *settings_menu = nullptr; + PopupMenu *help_menu = nullptr; PopupMenu *tool_menu = nullptr; PopupMenu *export_as_menu = nullptr; Button *export_button = nullptr; diff --git a/editor/editor_path.cpp b/editor/editor_path.cpp index dc77b5fea9..87ebd3e1c1 100644 --- a/editor/editor_path.cpp +++ b/editor/editor_path.cpp @@ -72,7 +72,7 @@ void EditorPath::_add_children_to_popup(Object *p_obj, int p_depth) { int index = sub_objects_menu->get_item_count(); sub_objects_menu->add_icon_item(icon, proper_name, objects.size()); - sub_objects_menu->set_item_horizontal_offset(index, p_depth * 10 * EDSCALE); + sub_objects_menu->set_item_indent(index, p_depth); objects.push_back(obj->get_instance_id()); _add_children_to_popup(obj, p_depth + 1); diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 566c22f5a9..400ad1ebac 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -445,7 +445,7 @@ void EditorPlugin::add_control_to_container(CustomControlContainer p_location, C CanvasItemEditor::get_singleton()->get_bottom_split()->add_child(p_control); } break; - case CONTAINER_PROPERTY_EDITOR_BOTTOM: { + case CONTAINER_INSPECTOR_BOTTOM: { InspectorDock::get_singleton()->get_addon_area()->add_child(p_control); } break; @@ -498,7 +498,7 @@ void EditorPlugin::remove_control_from_container(CustomControlContainer p_locati CanvasItemEditor::get_singleton()->get_bottom_split()->remove_child(p_control); } break; - case CONTAINER_PROPERTY_EDITOR_BOTTOM: { + case CONTAINER_INSPECTOR_BOTTOM: { InspectorDock::get_singleton()->get_addon_area()->remove_child(p_control); } break; @@ -950,7 +950,7 @@ void EditorPlugin::_bind_methods() { BIND_ENUM_CONSTANT(CONTAINER_CANVAS_EDITOR_SIDE_LEFT); BIND_ENUM_CONSTANT(CONTAINER_CANVAS_EDITOR_SIDE_RIGHT); BIND_ENUM_CONSTANT(CONTAINER_CANVAS_EDITOR_BOTTOM); - BIND_ENUM_CONSTANT(CONTAINER_PROPERTY_EDITOR_BOTTOM); + BIND_ENUM_CONSTANT(CONTAINER_INSPECTOR_BOTTOM); BIND_ENUM_CONSTANT(CONTAINER_PROJECT_SETTING_TAB_LEFT); BIND_ENUM_CONSTANT(CONTAINER_PROJECT_SETTING_TAB_RIGHT); diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h index 84c63d1021..3f9d276b6a 100644 --- a/editor/editor_plugin.h +++ b/editor/editor_plugin.h @@ -184,7 +184,7 @@ public: CONTAINER_CANVAS_EDITOR_SIDE_LEFT, CONTAINER_CANVAS_EDITOR_SIDE_RIGHT, CONTAINER_CANVAS_EDITOR_BOTTOM, - CONTAINER_PROPERTY_EDITOR_BOTTOM, + CONTAINER_INSPECTOR_BOTTOM, CONTAINER_PROJECT_SETTING_TAB_LEFT, CONTAINER_PROJECT_SETTING_TAB_RIGHT, }; diff --git a/editor/editor_sectioned_inspector.cpp b/editor/editor_sectioned_inspector.cpp index 801a1a4641..cbca3e9dcd 100644 --- a/editor/editor_sectioned_inspector.cpp +++ b/editor/editor_sectioned_inspector.cpp @@ -114,11 +114,11 @@ class SectionedInspectorFilter : public Object { } bool property_can_revert(const String &p_name) { - return edited->call("property_can_revert", section + "/" + p_name); + return edited->property_can_revert(section + "/" + p_name); } Variant property_get_revert(const String &p_name) { - return edited->call("property_get_revert", section + "/" + p_name); + return edited->property_get_revert(section + "/" + p_name); } protected: diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 80e77a1125..36e64cbd7a 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -406,6 +406,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("interface/editor/debug/enable_pseudolocalization", false); set_restart_if_changed("interface/editor/debug/enable_pseudolocalization", true); // Use pseudolocalization in editor. + EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/use_embedded_menu", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING_USAGE(Variant::FLOAT, PROPERTY_HINT_RANGE, "interface/editor/custom_display_scale", 1.0, "0.5,3,0.01", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/editor/main_font_size", 14, "8,48,1") @@ -1073,24 +1074,25 @@ Variant _EDITOR_GET(const String &p_setting) { return EditorSettings::get_singleton()->get(p_setting); } -bool EditorSettings::property_can_revert(const String &p_setting) { - if (!props.has(p_setting)) { +bool EditorSettings::_property_can_revert(const StringName &p_name) const { + if (!props.has(p_name)) { return false; } - if (!props[p_setting].has_default_value) { + if (!props[p_name].has_default_value) { return false; } - return props[p_setting].initial != props[p_setting].variant; + return props[p_name].initial != props[p_name].variant; } -Variant EditorSettings::property_get_revert(const String &p_setting) { - if (!props.has(p_setting) || !props[p_setting].has_default_value) { - return Variant(); +bool EditorSettings::_property_get_revert(const StringName &p_name, Variant &r_property) const { + if (!props.has(p_name) || !props[p_name].has_default_value) { + return false; } - return props[p_setting].initial; + r_property = props[p_name].initial; + return true; } void EditorSettings::add_property_hint(const PropertyInfo &p_hint) { @@ -1621,8 +1623,6 @@ void EditorSettings::_bind_methods() { ClassDB::bind_method(D_METHOD("get_setting", "name"), &EditorSettings::get_setting); ClassDB::bind_method(D_METHOD("erase", "property"), &EditorSettings::erase); ClassDB::bind_method(D_METHOD("set_initial_value", "name", "value", "update_current"), &EditorSettings::set_initial_value); - ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &EditorSettings::property_can_revert); - ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &EditorSettings::property_get_revert); ClassDB::bind_method(D_METHOD("add_property_info", "info"), &EditorSettings::_add_property_info_bind); ClassDB::bind_method(D_METHOD("set_project_metadata", "section", "key", "data"), &EditorSettings::set_project_metadata); diff --git a/editor/editor_settings.h b/editor/editor_settings.h index 5faeec88c8..f921171c57 100644 --- a/editor/editor_settings.h +++ b/editor/editor_settings.h @@ -100,6 +100,8 @@ private: void _initial_set(const StringName &p_name, const Variant &p_value); void _get_property_list(List<PropertyInfo> *p_list) const; void _add_property_info_bind(const Dictionary &p_info); + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; void _load_defaults(Ref<ConfigFile> p_extra_config = Ref<ConfigFile>()); void _load_godot2_text_editor_theme(); @@ -138,8 +140,6 @@ public: _set_only(p_setting, p_value); } } - bool property_can_revert(const String &p_setting); - Variant property_get_revert(const String &p_setting); void add_property_hint(const PropertyInfo &p_hint); Array get_changed_settings() const; bool check_changed_settings_in_group(const String &p_setting_prefix) const; diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index c58d1263f3..3da9899052 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -788,6 +788,25 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { editor_log_button_pressed->set_border_color(accent_color); theme->set_stylebox("pressed", "EditorLogFilterButton", editor_log_button_pressed); + // MenuBar + theme->set_stylebox("normal", "MenuBar", style_widget); + theme->set_stylebox("hover", "MenuBar", style_widget_hover); + theme->set_stylebox("pressed", "MenuBar", style_widget_pressed); + theme->set_stylebox("focus", "MenuBar", style_widget_focus); + theme->set_stylebox("disabled", "MenuBar", style_widget_disabled); + + theme->set_color("font_color", "MenuBar", font_color); + theme->set_color("font_hover_color", "MenuBar", font_hover_color); + theme->set_color("font_focus_color", "MenuBar", font_focus_color); + theme->set_color("font_pressed_color", "MenuBar", accent_color); + theme->set_color("font_disabled_color", "MenuBar", font_disabled_color); + + theme->set_color("icon_normal_color", "MenuBar", icon_normal_color); + theme->set_color("icon_hover_color", "MenuBar", icon_hover_color); + theme->set_color("icon_focus_color", "MenuBar", icon_focus_color); + theme->set_color("icon_pressed_color", "MenuBar", icon_pressed_color); + theme->set_color("icon_disabled_color", "MenuBar", icon_disabled_color); + // OptionButton Ref<StyleBoxFlat> style_option_button_focus = style_widget_focus->duplicate(); Ref<StyleBoxFlat> style_option_button_normal = style_widget->duplicate(); @@ -912,6 +931,9 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // Always display a border for PopupMenus so they can be distinguished from their background. style_popup_menu->set_border_width_all(EDSCALE); style_popup_menu->set_border_color(dark_color_2); + // Popups are separate windows by default in the editor. Windows currently don't support per-pixel transparency + // in 4.0, and even if it was, it may not always work in practice (e.g. running with compositing disabled). + style_popup_menu->set_corner_radius_all(0); theme->set_stylebox("panel", "PopupMenu", style_popup_menu); Ref<StyleBoxFlat> style_menu_hover = style_widget_hover->duplicate(); @@ -1460,6 +1482,17 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // PopupPanel theme->set_stylebox("panel", "PopupPanel", style_popup); + Ref<StyleBoxFlat> control_editor_popup_style = style_popup->duplicate(); + control_editor_popup_style->set_shadow_size(0); + control_editor_popup_style->set_default_margin(SIDE_LEFT, default_margin_size * EDSCALE); + control_editor_popup_style->set_default_margin(SIDE_TOP, default_margin_size * EDSCALE); + control_editor_popup_style->set_default_margin(SIDE_RIGHT, default_margin_size * EDSCALE); + control_editor_popup_style->set_default_margin(SIDE_BOTTOM, default_margin_size * EDSCALE); + control_editor_popup_style->set_border_width_all(0); + + theme->set_stylebox("panel", "ControlEditorPopupButton", control_editor_popup_style); + theme->set_type_variation("ControlEditorPopupButton", "PopupPanel"); + // SpinBox theme->set_icon("updown", "SpinBox", theme->get_icon(SNAME("GuiSpinboxUpdown"), SNAME("EditorIcons"))); theme->set_icon("updown_disabled", "SpinBox", theme->get_icon(SNAME("GuiSpinboxUpdownDisabled"), SNAME("EditorIcons"))); diff --git a/editor/icons/ContainerLayout.svg b/editor/icons/ContainerLayout.svg new file mode 100644 index 0000000000..feabc2c350 --- /dev/null +++ b/editor/icons/ContainerLayout.svg @@ -0,0 +1 @@ +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero"><path d="m3 1c-1.105 0-2 .895-2 2h2zm2 0v2h2v-2zm4 0v2h2v-2zm4 0v2h2c0-1.105-.895-2-2-2zm-12 4v2h2v-2zm12 0v2h2v-2zm-12 4v2h2v-2zm12 0v2h2v-2zm-12 4c0 1.105.895 2 2 2v-2zm4 0v2h2v-2zm4 0v2h2v-2zm4 0v2c1.105 0 2-.895 2-2z" fill="#8eef97"/><path d="m7 7h4v4h-4z" fill="#d6d6d6"/></g></svg> diff --git a/editor/icons/ControlLayout.svg b/editor/icons/ControlLayout.svg index 11dd2554be..8503e3313c 100644 --- a/editor/icons/ControlLayout.svg +++ b/editor/icons/ControlLayout.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1 1v14h14v-14zm2 2h3v3h-3zm5 0h5v3h-5zm-5 5h3v5h-3zm5 0h5v5h-5z" fill="#8eef97"/></svg> +<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero"><path d="m11.793 8v-2h-3.793v-2.113h-2v2.113h-2.142v2h2.142v3.967h2v-3.967z" fill="#d6d6d6"/><path d="m8 .345c-4.199 0-7.655 3.456-7.655 7.655s3.456 7.655 7.655 7.655 7.655-3.456 7.655-7.655-3.456-7.655-7.655-7.655zm0 1.999c3.103 0 5.656 2.553 5.656 5.656s-2.553 5.656-5.656 5.656-5.656-2.553-5.656-5.656 2.553-5.656 5.656-5.656z" fill="#8eef97"/></g></svg> diff --git a/editor/icons/DefaultProjectIcon.svg b/editor/icons/DefaultProjectIcon.svg index f81ba4d390..adc26df6c2 100644 --- a/editor/icons/DefaultProjectIcon.svg +++ b/editor/icons/DefaultProjectIcon.svg @@ -1 +1 @@ -<svg height="64" viewBox="0 0 64 64" width="64" xmlns="http://www.w3.org/2000/svg"><g stroke-linejoin="round"><path d="m8 0c-4.432 0-8 3.568-8 8v48c0 4.432 3.568 8 8 8h48c4.432 0 8-3.568 8-8v-48c0-4.432-3.568-8-8-8z" fill="#355570" stroke-linecap="round" stroke-width="2"/><path d="m8 0c-4.432 0-8 3.568-8 8v48c0 4.432 3.568 8 8 8h48c4.432 0 8-3.568 8-8v-48c0-4.432-3.568-8-8-8zm0 2h48c3.324 0 6 2.676 6 6v48c0 3.324-2.676 6-6 6h-48c-3.324 0-6-2.676-6-6v-48c0-3.324 2.676-6 6-6z" fill-opacity=".19608" stroke-linecap="round" stroke-width="2"/><path d="m27.254 10c-2.1314.47383-4.2401 1.134-6.2168 2.1289.04521 1.7455.15796 3.4164.38672 5.1152-.76768.4919-1.574.91443-2.291 1.4902-.72854.5604-1.4731 1.0965-2.1328 1.752-1.3179-.8716-2.7115-1.691-4.1484-2.4141-1.549 1.667-2.9985 3.4672-4.1816 5.4805.89011 1.4399 1.8209 2.7894 2.8242 4.0703h.027343v9.9453 1.2617 1.1504l-.009765 1.6309h-.001953c.0031.7321.011718 1.5356.011718 1.6953 0 7.1942 9.1264 10.652 20.465 10.691h.013672.013672c11.338-.04 20.461-3.4972 20.461-10.691 0-.1626.010282-.96271.013672-1.6953h-.001953l-.011719-1.6309v-.98633l.003907-.001953v-11.369h.027343c1.0035-1.2809 1.9337-2.6304 2.8242-4.0703-1.1827-2.0133-2.6327-3.8135-4.1816-5.4805-1.4366.7231-2.8325 1.5425-4.1504 2.4141-.65947-.6555-1.4013-1.1916-2.1309-1.752-.71682-.5758-1.5248-.99833-2.291-1.4902.22813-1.6988.3413-3.3697.38672-5.1152-1.977-.99494-4.0863-1.6551-6.2188-2.1289-.85139 1.4309-1.6285 2.9812-2.3066 4.4961-.80409-.1344-1.613-.18571-2.4219-.19531h-.015625-.015625c-.81037.01-1.6176.060513-2.4219.19531-.67768-1.5149-1.4559-3.0652-2.3086-4.4961z" fill="#fff" stroke="#fff" stroke-width="3"/></g><g stroke-width=".32031" transform="matrix(.050279 0 0 .050279 6.2574 1.18)"><path d="m0 0s-.325 1.994-.515 1.976l-36.182-3.491c-2.879-.278-5.115-2.574-5.317-5.459l-.994-14.247-27.992-1.997-1.904 12.912c-.424 2.872-2.932 5.037-5.835 5.037h-38.188c-2.902 0-5.41-2.165-5.834-5.037l-1.905-12.912-27.992 1.997-.994 14.247c-.202 2.886-2.438 5.182-5.317 5.46l-36.2 3.49c-.187.018-.324-1.978-.511-1.978l-.049-7.83 30.658-4.944 1.004-14.374c.203-2.91 2.551-5.263 5.463-5.472l38.551-2.75c.146-.01.29-.016.434-.016 2.897 0 5.401 2.166 5.825 5.038l1.959 13.286h28.005l1.959-13.286c.423-2.871 2.93-5.037 5.831-5.037.142 0 .284.005.423.015l38.556 2.75c2.911.209 5.26 2.562 5.463 5.472l1.003 14.374 30.645 4.966z" fill="#fff" transform="matrix(4.1626 0 0 -4.1626 919.24 771.67)"/><path d="m0 0v-59.041c.108-.001.216-.005.323-.015l36.196-3.49c1.896-.183 3.382-1.709 3.514-3.609l1.116-15.978 31.574-2.253 2.175 14.747c.282 1.912 1.922 3.329 3.856 3.329h38.188c1.933 0 3.573-1.417 3.855-3.329l2.175-14.747 31.575 2.253 1.115 15.978c.133 1.9 1.618 3.425 3.514 3.609l36.182 3.49c.107.01.214.014.322.015v4.711l.015.005v54.325h.134c4.795 6.12 9.232 12.569 13.487 19.449-5.651 9.62-12.575 18.217-19.976 26.182-6.864-3.455-13.531-7.369-19.828-11.534-3.151 3.132-6.7 5.694-10.186 8.372-3.425 2.751-7.285 4.768-10.946 7.118 1.09 8.117 1.629 16.108 1.846 24.448-9.446 4.754-19.519 7.906-29.708 10.17-4.068-6.837-7.788-14.241-11.028-21.479-3.842.642-7.702.88-11.567.926v.006c-.027 0-.052-.006-.075-.006-.024 0-.049.006-.073.006v-.006c-3.872-.046-7.729-.284-11.572-.926-3.238 7.238-6.956 14.642-11.03 21.479-10.184-2.264-20.258-5.416-29.703-10.17.216-8.34.755-16.331 1.848-24.448-3.668-2.35-7.523-4.367-10.949-7.118-3.481-2.678-7.036-5.24-10.188-8.372-6.297 4.165-12.962 8.079-19.828 11.534-7.401-7.965-14.321-16.562-19.974-26.182 4.253-6.88 8.693-13.329 13.487-19.449z" fill="#478cbf" transform="matrix(4.1626 0 0 -4.1626 104.7 525.91)"/><path d="m0 0-1.121-16.063c-.135-1.936-1.675-3.477-3.611-3.616l-38.555-2.751c-.094-.007-.188-.01-.281-.01-1.916 0-3.569 1.406-3.852 3.33l-2.211 14.994h-31.459l-2.211-14.994c-.297-2.018-2.101-3.469-4.133-3.32l-38.555 2.751c-1.936.139-3.476 1.68-3.611 3.616l-1.121 16.063-32.547 3.138c.015-3.498.06-7.33.06-8.093 0-34.374 43.605-50.896 97.781-51.086h.133c54.176.19 97.766 16.712 97.766 51.086 0 .777.047 4.593.063 8.093z" fill="#478cbf" transform="matrix(4.1626 0 0 -4.1626 784.07 817.24)"/><path d="m0 0c0-12.052-9.765-21.815-21.813-21.815-12.042 0-21.81 9.763-21.81 21.815 0 12.044 9.768 21.802 21.81 21.802 12.048 0 21.813-9.758 21.813-21.802" fill="#fff" transform="matrix(4.1626 0 0 -4.1626 389.21 625.67)"/><path d="m0 0c0-7.994-6.479-14.473-14.479-14.473-7.996 0-14.479 6.479-14.479 14.473s6.483 14.479 14.479 14.479c8 0 14.479-6.485 14.479-14.479" fill="#414042" transform="matrix(4.1626 0 0 -4.1626 367.37 631.06)"/><path d="m0 0c-3.878 0-7.021 2.858-7.021 6.381v20.081c0 3.52 3.143 6.381 7.021 6.381s7.028-2.861 7.028-6.381v-20.081c0-3.523-3.15-6.381-7.028-6.381" fill="#fff" transform="matrix(4.1626 0 0 -4.1626 511.99 724.74)"/><path d="m0 0c0-12.052 9.765-21.815 21.815-21.815 12.041 0 21.808 9.763 21.808 21.815 0 12.044-9.767 21.802-21.808 21.802-12.05 0-21.815-9.758-21.815-21.802" fill="#fff" transform="matrix(4.1626 0 0 -4.1626 634.79 625.67)"/><path d="m0 0c0-7.994 6.477-14.473 14.471-14.473 8.002 0 14.479 6.479 14.479 14.473s-6.477 14.479-14.479 14.479c-7.994 0-14.471-6.485-14.471-14.479" fill="#414042" transform="matrix(4.1626 0 0 -4.1626 656.64 631.06)"/></g></svg> +<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(32 32)"><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99z" fill="#363d52"/><path d="m-16-32c-8.86 0-16 7.13-16 15.99v95.98c0 8.86 7.13 15.99 16 15.99h96c8.86 0 16-7.13 16-15.99v-95.98c0-8.85-7.14-15.99-16-15.99zm0 4h96c6.64 0 12 5.35 12 11.99v95.98c0 6.64-5.35 11.99-12 11.99h-96c-6.64 0-12-5.35-12-11.99v-95.98c0-6.64 5.36-11.99 12-11.99z" fill-opacity=".4"/></g><g stroke-width="9.92746" transform="matrix(.10073078 0 0 .10073078 12.425923 2.256365)"><path d="m0 0s-.325 1.994-.515 1.976l-36.182-3.491c-2.879-.278-5.115-2.574-5.317-5.459l-.994-14.247-27.992-1.997-1.904 12.912c-.424 2.872-2.932 5.037-5.835 5.037h-38.188c-2.902 0-5.41-2.165-5.834-5.037l-1.905-12.912-27.992 1.997-.994 14.247c-.202 2.886-2.438 5.182-5.317 5.46l-36.2 3.49c-.187.018-.324-1.978-.511-1.978l-.049-7.83 30.658-4.944 1.004-14.374c.203-2.91 2.551-5.263 5.463-5.472l38.551-2.75c.146-.01.29-.016.434-.016 2.897 0 5.401 2.166 5.825 5.038l1.959 13.286h28.005l1.959-13.286c.423-2.871 2.93-5.037 5.831-5.037.142 0 .284.005.423.015l38.556 2.75c2.911.209 5.26 2.562 5.463 5.472l1.003 14.374 30.645 4.966z" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 919.24059 771.67186)"/><path d="m0 0v-47.514-6.035-5.492c.108-.001.216-.005.323-.015l36.196-3.49c1.896-.183 3.382-1.709 3.514-3.609l1.116-15.978 31.574-2.253 2.175 14.747c.282 1.912 1.922 3.329 3.856 3.329h38.188c1.933 0 3.573-1.417 3.855-3.329l2.175-14.747 31.575 2.253 1.115 15.978c.133 1.9 1.618 3.425 3.514 3.609l36.182 3.49c.107.01.214.014.322.015v4.711l.015.005v54.325c5.09692 6.4164715 9.92323 13.494208 13.621 19.449-5.651 9.62-12.575 18.217-19.976 26.182-6.864-3.455-13.531-7.369-19.828-11.534-3.151 3.132-6.7 5.694-10.186 8.372-3.425 2.751-7.285 4.768-10.946 7.118 1.09 8.117 1.629 16.108 1.846 24.448-9.446 4.754-19.519 7.906-29.708 10.17-4.068-6.837-7.788-14.241-11.028-21.479-3.842.642-7.702.88-11.567.926v.006c-.027 0-.052-.006-.075-.006-.024 0-.049.006-.073.006v-.006c-3.872-.046-7.729-.284-11.572-.926-3.238 7.238-6.956 14.642-11.03 21.479-10.184-2.264-20.258-5.416-29.703-10.17.216-8.34.755-16.331 1.848-24.448-3.668-2.35-7.523-4.367-10.949-7.118-3.481-2.678-7.036-5.24-10.188-8.372-6.297 4.165-12.962 8.079-19.828 11.534-7.401-7.965-14.321-16.562-19.974-26.182 4.4426579-6.973692 9.2079702-13.9828876 13.621-19.449z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 104.69892 525.90697)"/><path d="m0 0-1.121-16.063c-.135-1.936-1.675-3.477-3.611-3.616l-38.555-2.751c-.094-.007-.188-.01-.281-.01-1.916 0-3.569 1.406-3.852 3.33l-2.211 14.994h-31.459l-2.211-14.994c-.297-2.018-2.101-3.469-4.133-3.32l-38.555 2.751c-1.936.139-3.476 1.68-3.611 3.616l-1.121 16.063-32.547 3.138c.015-3.498.06-7.33.06-8.093 0-34.374 43.605-50.896 97.781-51.086h.066.067c54.176.19 97.766 16.712 97.766 51.086 0 .777.047 4.593.063 8.093z" fill="#478cbf" transform="matrix(4.162611 0 0 -4.162611 784.07144 817.24284)"/><path d="m0 0c0-12.052-9.765-21.815-21.813-21.815-12.042 0-21.81 9.763-21.81 21.815 0 12.044 9.768 21.802 21.81 21.802 12.048 0 21.813-9.758 21.813-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 389.21484 625.67104)"/><path d="m0 0c0-7.994-6.479-14.473-14.479-14.473-7.996 0-14.479 6.479-14.479 14.473s6.483 14.479 14.479 14.479c8 0 14.479-6.485 14.479-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 367.36686 631.05679)"/><path d="m0 0c-3.878 0-7.021 2.858-7.021 6.381v20.081c0 3.52 3.143 6.381 7.021 6.381s7.028-2.861 7.028-6.381v-20.081c0-3.523-3.15-6.381-7.028-6.381" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 511.99336 724.73954)"/><path d="m0 0c0-12.052 9.765-21.815 21.815-21.815 12.041 0 21.808 9.763 21.808 21.815 0 12.044-9.767 21.802-21.808 21.802-12.05 0-21.815-9.758-21.815-21.802" fill="#fff" transform="matrix(4.162611 0 0 -4.162611 634.78706 625.67104)"/><path d="m0 0c0-7.994 6.477-14.473 14.471-14.473 8.002 0 14.479 6.479 14.479 14.473s-6.477 14.479-14.479 14.479c-7.994 0-14.471-6.485-14.471-14.479" fill="#414042" transform="matrix(4.162611 0 0 -4.162611 656.64056 631.05679)"/></g></svg> diff --git a/editor/icons/MenuBar.svg b/editor/icons/MenuBar.svg new file mode 100644 index 0000000000..0a53f07f85 --- /dev/null +++ b/editor/icons/MenuBar.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 6a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1Zm1 2h10v2H3Zm0 3h10v2H3ZM1 1v4h6V1Zm1 1h4L4 4Z" fill="#8eef97"/></svg> diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp index 2756e45cf4..ff5d112956 100644 --- a/editor/plugins/control_editor_plugin.cpp +++ b/editor/plugins/control_editor_plugin.cpp @@ -31,10 +31,13 @@ #include "control_editor_plugin.h" #include "editor/editor_node.h" +#include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "scene/gui/separator.h" +// Inspector controls. + void ControlPositioningWarning::_update_warning() { if (!control_node) { title_icon->set_texture(nullptr); @@ -49,7 +52,7 @@ void ControlPositioningWarning::_update_warning() { title_label->set_text(TTR("This node doesn't have a control parent.")); hint_label->set_text(TTR("Use the appropriate layout properties depending on where you are going to put it.")); } else if (Object::cast_to<Container>(parent_node)) { - title_icon->set_texture(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); + title_icon->set_texture(get_theme_icon(SNAME("ContainerLayout"), SNAME("EditorIcons"))); title_label->set_text(TTR("This node is a child of a container.")); hint_label->set_text(TTR("Use container properties for positioning.")); } else { @@ -448,37 +451,280 @@ bool EditorInspectorPluginControl::parse_property(Object *p_object, const Varian return false; } -void ControlEditorToolbar::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) { +// Toolbars controls. + +Size2 ControlEditorPopupButton::get_minimum_size() const { + Vector2 base_size = Vector2(26, 26) * EDSCALE; + + if (arrow_icon.is_null()) { + return base_size; + } + + Vector2 final_size; + final_size.x = base_size.x + arrow_icon->get_width(); + final_size.y = MAX(base_size.y, arrow_icon->get_height()); + + return final_size; +} + +void ControlEditorPopupButton::toggled(bool p_pressed) { + if (!p_pressed) { + return; + } + + Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale(); + + popup_panel->set_size(Size2(size.width, 0)); + Point2 gp = get_screen_position(); + gp.y += size.y; + if (is_layout_rtl()) { + gp.x += size.width - popup_panel->get_size().width; + } + popup_panel->set_position(gp); + + popup_panel->popup(); +} + +void ControlEditorPopupButton::_popup_visibility_changed(bool p_visible) { + set_pressed(p_visible); +} + +void ControlEditorPopupButton::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + arrow_icon = get_theme_icon("select_arrow", "Tree"); + } break; + + case NOTIFICATION_DRAW: { + if (arrow_icon.is_valid()) { + Vector2 arrow_pos = Point2(26, 0) * EDSCALE; + arrow_pos.y = get_size().y / 2 - arrow_icon->get_height() / 2; + draw_texture(arrow_icon, arrow_pos); + } + } break; + + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: { + popup_panel->set_layout_direction((Window::LayoutDirection)get_layout_direction()); + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible_in_tree()) { + popup_panel->hide(); + } + } break; + } +} + +ControlEditorPopupButton::ControlEditorPopupButton() { + set_flat(true); + set_toggle_mode(true); + set_focus_mode(FOCUS_NONE); + + popup_panel = memnew(PopupPanel); + popup_panel->set_theme_type_variation("ControlEditorPopupButton"); + add_child(popup_panel); + popup_panel->connect("about_to_popup", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(true)); + popup_panel->connect("popup_hide", callable_mp(this, &ControlEditorPopupButton::_popup_visibility_changed).bind(false)); + + popup_vbox = memnew(VBoxContainer); + popup_panel->add_child(popup_vbox); +} + +void ControlEditorPresetPicker::_add_row_button(HBoxContainer *p_row, const int p_preset, const String &p_name) { + ERR_FAIL_COND(preset_buttons.has(p_preset)); + + Button *b = memnew(Button); + b->set_custom_minimum_size(Size2i(36, 36) * EDSCALE); + b->set_icon_alignment(HORIZONTAL_ALIGNMENT_CENTER); + b->set_tooltip(p_name); + b->set_flat(true); + p_row->add_child(b); + b->connect("pressed", callable_mp(this, &ControlEditorPresetPicker::_preset_button_pressed).bind(p_preset)); + + preset_buttons[p_preset] = b; +} + +void ControlEditorPresetPicker::_add_separator(BoxContainer *p_box, Separator *p_separator) { + p_separator->add_theme_constant_override("separation", grid_separation); + p_separator->set_custom_minimum_size(Size2i(1, 1)); + p_box->add_child(p_separator); +} + +void AnchorPresetPicker::_preset_button_pressed(const int p_preset) { + emit_signal("anchors_preset_selected", p_preset); +} + +void AnchorPresetPicker::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + preset_buttons[PRESET_TOP_LEFT]->set_icon(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons"))); + preset_buttons[PRESET_CENTER_TOP]->set_icon(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons"))); + preset_buttons[PRESET_TOP_RIGHT]->set_icon(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_CENTER_LEFT]->set_icon(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons"))); + preset_buttons[PRESET_CENTER]->set_icon(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons"))); + preset_buttons[PRESET_CENTER_RIGHT]->set_icon(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_BOTTOM_LEFT]->set_icon(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons"))); + preset_buttons[PRESET_CENTER_BOTTOM]->set_icon(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons"))); + preset_buttons[PRESET_BOTTOM_RIGHT]->set_icon(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_TOP_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons"))); + preset_buttons[PRESET_HCENTER_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons"))); + preset_buttons[PRESET_BOTTOM_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_LEFT_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons"))); + preset_buttons[PRESET_VCENTER_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons"))); + preset_buttons[PRESET_RIGHT_WIDE]->set_icon(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons"))); + + preset_buttons[PRESET_FULL_RECT]->set_icon(get_theme_icon(SNAME("ControlAlignFullRect"), SNAME("EditorIcons"))); + } break; + } +} + +void AnchorPresetPicker::_bind_methods() { + ADD_SIGNAL(MethodInfo("anchors_preset_selected", PropertyInfo(Variant::INT, "preset"))); +} + +AnchorPresetPicker::AnchorPresetPicker() { + VBoxContainer *main_vb = memnew(VBoxContainer); + main_vb->add_theme_constant_override("separation", grid_separation); + add_child(main_vb); + + HBoxContainer *top_row = memnew(HBoxContainer); + top_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + top_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(top_row); + + _add_row_button(top_row, PRESET_TOP_LEFT, TTR("Top Left")); + _add_row_button(top_row, PRESET_CENTER_TOP, TTR("Center Top")); + _add_row_button(top_row, PRESET_TOP_RIGHT, TTR("Top Right")); + _add_separator(top_row, memnew(VSeparator)); + _add_row_button(top_row, PRESET_TOP_WIDE, TTR("Top Wide")); + + HBoxContainer *mid_row = memnew(HBoxContainer); + mid_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + mid_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(mid_row); + + _add_row_button(mid_row, PRESET_CENTER_LEFT, TTR("Center Left")); + _add_row_button(mid_row, PRESET_CENTER, TTR("Center")); + _add_row_button(mid_row, PRESET_CENTER_RIGHT, TTR("Center Right")); + _add_separator(mid_row, memnew(VSeparator)); + _add_row_button(mid_row, PRESET_HCENTER_WIDE, TTR("HCenter Wide")); + + HBoxContainer *bot_row = memnew(HBoxContainer); + bot_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + bot_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(bot_row); + + _add_row_button(bot_row, PRESET_BOTTOM_LEFT, TTR("Bottom Left")); + _add_row_button(bot_row, PRESET_CENTER_BOTTOM, TTR("Center Bottom")); + _add_row_button(bot_row, PRESET_BOTTOM_RIGHT, TTR("Bottom Right")); + _add_separator(bot_row, memnew(VSeparator)); + _add_row_button(bot_row, PRESET_BOTTOM_WIDE, TTR("Bottom Wide")); + + _add_separator(main_vb, memnew(HSeparator)); + + HBoxContainer *extra_row = memnew(HBoxContainer); + extra_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + extra_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(extra_row); + + _add_row_button(extra_row, PRESET_LEFT_WIDE, TTR("Left Wide")); + _add_row_button(extra_row, PRESET_VCENTER_WIDE, TTR("VCenter Wide")); + _add_row_button(extra_row, PRESET_RIGHT_WIDE, TTR("Right Wide")); + _add_separator(extra_row, memnew(VSeparator)); + _add_row_button(extra_row, PRESET_FULL_RECT, TTR("Full Rect")); +} + +void SizeFlagPresetPicker::_preset_button_pressed(const int p_preset) { + int flags = (SizeFlags)p_preset; + if (expand_button->is_pressed()) { + flags |= SIZE_EXPAND; + } + + emit_signal("size_flags_selected", flags); +} + +void SizeFlagPresetPicker::set_allowed_flags(Vector<SizeFlags> &p_flags) { + preset_buttons[SIZE_SHRINK_BEGIN]->set_disabled(!p_flags.has(SIZE_SHRINK_BEGIN)); + preset_buttons[SIZE_SHRINK_CENTER]->set_disabled(!p_flags.has(SIZE_SHRINK_CENTER)); + preset_buttons[SIZE_SHRINK_END]->set_disabled(!p_flags.has(SIZE_SHRINK_END)); + preset_buttons[SIZE_FILL]->set_disabled(!p_flags.has(SIZE_FILL)); + + expand_button->set_disabled(!p_flags.has(SIZE_EXPAND)); + if (p_flags.has(SIZE_EXPAND)) { + expand_button->set_tooltip(TTR("Enable to also set the Expand flag.\nDisable to only set Shrink/Fill flags.")); + } else { + expand_button->set_pressed(false); + expand_button->set_tooltip(TTR("Some parents of the selected nodes do not support the Expand flag.")); + } +} + +void SizeFlagPresetPicker::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + if (vertical) { + preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons"))); + preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons"))); + preset_buttons[SIZE_SHRINK_END]->set_icon(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons"))); + + preset_buttons[SIZE_FILL]->set_icon(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons"))); + } else { + preset_buttons[SIZE_SHRINK_BEGIN]->set_icon(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons"))); + preset_buttons[SIZE_SHRINK_CENTER]->set_icon(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons"))); + preset_buttons[SIZE_SHRINK_END]->set_icon(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons"))); + + preset_buttons[SIZE_FILL]->set_icon(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons"))); + } + } break; + } +} + +void SizeFlagPresetPicker::_bind_methods() { + ADD_SIGNAL(MethodInfo("size_flags_selected", PropertyInfo(Variant::INT, "size_flags"))); +} + +SizeFlagPresetPicker::SizeFlagPresetPicker(bool p_vertical) { + vertical = p_vertical; + + VBoxContainer *main_vb = memnew(VBoxContainer); + add_child(main_vb); + + HBoxContainer *main_row = memnew(HBoxContainer); + main_row->set_alignment(BoxContainer::ALIGNMENT_CENTER); + main_row->add_theme_constant_override("separation", grid_separation); + main_vb->add_child(main_row); + + _add_row_button(main_row, SIZE_SHRINK_BEGIN, TTR("Shrink Begin")); + _add_row_button(main_row, SIZE_SHRINK_CENTER, TTR("Shrink Center")); + _add_row_button(main_row, SIZE_SHRINK_END, TTR("Shrink End")); + _add_separator(main_row, memnew(VSeparator)); + _add_row_button(main_row, SIZE_FILL, TTR("Fill")); + + expand_button = memnew(CheckBox); + expand_button->set_flat(true); + expand_button->set_text(TTR("Align with Expand")); + expand_button->set_tooltip(TTR("Enable to also set the Expand flag.\nDisable to only set Shrink/Fill flags.")); + main_vb->add_child(expand_button); +} + +// Toolbar. + +void ControlEditorToolbar::_anchors_preset_selected(int p_preset) { + LayoutPreset preset = (LayoutPreset)p_preset; List<Node *> selection = editor_selection->get_selected_node_list(); - undo_redo->create_action(TTR("Change Anchors and Offsets")); + undo_redo->create_action(TTR("Change Anchors, Offsets, Grow Direction")); for (Node *E : selection) { Control *control = Object::cast_to<Control>(E); if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_TOP_RIGHT: - case PRESET_BOTTOM_LEFT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_LEFT: - case PRESET_CENTER_TOP: - case PRESET_CENTER_RIGHT: - case PRESET_CENTER_BOTTOM: - case PRESET_CENTER: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_KEEP_SIZE); - break; - case PRESET_LEFT_WIDE: - case PRESET_TOP_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_VCENTER_WIDE: - case PRESET_HCENTER_WIDE: - case PRESET_FULL_RECT: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_MINSIZE); - break; - } + undo_redo->add_do_property(control, "anchors_preset", preset); undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); } } @@ -489,10 +735,10 @@ void ControlEditorToolbar::_set_anchors_and_offsets_preset(Control::LayoutPreset anchor_mode_button->set_pressed(anchors_mode); } -void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() { +void ControlEditorToolbar::_anchors_to_current_ratio() { List<Node *> selection = editor_selection->get_selected_node_list(); - undo_redo->create_action(TTR("Change Anchors and Offsets")); + undo_redo->create_action(TTR("Change Anchors, Offsets (Keep Ratio)")); for (Node *E : selection) { Control *control = Object::cast_to<Control>(E); @@ -521,44 +767,41 @@ void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() { undo_redo->commit_action(); } -void ControlEditorToolbar::_set_anchors_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); +void ControlEditorToolbar::_anchor_mode_toggled(bool p_status) { + List<Control *> selection = _get_edited_controls(); + for (Control *E : selection) { + if (Object::cast_to<Container>(E->get_parent())) { + continue; + } - undo_redo->create_action(TTR("Change Anchors")); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + if (p_status) { + E->set_meta("_edit_use_anchors_", true); + } else { + E->remove_meta("_edit_use_anchors_"); } } - undo_redo->commit_action(); + anchors_mode = p_status; + CanvasItemEditor::get_singleton()->update_viewport(); } -void ControlEditorToolbar::_set_container_h_preset(Control::SizeFlags p_preset) { +void ControlEditorToolbar::_container_flags_selected(int p_flags, bool p_vertical) { List<Node *> selection = editor_selection->get_selected_node_list(); - undo_redo->create_action(TTR("Change Horizontal Size Flags")); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_h_size_flags", p_preset); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } + if (p_vertical) { + undo_redo->create_action(TTR("Change Vertical Size Flags")); + } else { + undo_redo->create_action(TTR("Change Horizontal Size Flags")); } - undo_redo->commit_action(); -} - -void ControlEditorToolbar::_set_container_v_preset(Control::SizeFlags p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Horizontal Size Flags")); for (Node *E : selection) { Control *control = Object::cast_to<Control>(E); if (control) { - undo_redo->add_do_method(control, "set_v_size_flags", p_preset); + if (p_vertical) { + undo_redo->add_do_method(control, "set_v_size_flags", p_flags); + } else { + undo_redo->add_do_method(control, "set_h_size_flags", p_flags); + } undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); } } @@ -594,400 +837,205 @@ Vector2 ControlEditorToolbar::_position_to_anchor(const Control *p_control, Vect return output; } -void ControlEditorToolbar::_button_toggle_anchor_mode(bool p_status) { - List<Control *> selection = _get_edited_controls(false, false); - for (Control *E : selection) { - if (Object::cast_to<Container>(E->get_parent())) { - continue; - } - - if (p_status) { - E->set_meta("_edit_use_anchors_", true); - } else { - E->remove_meta("_edit_use_anchors_"); - } - } - - anchors_mode = p_status; - CanvasItemEditor::get_singleton()->update_viewport(); -} - bool ControlEditorToolbar::_is_node_locked(const Node *p_node) { return p_node->get_meta("_edit_lock_", false); } -List<Control *> ControlEditorToolbar::_get_edited_controls(bool retrieve_locked, bool remove_controls_if_parent_in_selection) { +List<Control *> ControlEditorToolbar::_get_edited_controls() { List<Control *> selection; for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { Control *control = Object::cast_to<Control>(E.key); - if (control && control->is_visible_in_tree() && control->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(control))) { + if (control && control->is_visible_in_tree() && control->get_viewport() == EditorNode::get_singleton()->get_scene_root() && !_is_node_locked(control)) { selection.push_back(control); } } - if (remove_controls_if_parent_in_selection) { - List<Control *> filtered_selection; - for (Control *E : selection) { - if (!selection.find(E->get_parent())) { - filtered_selection.push_back(E); - } - } - return filtered_selection; - } - return selection; } -void ControlEditorToolbar::_popup_callback(int p_op) { - switch (p_op) { - case ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT: { - _set_anchors_and_offsets_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP: { - _set_anchors_and_offsets_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM: { - _set_anchors_and_offsets_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER: { - _set_anchors_and_offsets_preset(PRESET_CENTER); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE: { - _set_anchors_and_offsets_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_FULL_RECT: { - _set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO: { - _set_anchors_and_offsets_to_keep_ratio(); - } break; - - case ANCHORS_PRESET_TOP_LEFT: { - _set_anchors_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_PRESET_TOP_RIGHT: { - _set_anchors_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_PRESET_BOTTOM_LEFT: { - _set_anchors_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_PRESET_BOTTOM_RIGHT: { - _set_anchors_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_LEFT: { - _set_anchors_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_PRESET_CENTER_RIGHT: { - _set_anchors_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_TOP: { - _set_anchors_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_PRESET_CENTER_BOTTOM: { - _set_anchors_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_PRESET_CENTER: { - _set_anchors_preset(PRESET_CENTER); - } break; - case ANCHORS_PRESET_TOP_WIDE: { - _set_anchors_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_PRESET_LEFT_WIDE: { - _set_anchors_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_PRESET_RIGHT_WIDE: { - _set_anchors_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_PRESET_BOTTOM_WIDE: { - _set_anchors_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_PRESET_VCENTER_WIDE: { - _set_anchors_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_PRESET_HCENTER_WIDE: { - _set_anchors_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_PRESET_FULL_RECT: { - _set_anchors_preset(Control::PRESET_FULL_RECT); - } break; - - case CONTAINERS_H_PRESET_FILL: { - _set_container_h_preset(Control::SIZE_FILL); - } break; - case CONTAINERS_H_PRESET_FILL_EXPAND: { - _set_container_h_preset(Control::SIZE_EXPAND_FILL); - } break; - case CONTAINERS_H_PRESET_SHRINK_BEGIN: { - _set_container_h_preset(Control::SIZE_SHRINK_BEGIN); - } break; - case CONTAINERS_H_PRESET_SHRINK_CENTER: { - _set_container_h_preset(Control::SIZE_SHRINK_CENTER); - } break; - case CONTAINERS_H_PRESET_SHRINK_END: { - _set_container_h_preset(Control::SIZE_SHRINK_END); - } break; - - case CONTAINERS_V_PRESET_FILL: { - _set_container_v_preset(Control::SIZE_FILL); - } break; - case CONTAINERS_V_PRESET_FILL_EXPAND: { - _set_container_v_preset(Control::SIZE_EXPAND_FILL); - } break; - case CONTAINERS_V_PRESET_SHRINK_BEGIN: { - _set_container_v_preset(Control::SIZE_SHRINK_BEGIN); - } break; - case CONTAINERS_V_PRESET_SHRINK_CENTER: { - _set_container_v_preset(Control::SIZE_SHRINK_CENTER); - } break; - case CONTAINERS_V_PRESET_SHRINK_END: { - _set_container_v_preset(Control::SIZE_SHRINK_END); - } break; - } -} - void ControlEditorToolbar::_selection_changed() { - // Update the anchors_mode. - int nb_controls = 0; - int nb_valid_controls = 0; - int nb_anchors_mode = 0; + // Update toolbar visibility. + bool has_controls = false; + bool has_control_parents = false; + bool has_container_parents = false; + + // Also update which size flags can be configured for the selected nodes. + Vector<SizeFlags> allowed_h_flags = { + SIZE_SHRINK_BEGIN, + SIZE_SHRINK_CENTER, + SIZE_SHRINK_END, + SIZE_FILL, + SIZE_EXPAND, + }; + Vector<SizeFlags> allowed_v_flags = { + SIZE_SHRINK_BEGIN, + SIZE_SHRINK_CENTER, + SIZE_SHRINK_END, + SIZE_FILL, + SIZE_EXPAND, + }; - List<Node *> selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + Control *control = Object::cast_to<Control>(E.key); if (!control) { continue; } + has_controls = true; - nb_controls++; + if (Object::cast_to<Control>(control->get_parent())) { + has_control_parents = true; + } if (Object::cast_to<Container>(control->get_parent())) { - continue; + has_container_parents = true; + + Container *parent_container = Object::cast_to<Container>(control->get_parent()); + + Vector<int> container_h_flags = parent_container->get_allowed_size_flags_horizontal(); + Vector<SizeFlags> tmp_flags = allowed_h_flags.duplicate(); + for (int i = 0; i < allowed_h_flags.size(); i++) { + if (!container_h_flags.has((int)allowed_h_flags[i])) { + tmp_flags.erase(allowed_h_flags[i]); + } + } + allowed_h_flags = tmp_flags; + + Vector<int> container_v_flags = parent_container->get_allowed_size_flags_vertical(); + tmp_flags = allowed_v_flags.duplicate(); + for (int i = 0; i < allowed_v_flags.size(); i++) { + if (!container_v_flags.has((int)allowed_v_flags[i])) { + tmp_flags.erase(allowed_v_flags[i]); + } + } + allowed_v_flags = tmp_flags; } + } + + // Set general toolbar visibility. + set_visible(has_controls); + + // Set anchor tools visibility. + if (has_controls && (!has_control_parents || !has_container_parents)) { + anchors_button->set_visible(true); + anchor_mode_button->set_visible(true); + + // Update anchor mode. + int nb_valid_controls = 0; + int nb_anchors_mode = 0; - nb_valid_controls++; - if (control->get_meta("_edit_use_anchors_", false)) { - nb_anchors_mode++; + List<Node *> selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (!control) { + continue; + } + if (Object::cast_to<Container>(control->get_parent())) { + continue; + } + + nb_valid_controls++; + if (control->get_meta("_edit_use_anchors_", false)) { + nb_anchors_mode++; + } } + + anchors_mode = (nb_valid_controls == nb_anchors_mode); + anchor_mode_button->set_pressed(anchors_mode); + } else { + anchors_button->set_visible(false); + anchor_mode_button->set_visible(false); + anchor_mode_button->set_pressed(false); } - anchors_mode = (nb_valid_controls == nb_anchors_mode); - anchor_mode_button->set_pressed(anchors_mode); + // Set container tools visibility. + if (has_controls && (!has_control_parents || has_container_parents)) { + containers_button->set_visible(true); - if (nb_controls > 0) { - set_physics_process(true); + // Update allowed size flags. + if (has_container_parents) { + container_h_picker->set_allowed_flags(allowed_h_flags); + container_v_picker->set_allowed_flags(allowed_v_flags); + } else { + Vector<SizeFlags> allowed_all_flags = { + SIZE_SHRINK_BEGIN, + SIZE_SHRINK_CENTER, + SIZE_SHRINK_END, + SIZE_FILL, + SIZE_EXPAND, + }; + + container_h_picker->set_allowed_flags(allowed_all_flags); + container_v_picker->set_allowed_flags(allowed_all_flags); + } } else { - set_physics_process(false); - set_visible(false); + containers_button->set_visible(false); } } void ControlEditorToolbar::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - anchor_presets_menu->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); - - PopupMenu *p = anchor_presets_menu->get_popup(); - p->clear(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignFullRect"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_FULL_RECT); - p->add_icon_item(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")), TTR("Keep Current Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); - p->set_item_tooltip(19, TTR("Adjust anchors and offsets to match the current rect size.")); - - p->add_separator(); - p->add_submenu_item(TTR("Anchors only"), "Anchors"); - p->set_item_icon(21, get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - - anchors_popup->clear(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_PRESET_CENTER); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignFullRect"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_FULL_RECT); - + case NOTIFICATION_THEME_CHANGED: { + anchors_button->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - - container_h_presets_menu->set_icon(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); - container_v_presets_menu->set_icon(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); - - p = container_h_presets_menu->get_popup(); - p->clear(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_H_PRESET_FILL); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignHCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_H_PRESET_FILL_EXPAND); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterLeft"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_H_PRESET_SHRINK_BEGIN); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_H_PRESET_SHRINK_CENTER); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterRight"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_H_PRESET_SHRINK_END); - - p = container_v_presets_menu->get_popup(); - p->clear(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill"), CONTAINERS_V_PRESET_FILL); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignVCenterWide"), SNAME("EditorIcons")), TTR("Fill & Expand"), CONTAINERS_V_PRESET_FILL_EXPAND); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterTop"), SNAME("EditorIcons")), TTR("Shrink Begin"), CONTAINERS_V_PRESET_SHRINK_BEGIN); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), CONTAINERS_V_PRESET_SHRINK_CENTER); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenterBottom"), SNAME("EditorIcons")), TTR("Shrink End"), CONTAINERS_V_PRESET_SHRINK_END); - } break; - - case NOTIFICATION_PHYSICS_PROCESS: { - bool has_control_parents = false; - bool has_container_parents = false; - - // Update the viewport if the canvas_item changes - List<Control *> selection = _get_edited_controls(true); - for (Control *control : selection) { - if (Object::cast_to<Control>(control->get_parent())) { - has_control_parents = true; - } - if (Object::cast_to<Container>(control->get_parent())) { - has_container_parents = true; - } - } - - // Show / Hide the control layout buttons. - if (selection.size() > 0) { - set_visible(true); - - // Toggle anchor and container layout buttons depending on parents of the selected nodes. - // - If there are no control parents, enable everything. - // - If there are container parents, then enable only container buttons. - // - If there are NO container parents, then enable only anchor buttons. - bool enable_anchors = false; - bool enable_containers = false; - if (!has_control_parents) { - enable_anchors = true; - enable_containers = true; - } else if (has_container_parents) { - enable_containers = true; - } else { - enable_anchors = true; - } - - if (enable_anchors) { - anchor_presets_menu->set_visible(true); - anchor_mode_button->set_visible(true); - } else { - anchor_presets_menu->set_visible(false); - anchor_mode_button->set_visible(false); - } - - if (enable_containers) { - container_h_presets_menu->set_visible(true); - container_v_presets_menu->set_visible(true); - } else { - container_h_presets_menu->set_visible(false); - container_v_presets_menu->set_visible(false); - } - } else { - set_visible(false); - } + containers_button->set_icon(get_theme_icon(SNAME("ContainerLayout"), SNAME("EditorIcons"))); } break; } } ControlEditorToolbar::ControlEditorToolbar() { - anchor_presets_menu = memnew(MenuButton); - anchor_presets_menu->set_shortcut_context(this); - anchor_presets_menu->set_text(TTR("Anchors")); - anchor_presets_menu->set_tooltip(TTR("Presets for the anchor and offset values of a Control node.")); - add_child(anchor_presets_menu); - anchor_presets_menu->set_switch_on_hover(true); + add_child(memnew(VSeparator)); - PopupMenu *p = anchor_presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + // Anchor and offset tools. + anchors_button = memnew(ControlEditorPopupButton); + anchors_button->set_tooltip(TTR("Presets for the anchor and offset values of a Control node.")); + add_child(anchors_button); - anchors_popup = memnew(PopupMenu); - p->add_child(anchors_popup); - anchors_popup->set_name("Anchors"); - anchors_popup->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + Label *anchors_label = memnew(Label); + anchors_label->set_text(TTR("Anchor preset")); + anchors_button->get_popup_hbox()->add_child(anchors_label); + AnchorPresetPicker *anchors_picker = memnew(AnchorPresetPicker); + anchors_picker->set_h_size_flags(SIZE_SHRINK_CENTER); + anchors_button->get_popup_hbox()->add_child(anchors_picker); + anchors_picker->connect("anchors_preset_selected", callable_mp(this, &ControlEditorToolbar::_anchors_preset_selected)); + + anchors_button->get_popup_hbox()->add_child(memnew(HSeparator)); + + Button *keep_ratio_button = memnew(Button); + keep_ratio_button->set_text_alignment(HORIZONTAL_ALIGNMENT_LEFT); + keep_ratio_button->set_text(TTR("Set to Current Ratio")); + keep_ratio_button->set_tooltip(TTR("Adjust anchors and offsets to match the current rect size.")); + anchors_button->get_popup_hbox()->add_child(keep_ratio_button); + keep_ratio_button->connect("pressed", callable_mp(this, &ControlEditorToolbar::_anchors_to_current_ratio)); anchor_mode_button = memnew(Button); anchor_mode_button->set_flat(true); anchor_mode_button->set_toggle_mode(true); anchor_mode_button->set_tooltip(TTR("When active, moving Control nodes changes their anchors instead of their offsets.")); add_child(anchor_mode_button); - anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_button_toggle_anchor_mode)); - - add_child(memnew(VSeparator)); - - container_h_presets_menu = memnew(MenuButton); - container_h_presets_menu->set_shortcut_context(this); - container_h_presets_menu->set_text(TTR("Horizontal")); - container_h_presets_menu->set_tooltip(TTR("Horizontal sizing setting for children of a Container node.")); - add_child(container_h_presets_menu); - container_h_presets_menu->set_switch_on_hover(true); - - p = container_h_presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); - - container_v_presets_menu = memnew(MenuButton); - container_v_presets_menu->set_shortcut_context(this); - container_v_presets_menu->set_text(TTR("Vertical")); - container_v_presets_menu->set_tooltip(TTR("Vertical sizing setting for children of a Container node.")); - add_child(container_v_presets_menu); - container_v_presets_menu->set_switch_on_hover(true); - - p = container_v_presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); - + anchor_mode_button->connect("toggled", callable_mp(this, &ControlEditorToolbar::_anchor_mode_toggled)); + + // Container tools. + containers_button = memnew(ControlEditorPopupButton); + containers_button->set_tooltip(TTR("Sizing settings for children of a Container node.")); + add_child(containers_button); + + Label *container_h_label = memnew(Label); + container_h_label->set_text(TTR("Horizontal alignment")); + containers_button->get_popup_hbox()->add_child(container_h_label); + container_h_picker = memnew(SizeFlagPresetPicker(false)); + containers_button->get_popup_hbox()->add_child(container_h_picker); + container_h_picker->connect("size_flags_selected", callable_mp(this, &ControlEditorToolbar::_container_flags_selected).bind(false)); + + containers_button->get_popup_hbox()->add_child(memnew(HSeparator)); + + Label *container_v_label = memnew(Label); + container_v_label->set_text(TTR("Vertical alignment")); + containers_button->get_popup_hbox()->add_child(container_v_label); + container_v_picker = memnew(SizeFlagPresetPicker(true)); + containers_button->get_popup_hbox()->add_child(container_v_picker); + container_v_picker->connect("size_flags_selected", callable_mp(this, &ControlEditorToolbar::_container_flags_selected).bind(true)); + + // Editor connections. undo_redo = EditorNode::get_singleton()->get_undo_redo(); editor_selection = EditorNode::get_singleton()->get_editor_selection(); editor_selection->add_editor_plugin(this); @@ -998,6 +1046,8 @@ ControlEditorToolbar::ControlEditorToolbar() { ControlEditorToolbar *ControlEditorToolbar::singleton = nullptr; +// Editor plugin. + ControlEditorPlugin::ControlEditorPlugin() { toolbar = memnew(ControlEditorToolbar); toolbar->hide(); diff --git a/editor/plugins/control_editor_plugin.h b/editor/plugins/control_editor_plugin.h index 11389bc095..f1b9190a0b 100644 --- a/editor/plugins/control_editor_plugin.h +++ b/editor/plugins/control_editor_plugin.h @@ -33,14 +33,18 @@ #include "editor/editor_plugin.h" #include "scene/gui/box_container.h" +#include "scene/gui/button.h" #include "scene/gui/check_box.h" #include "scene/gui/control.h" #include "scene/gui/label.h" #include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/panel_container.h" +#include "scene/gui/popup.h" +#include "scene/gui/separator.h" #include "scene/gui/texture_rect.h" +// Inspector controls. class ControlPositioningWarning : public MarginContainer { GDCLASS(ControlPositioningWarning, MarginContainer); @@ -125,102 +129,101 @@ public: virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; }; +// Toolbar controls. +class ControlEditorPopupButton : public Button { + GDCLASS(ControlEditorPopupButton, Button); + + Ref<Texture2D> arrow_icon; + + PopupPanel *popup_panel = nullptr; + VBoxContainer *popup_vbox = nullptr; + + void _popup_visibility_changed(bool p_visible); + +protected: + void _notification(int p_what); + +public: + virtual Size2 get_minimum_size() const override; + virtual void toggled(bool p_pressed) override; + + VBoxContainer *get_popup_hbox() const { return popup_vbox; } + + ControlEditorPopupButton(); +}; + +class ControlEditorPresetPicker : public MarginContainer { + GDCLASS(ControlEditorPresetPicker, MarginContainer); + + virtual void _preset_button_pressed(const int p_preset) {} + +protected: + static constexpr int grid_separation = 0; + HashMap<int, Button *> preset_buttons; + + void _add_row_button(HBoxContainer *p_row, const int p_preset, const String &p_name); + void _add_separator(BoxContainer *p_box, Separator *p_separator); + +public: + ControlEditorPresetPicker() {} +}; + +class AnchorPresetPicker : public ControlEditorPresetPicker { + GDCLASS(AnchorPresetPicker, ControlEditorPresetPicker); + + virtual void _preset_button_pressed(const int p_preset) override; + +protected: + void _notification(int p_notification); + static void _bind_methods(); + +public: + AnchorPresetPicker(); +}; + +class SizeFlagPresetPicker : public ControlEditorPresetPicker { + GDCLASS(SizeFlagPresetPicker, ControlEditorPresetPicker); + + CheckBox *expand_button; + + bool vertical = false; + + virtual void _preset_button_pressed(const int p_preset) override; + +protected: + void _notification(int p_notification); + static void _bind_methods(); + +public: + void set_allowed_flags(Vector<SizeFlags> &p_flags); + + SizeFlagPresetPicker(bool p_vertical); +}; + class ControlEditorToolbar : public HBoxContainer { GDCLASS(ControlEditorToolbar, HBoxContainer); UndoRedo *undo_redo = nullptr; EditorSelection *editor_selection = nullptr; - enum MenuOption { - ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT, - ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP, - ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM, - ANCHORS_AND_OFFSETS_PRESET_CENTER, - ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE, - ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE, - ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_FULL_RECT, - - ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO, - - ANCHORS_PRESET_TOP_LEFT, - ANCHORS_PRESET_TOP_RIGHT, - ANCHORS_PRESET_BOTTOM_LEFT, - ANCHORS_PRESET_BOTTOM_RIGHT, - ANCHORS_PRESET_CENTER_LEFT, - ANCHORS_PRESET_CENTER_RIGHT, - ANCHORS_PRESET_CENTER_TOP, - ANCHORS_PRESET_CENTER_BOTTOM, - ANCHORS_PRESET_CENTER, - ANCHORS_PRESET_TOP_WIDE, - ANCHORS_PRESET_LEFT_WIDE, - ANCHORS_PRESET_RIGHT_WIDE, - ANCHORS_PRESET_BOTTOM_WIDE, - ANCHORS_PRESET_VCENTER_WIDE, - ANCHORS_PRESET_HCENTER_WIDE, - ANCHORS_PRESET_FULL_RECT, - - // Offsets Presets are not currently in use. - OFFSETS_PRESET_TOP_LEFT, - OFFSETS_PRESET_TOP_RIGHT, - OFFSETS_PRESET_BOTTOM_LEFT, - OFFSETS_PRESET_BOTTOM_RIGHT, - OFFSETS_PRESET_CENTER_LEFT, - OFFSETS_PRESET_CENTER_RIGHT, - OFFSETS_PRESET_CENTER_TOP, - OFFSETS_PRESET_CENTER_BOTTOM, - OFFSETS_PRESET_CENTER, - OFFSETS_PRESET_TOP_WIDE, - OFFSETS_PRESET_LEFT_WIDE, - OFFSETS_PRESET_RIGHT_WIDE, - OFFSETS_PRESET_BOTTOM_WIDE, - OFFSETS_PRESET_VCENTER_WIDE, - OFFSETS_PRESET_HCENTER_WIDE, - OFFSETS_PRESET_FULL_RECT, - - CONTAINERS_H_PRESET_FILL, - CONTAINERS_H_PRESET_FILL_EXPAND, - CONTAINERS_H_PRESET_SHRINK_BEGIN, - CONTAINERS_H_PRESET_SHRINK_CENTER, - CONTAINERS_H_PRESET_SHRINK_END, - CONTAINERS_V_PRESET_FILL, - CONTAINERS_V_PRESET_FILL_EXPAND, - CONTAINERS_V_PRESET_SHRINK_BEGIN, - CONTAINERS_V_PRESET_SHRINK_CENTER, - CONTAINERS_V_PRESET_SHRINK_END, - }; - - MenuButton *anchor_presets_menu = nullptr; - PopupMenu *anchors_popup = nullptr; - MenuButton *container_h_presets_menu = nullptr; - MenuButton *container_v_presets_menu = nullptr; - + ControlEditorPopupButton *anchors_button = nullptr; + ControlEditorPopupButton *containers_button = nullptr; Button *anchor_mode_button = nullptr; + SizeFlagPresetPicker *container_h_picker = nullptr; + SizeFlagPresetPicker *container_v_picker = nullptr; + bool anchors_mode = false; - void _set_anchors_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_to_keep_ratio(); - void _set_container_h_preset(Control::SizeFlags p_preset); - void _set_container_v_preset(Control::SizeFlags p_preset); + void _anchors_preset_selected(int p_preset); + void _anchors_to_current_ratio(); + void _anchor_mode_toggled(bool p_status); + void _container_flags_selected(int p_flags, bool p_vertical); Vector2 _anchor_to_position(const Control *p_control, Vector2 anchor); Vector2 _position_to_anchor(const Control *p_control, Vector2 position); - - void _button_toggle_anchor_mode(bool p_status); - bool _is_node_locked(const Node *p_node); - List<Control *> _get_edited_controls(bool retrieve_locked = false, bool remove_controls_if_parent_in_selection = true); - void _popup_callback(int p_op); + List<Control *> _get_edited_controls(); void _selection_changed(); protected: @@ -236,6 +239,7 @@ public: ControlEditorToolbar(); }; +// Editor plugin. class ControlEditorPlugin : public EditorPlugin { GDCLASS(ControlEditorPlugin, EditorPlugin); diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index c572b5b7fe..40e7dfead2 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -40,7 +40,7 @@ #include "editor/plugins/script_editor_plugin.h" #include "scene/gui/menu_button.h" -DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) { +DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) { EditorDebuggerServer::initialize(); ED_SHORTCUT("debugger/step_into", TTR("Step Into"), Key::F11); @@ -61,30 +61,29 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) { // Main editor debug menu. debug_menu = p_debug_menu; - PopupMenu *p = debug_menu->get_popup(); - p->set_hide_on_checkable_item_selection(false); - p->add_check_shortcut(ED_SHORTCUT("editor/deploy_with_remote_debug", TTR("Deploy with Remote Debug")), RUN_DEPLOY_REMOTE_DEBUG); - p->set_item_tooltip(-1, + debug_menu->set_hide_on_checkable_item_selection(false); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/deploy_with_remote_debug", TTR("Deploy with Remote Debug")), RUN_DEPLOY_REMOTE_DEBUG); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, using one-click deploy will make the executable attempt to connect to this computer's IP so the running project can be debugged.\nThis option is intended to be used for remote debugging (typically with a mobile device).\nYou don't need to enable it to use the GDScript debugger locally.")); - p->add_check_shortcut(ED_SHORTCUT("editor/small_deploy_with_network_fs", TTR("Small Deploy with Network Filesystem")), RUN_FILE_SERVER); - p->set_item_tooltip(-1, + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/small_deploy_with_network_fs", TTR("Small Deploy with Network Filesystem")), RUN_FILE_SERVER); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, using one-click deploy for Android will only export an executable without the project data.\nThe filesystem will be provided from the project by the editor over the network.\nOn Android, deploying will use the USB cable for faster performance. This option speeds up testing for projects with large assets.")); - p->add_separator(); - p->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTR("Visible Collision Shapes")), RUN_DEBUG_COLLISONS); - p->set_item_tooltip(-1, + debug_menu->add_separator(); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTR("Visible Collision Shapes")), RUN_DEBUG_COLLISONS); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, collision shapes and raycast nodes (for 2D and 3D) will be visible in the running project.")); - p->add_check_shortcut(ED_SHORTCUT("editor/visible_paths", TTR("Visible Paths")), RUN_DEBUG_PATHS); - p->set_item_tooltip(-1, + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_paths", TTR("Visible Paths")), RUN_DEBUG_PATHS); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, curve resources used by path nodes will be visible in the running project.")); - p->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION); - p->set_item_tooltip(-1, + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, navigation meshes and polygons will be visible in the running project.")); - p->add_separator(); - p->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG); - p->set_item_tooltip(-1, + debug_menu->add_separator(); + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, any changes made to the scene in the editor will be replicated in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled.")); - p->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Synchronize Script Changes")), RUN_RELOAD_SCRIPTS); - p->set_item_tooltip(-1, + debug_menu->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Synchronize Script Changes")), RUN_RELOAD_SCRIPTS); + debug_menu->set_item_tooltip(-1, TTR("When this option is enabled, any script that is saved will be reloaded in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled.")); // Multi-instance, start/stop @@ -92,9 +91,9 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) { instances_menu->set_name("run_instances"); instances_menu->set_hide_on_checkable_item_selection(false); - p->add_child(instances_menu); - p->add_separator(); - p->add_submenu_item(TTR("Run Multiple Instances"), "run_instances"); + debug_menu->add_child(instances_menu); + debug_menu->add_separator(); + debug_menu->add_submenu_item(TTR("Run Multiple Instances"), "run_instances"); instances_menu->add_radio_check_item(TTR("Run 1 Instance")); instances_menu->set_item_metadata(0, 1); @@ -106,7 +105,7 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) { instances_menu->set_item_metadata(3, 4); instances_menu->set_item_checked(0, true); instances_menu->connect("index_pressed", callable_mp(this, &DebuggerEditorPlugin::_select_run_count)); - p->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option)); + debug_menu->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option)); } DebuggerEditorPlugin::~DebuggerEditorPlugin() { @@ -125,7 +124,7 @@ void DebuggerEditorPlugin::_select_run_count(int p_index) { void DebuggerEditorPlugin::_menu_option(int p_option) { switch (p_option) { case RUN_FILE_SERVER: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_FILE_SERVER)); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_FILE_SERVER)); if (ischecked) { file_server->stop(); @@ -133,45 +132,45 @@ void DebuggerEditorPlugin::_menu_option(int p_option) { file_server->start(); } - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_FILE_SERVER), !ischecked); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_FILE_SERVER), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_file_server", !ischecked); } break; case RUN_LIVE_DEBUG: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG)); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_LIVE_DEBUG)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_LIVE_DEBUG), !ischecked); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_LIVE_DEBUG), !ischecked); EditorDebuggerNode::get_singleton()->set_live_debugging(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_live_debug", !ischecked); } break; case RUN_DEPLOY_REMOTE_DEBUG: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEPLOY_REMOTE_DEBUG)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEPLOY_REMOTE_DEBUG), !ischecked); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEPLOY_REMOTE_DEBUG)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEPLOY_REMOTE_DEBUG), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_deploy_remote_debug", !ischecked); } break; case RUN_DEBUG_COLLISONS: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_COLLISONS)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_COLLISONS), !ischecked); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_COLLISONS)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_COLLISONS), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_collisons", !ischecked); } break; case RUN_DEBUG_PATHS: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_PATHS)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_PATHS), !ischecked); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_PATHS)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_PATHS), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_paths", !ischecked); } break; case RUN_DEBUG_NAVIGATION: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_NAVIGATION)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_NAVIGATION), !ischecked); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_DEBUG_NAVIGATION)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_DEBUG_NAVIGATION), !ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_navigation", !ischecked); } break; case RUN_RELOAD_SCRIPTS: { - bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_RELOAD_SCRIPTS)); - debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->get_item_index(RUN_RELOAD_SCRIPTS), !ischecked); + bool ischecked = debug_menu->is_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS)); + debug_menu->set_item_checked(debug_menu->get_item_index(RUN_RELOAD_SCRIPTS), !ischecked); ScriptEditor::get_singleton()->set_live_auto_reload_running_scripts(!ischecked); EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_reload_scripts", !ischecked); diff --git a/editor/plugins/debugger_editor_plugin.h b/editor/plugins/debugger_editor_plugin.h index fb963385cd..d8871128c3 100644 --- a/editor/plugins/debugger_editor_plugin.h +++ b/editor/plugins/debugger_editor_plugin.h @@ -41,7 +41,7 @@ class DebuggerEditorPlugin : public EditorPlugin { GDCLASS(DebuggerEditorPlugin, EditorPlugin); private: - MenuButton *debug_menu = nullptr; + PopupMenu *debug_menu = nullptr; EditorFileServer *file_server = nullptr; PopupMenu *instances_menu = nullptr; @@ -64,7 +64,7 @@ public: virtual String get_name() const override { return "Debugger"; } bool has_main_screen() const override { return false; } - DebuggerEditorPlugin(MenuButton *p_menu); + DebuggerEditorPlugin(PopupMenu *p_menu); ~DebuggerEditorPlugin(); }; diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp index cadb974345..c7d3e92802 100644 --- a/editor/plugins/font_config_plugin.cpp +++ b/editor/plugins/font_config_plugin.cpp @@ -100,11 +100,6 @@ bool EditorPropertyFontOTObject::_get(const StringName &p_name, Variant &r_ret) return false; } -void EditorPropertyFontOTObject::_bind_methods() { - ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &EditorPropertyFontOTObject::property_can_revert); - ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &EditorPropertyFontOTObject::property_get_revert); -} - void EditorPropertyFontOTObject::set_dict(const Dictionary &p_dict) { dict = p_dict; } @@ -121,7 +116,7 @@ Dictionary EditorPropertyFontOTObject::get_defaults() { return defaults_dict; } -bool EditorPropertyFontOTObject::property_can_revert(const String &p_name) { +bool EditorPropertyFontOTObject::_property_can_revert(const StringName &p_name) const { String name = p_name; if (name.begins_with("keys")) { @@ -136,18 +131,19 @@ bool EditorPropertyFontOTObject::property_can_revert(const String &p_name) { return false; } -Variant EditorPropertyFontOTObject::property_get_revert(const String &p_name) { +bool EditorPropertyFontOTObject::_property_get_revert(const StringName &p_name, Variant &r_property) const { String name = p_name; if (name.begins_with("keys")) { int key = name.get_slicec('/', 1).to_int(); if (defaults_dict.has(key)) { Vector3i range = defaults_dict[key]; - return range.z; + r_property = range.z; + return true; } } - return Variant(); + return false; } /*************************************************************************/ diff --git a/editor/plugins/font_config_plugin.h b/editor/plugins/font_config_plugin.h index 3eaa2fdc17..41dde3cc59 100644 --- a/editor/plugins/font_config_plugin.h +++ b/editor/plugins/font_config_plugin.h @@ -66,7 +66,8 @@ class EditorPropertyFontOTObject : public RefCounted { protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; - static void _bind_methods(); + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; public: void set_dict(const Dictionary &p_dict); @@ -75,9 +76,6 @@ public: void set_defaults(const Dictionary &p_dict); Dictionary get_defaults(); - bool property_can_revert(const String &p_name); - Variant property_get_revert(const String &p_name); - EditorPropertyFontOTObject(){}; }; diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 4a144bc391..cf24095582 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -6063,7 +6063,7 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par return editor; } -void EditorPropertyShaderMode::_option_selected(int p_which) { +void EditorPropertyVisualShaderMode::_option_selected(int p_which) { Ref<VisualShader> visual_shader(Object::cast_to<VisualShader>(get_edited_object())); if (visual_shader->get_mode() == p_which) { return; @@ -6149,39 +6149,39 @@ void EditorPropertyShaderMode::_option_selected(int p_which) { undo_redo->commit_action(); } -void EditorPropertyShaderMode::update_property() { +void EditorPropertyVisualShaderMode::update_property() { int which = get_edited_object()->get(get_edited_property()); options->select(which); } -void EditorPropertyShaderMode::setup(const Vector<String> &p_options) { +void EditorPropertyVisualShaderMode::setup(const Vector<String> &p_options) { for (int i = 0; i < p_options.size(); i++) { options->add_item(p_options[i], i); } } -void EditorPropertyShaderMode::set_option_button_clip(bool p_enable) { +void EditorPropertyVisualShaderMode::set_option_button_clip(bool p_enable) { options->set_clip_text(p_enable); } -void EditorPropertyShaderMode::_bind_methods() { +void EditorPropertyVisualShaderMode::_bind_methods() { } -EditorPropertyShaderMode::EditorPropertyShaderMode() { +EditorPropertyVisualShaderMode::EditorPropertyVisualShaderMode() { options = memnew(OptionButton); options->set_clip_text(true); add_child(options); add_focusable(options); - options->connect("item_selected", callable_mp(this, &EditorPropertyShaderMode::_option_selected)); + options->connect("item_selected", callable_mp(this, &EditorPropertyVisualShaderMode::_option_selected)); } -bool EditorInspectorShaderModePlugin::can_handle(Object *p_object) { +bool EditorInspectorVisualShaderModePlugin::can_handle(Object *p_object) { return true; // Can handle everything. } -bool EditorInspectorShaderModePlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { +bool EditorInspectorVisualShaderModePlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { if (p_path == "mode" && p_object->is_class("VisualShader") && p_type == Variant::INT) { - EditorPropertyShaderMode *mode_editor = memnew(EditorPropertyShaderMode); + EditorPropertyVisualShaderMode *mode_editor = memnew(EditorPropertyVisualShaderMode); Vector<String> options = p_hint_text.split(","); mode_editor->setup(options); add_property_editor(p_path, mode_editor); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index b846c34f9e..b6a3b43754 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -529,8 +529,8 @@ public: virtual Control *create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) override; }; -class EditorPropertyShaderMode : public EditorProperty { - GDCLASS(EditorPropertyShaderMode, EditorProperty); +class EditorPropertyVisualShaderMode : public EditorProperty { + GDCLASS(EditorPropertyVisualShaderMode, EditorProperty); OptionButton *options = nullptr; void _option_selected(int p_which); @@ -542,11 +542,11 @@ public: void setup(const Vector<String> &p_options); virtual void update_property() override; void set_option_button_clip(bool p_enable); - EditorPropertyShaderMode(); + EditorPropertyVisualShaderMode(); }; -class EditorInspectorShaderModePlugin : public EditorInspectorPlugin { - GDCLASS(EditorInspectorShaderModePlugin, EditorInspectorPlugin); +class EditorInspectorVisualShaderModePlugin : public EditorInspectorPlugin { + GDCLASS(EditorInspectorVisualShaderModePlugin, EditorInspectorPlugin); public: virtual bool can_handle(Object *p_object) override; diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 5ce837f862..924b735012 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -1381,7 +1381,6 @@ static const char *class_renames[][2] = { { "Spatial", "Node3D" }, { "SpatialGizmo", "Node3DGizmo" }, { "SpatialMaterial", "StandardMaterial3D" }, - { "SpatialVelocityTracker", "VelocityTracker3D" }, { "SphereShape", "SphereShape3D" }, { "SpotLight", "SpotLight3D" }, { "SpringArm", "SpringArm3D" }, diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 21891e381b..9d2dcd129c 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1164,6 +1164,12 @@ void ProjectList::load_project_icon(int p_index) { icon = default_icon; } + // The default project icon is 128×128 to look crisp on hiDPI displays, + // but we want the actual displayed size to be 64×64 on loDPI displays. + item.control->icon->set_ignore_texture_size(true); + item.control->icon->set_custom_minimum_size(Size2(64, 64) * EDSCALE); + item.control->icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + item.control->icon->set_texture(icon); item.control->icon_needs_reload = false; } diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index d19a40599f..f0884a4a91 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -510,9 +510,8 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { } if (selected) { - create_dialog->popup_create(false, true, selected->get_class()); + create_dialog->popup_create(false, true, selected->get_class(), selected->get_name()); } - } break; case TOOL_EXTEND_SCRIPT: { attach_script_to_selected(true); @@ -2678,7 +2677,7 @@ void SceneTreeDock::_add_children_to_popup(Object *p_obj, int p_depth) { } int index = menu_subresources->get_item_count(); menu_subresources->add_icon_item(icon, E.name.capitalize(), EDIT_SUBRESOURCE_BASE + subresources.size()); - menu_subresources->set_item_horizontal_offset(index, p_depth * 10 * EDSCALE); + menu_subresources->set_item_indent(index, p_depth); subresources.push_back(obj->get_instance_id()); _add_children_to_popup(obj, p_depth + 1); diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp index bd1f2529ca..8c4a231e8a 100644 --- a/editor/shader_create_dialog.cpp +++ b/editor/shader_create_dialog.cpp @@ -37,6 +37,13 @@ #include "scene/resources/visual_shader.h" #include "servers/rendering/shader_types.h" +enum ShaderType { + SHADER_TYPE_TEXT, + SHADER_TYPE_VISUAL, + SHADER_TYPE_INC, + SHADER_TYPE_MAX, +}; + void ShaderCreateDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { diff --git a/editor/shader_create_dialog.h b/editor/shader_create_dialog.h index bf031c3601..9ba655369b 100644 --- a/editor/shader_create_dialog.h +++ b/editor/shader_create_dialog.h @@ -44,13 +44,6 @@ class EditorFileDialog; class ShaderCreateDialog : public ConfirmationDialog { GDCLASS(ShaderCreateDialog, ConfirmationDialog); - enum ShaderType { - SHADER_TYPE_TEXT, - SHADER_TYPE_VISUAL, - SHADER_TYPE_INC, - SHADER_TYPE_MAX, - }; - struct ShaderTypeData { List<String> extensions; String default_extension; diff --git a/main/main.cpp b/main/main.cpp index 965fcc66c6..740e3cc69d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -161,7 +161,7 @@ static DisplayServer::WindowMode window_mode = DisplayServer::WINDOW_MODE_WINDOW static DisplayServer::ScreenOrientation window_orientation = DisplayServer::SCREEN_LANDSCAPE; static DisplayServer::VSyncMode window_vsync_mode = DisplayServer::VSYNC_ENABLED; static uint32_t window_flags = 0; -static Size2i window_size = Size2i(1024, 600); +static Size2i window_size = Size2i(1152, 648); static int init_screen = -1; static bool init_fullscreen = false; @@ -1997,10 +1997,16 @@ Error Main::setup2(Thread::ID p_main_tid_override) { GLOBAL_DEF_RST("internationalization/rendering/text_driver", ""); String text_driver_options; for (int i = 0; i < TextServerManager::get_singleton()->get_interface_count(); i++) { - if (i > 0) { + const String driver_name = TextServerManager::get_singleton()->get_interface(i)->get_name(); + if (driver_name == "Dummy") { + // Dummy text driver cannot draw any text, making the editor unusable if selected. + continue; + } + if (!text_driver_options.is_empty() && text_driver_options.find(",") == -1) { + // Not the first option; add a comma before it as a separator for the property hint. text_driver_options += ","; } - text_driver_options += TextServerManager::get_singleton()->get_interface(i)->get_name(); + text_driver_options += driver_name; } ProjectSettings::get_singleton()->set_custom_property_info("internationalization/rendering/text_driver", PropertyInfo(Variant::STRING, "internationalization/rendering/text_driver", PROPERTY_HINT_ENUM, text_driver_options)); @@ -2249,18 +2255,6 @@ bool Main::start() { ERR_FAIL_COND_V_MSG(da.is_null(), false, "Argument supplied to --doctool must be a valid directory path."); } -#ifndef MODULE_MONO_ENABLED - // Hack to define Mono-specific project settings even on non-Mono builds, - // so that we don't lose their descriptions and default values in DocData. - // Default values should be synced with mono_gd/gd_mono.cpp. - GLOBAL_DEF("mono/debugger_agent/port", 23685); - GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false); - GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000); - GLOBAL_DEF("mono/profiler/args", "log:calls,alloc,sample,output=output.mlpd"); - GLOBAL_DEF("mono/profiler/enabled", false); - GLOBAL_DEF("mono/runtime/unhandled_exception_policy", 0); -#endif - Error err; DocTools doc; doc.generate(doc_base); diff --git a/methods.py b/methods.py index ba7474ea02..c02517d137 100644 --- a/methods.py +++ b/methods.py @@ -818,18 +818,21 @@ def generate_vs_project(env, num_jobs): module_configs = ModuleConfigs() if env.get("module_mono_enabled"): - import modules.mono.build_scripts.mono_reg_utils as mono_reg + import modules.mono.build_scripts.mono_configure as mono_configure - mono_root = env.get("mono_prefix") or mono_reg.find_mono_root_dir(env["bits"]) - if mono_root: + app_host_dir = mono_configure.find_dotnet_app_host_dir(env) + if app_host_dir and os.path.isdir(app_host_dir): + mono_defines = [("NETHOST_USE_AS_STATIC",)] + if env["tools"]: + mono_defines += [("GD_MONO_HOT_RELOAD",)] module_configs.add_mode( "mono", - includes=os.path.join(mono_root, "include", "mono-2.0"), - cli_args="module_mono_enabled=yes mono_glue=yes", - defines=[("MONO_GLUE_ENABLED",)], + includes=app_host_dir, + cli_args="module_mono_enabled=yes", + defines=mono_defines, ) else: - print("Mono installation directory not found. Generated project will not have build variants for Mono.") + print(".NET App Host directory not found. Generated project will not have build variants for .NET.") env["MSVSBUILDCOM"] = module_configs.build_commandline("scons") env["MSVSREBUILDCOM"] = module_configs.build_commandline("scons vsproj=yes") diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index 4fbf8c6936..a44be04b2d 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -8,6 +8,7 @@ For the list of the global functions and constants see [@GlobalScope]. </description> <tutorials> + <link title="GDScript exports">$DOCS_URL/tutorials/scripting/gdscript/gdscript_exports.html</link> </tutorials> <methods> <method name="Color8"> @@ -267,87 +268,178 @@ <annotation name="@export"> <return type="void" /> <description> + Mark the following property as exported (editable in the Inspector dock and saved to disk). To control the type of the exported property use the type hint notation. + [codeblock] + @export var int_number = 5 + @export var float_number: float = 5 + [/codeblock] </description> </annotation> <annotation name="@export_category"> <return type="void" /> <param index="0" name="name" type="String" /> <description> + Define a new category for the following exported properties. This helps to organize properties in the Inspector dock. + See also [constant PROPERTY_USAGE_CATEGORY]. + [codeblock] + @export_category("My Properties") + @export var number = 3 + @export var string = "" + [/codeblock] + [b]Note:[/b] Categories in the property list are supposed to indicate different base types, so the use of this annotation is not encouraged. See [annotation @export_group] and [annotation @export_subgroup] instead. </description> </annotation> <annotation name="@export_color_no_alpha"> <return type="void" /> <description> + Export a [Color] property without an alpha (fixed as [code]1.0[/code]). + See also [constant PROPERTY_HINT_COLOR_NO_ALPHA]. + [codeblock] + @export_color_no_alpha var modulate_color: Color + [/codeblock] </description> </annotation> <annotation name="@export_dir"> <return type="void" /> <description> + Export a [String] property as a path to a directory. The path will be limited to the project folder and its subfolders. See [annotation @export_global_dir] to allow picking from the entire filesystem. + See also [constant PROPERTY_HINT_DIR]. + [codeblock] + @export_dir var sprite_folder: String + [/codeblock] </description> </annotation> <annotation name="@export_enum" qualifiers="vararg"> <return type="void" /> <param index="0" name="names" type="String" /> <description> + Export a [String] or integer property as an enumerated list of options. If the property is an integer field, then the index of the value is stored, in the same order the values are provided. You can add specific identifiers for allowed values using a colon. + See also [constant PROPERTY_HINT_ENUM]. + [codeblock] + @export_enum("Rebecca", "Mary", "Leah") var character_name: String + @export_enum("Warrior", "Magician", "Thief") var character_class: int + @export_enum("Walking:30", "Running:60", "Riding:200") var character_speed: int + [/codeblock] </description> </annotation> <annotation name="@export_exp_easing" qualifiers="vararg"> <return type="void" /> <param index="0" name="hints" type="String" default="""" /> <description> + Export a floating-point property with an easing editor widget. Additional hints can be provided to adjust the behavior of the widget. [code]"attenuation"[/code] flips the curve, which makes it more intuitive for editing attenuation properties. [code]"positive_only"[/code] limits values to only be greater than or equal to zero. + See also [constant PROPERTY_HINT_EXP_EASING]. + [codeblock] + @export_exp_easing var transition_speed + @export_exp_easing("attenuation") var fading_attenuation + @export_exp_easing("positive_only") var effect_power + [/codeblock] </description> </annotation> <annotation name="@export_file" qualifiers="vararg"> <return type="void" /> <param index="0" name="filter" type="String" default="""" /> <description> + Export a [String] property as a path to a file. The path will be limited to the project folder and its subfolders. See [annotation @export_global_file] to allow picking from the entire filesystem. + If [param filter] is provided, only matching files will be available for picking. + See also [constant PROPERTY_HINT_FILE]. + [codeblock] + @export_file var sound_effect_file: String + @export_file("*.txt") var notes_file: String + [/codeblock] </description> </annotation> <annotation name="@export_flags" qualifiers="vararg"> <return type="void" /> <param index="0" name="names" type="String" /> <description> + Export an integer property as a bit flag field. This allows to store several "checked" or [code]true[/code] values with one property, and comfortably select them from the Inspector dock. + See also [constant PROPERTY_HINT_FLAGS]. + [codeblock] + @export_flags("Fire", "Water", "Earth", "Wind") var spell_elements = 0 + [/codeblock] </description> </annotation> <annotation name="@export_flags_2d_navigation"> <return type="void" /> <description> + Export an integer property as a bit flag field for 2D navigation layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/2d_navigation/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_2D_NAVIGATION]. + [codeblock] + @export_flags_2d_navigation var navigation_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_2d_physics"> <return type="void" /> <description> + Export an integer property as a bit flag field for 2D physics layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/2d_physics/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_2D_PHYSICS]. + [codeblock] + @export_flags_2d_physics var physics_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_2d_render"> <return type="void" /> <description> + Export an integer property as a bit flag field for 2D render layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/2d_render/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_2D_RENDER]. + [codeblock] + @export_flags_2d_render var render_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_3d_navigation"> <return type="void" /> <description> + Export an integer property as a bit flag field for 3D navigation layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/3d_navigation/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_3D_NAVIGATION]. + [codeblock] + @export_flags_3d_navigation var navigation_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_3d_physics"> <return type="void" /> <description> + Export an integer property as a bit flag field for 3D physics layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/3d_physics/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_3D_PHYSICS]. + [codeblock] + @export_flags_3d_physics var physics_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_flags_3d_render"> <return type="void" /> <description> + Export an integer property as a bit flag field for 3D render layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/3d_render/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_3D_RENDER]. + [codeblock] + @export_flags_3d_render var render_layers: int + [/codeblock] </description> </annotation> <annotation name="@export_global_dir"> <return type="void" /> <description> + Export a [String] property as a path to a directory. The path can be picked from the entire filesystem. See [annotation @export_dir] to limit it to the project folder and its subfolders. + See also [constant PROPERTY_HINT_GLOBAL_DIR]. + [codeblock] + @export_global_dir var sprite_folder: String + [/codeblock] </description> </annotation> <annotation name="@export_global_file" qualifiers="vararg"> <return type="void" /> <param index="0" name="filter" type="String" default="""" /> <description> + Export a [String] property as a path to a file. The path can be picked from the entire filesystem. See [annotation @export_file] to limit it to the project folder and its subfolders. + If [param filter] is provided, only matching files will be available for picking. + See also [constant PROPERTY_HINT_GLOBAL_FILE]. + [codeblock] + @export_global_file var sound_effect_file: String + @export_global_file("*.txt") var notes_file: String + [/codeblock] </description> </annotation> <annotation name="@export_group"> @@ -355,22 +447,54 @@ <param index="0" name="name" type="String" /> <param index="1" name="prefix" type="String" default="""" /> <description> + Define a new group for the following exported properties. This helps to organize properties in the Inspector dock. Groups can be added with an optional [param prefix], which would make group to only consider properties that have this prefix. The grouping will break on the first property that doesn't have a prefix. The prefix is also removed from the property's name in the Inspector dock. + If no [param prefix] is provided, the every following property is added to the group. The group ends when then next group or category is defined. You can also force end a group by using this annotation with empty strings for paramters, [code]@export_group("", "")[/code]. + Groups cannot be nested, use [annotation @export_subgroup] to add subgroups to your groups. + See also [constant PROPERTY_USAGE_GROUP]. + [codeblock] + @export_group("My Properties") + @export var number = 3 + @export var string = "" + + @export_group("Prefixed Properties", "prefix_") + @export var prefix_number = 3 + @export var prefix_string = "" + + @export_group("", "") + @export var ungrouped_number = 3 + [/codeblock] </description> </annotation> <annotation name="@export_multiline"> <return type="void" /> <description> + Export a [String] property with a large [TextEdit] widget instead of a [LineEdit]. This adds support for multiline content and makes it easier to edit large amount of text stored in the property. + See also [constant PROPERTY_HINT_MULTILINE_TEXT]. + [codeblock] + @export_multiline var character_bio + [/codeblock] </description> </annotation> <annotation name="@export_node_path" qualifiers="vararg"> <return type="void" /> <param index="0" name="type" type="String" default="""" /> <description> + Export a [NodePath] property with a filter for allowed node types. + See also [constant PROPERTY_HINT_NODE_PATH_VALID_TYPES]. + [codeblock] + @export_node_path(Button, TouchScreenButton) var some_button + [/codeblock] </description> </annotation> <annotation name="@export_placeholder"> <return type="void" /> + <param index="0" name="placeholder" type="String" /> <description> + Export a [String] property with a placeholder text displayed in the editor widget when no value is present. + See also [constant PROPERTY_HINT_PLACEHOLDER_TEXT]. + [codeblock] + @export_placeholder("Name in lowercase") var character_id: String + [/codeblock] </description> </annotation> <annotation name="@export_range" qualifiers="vararg"> @@ -380,6 +504,22 @@ <param index="2" name="step" type="float" default="1.0" /> <param index="3" name="extra_hints" type="String" default="""" /> <description> + Export a numeric property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting. + If hints [code]"or_greater"[/code] and [code]"or_lesser"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"no_slider"[/code] hint will hide the slider element of the editor widget. + Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string. + See also [constant PROPERTY_HINT_RANGE]. + [codeblock] + @export_range(0, 20) var number + @export_range(-10, 20) var number + @export_range(-10, 20, 0.2) var number: float + + @export_range(0, 100, 1, "or_greater") var power_percent + @export_range(0, 100, 1, "or_greater", "or_lesser") var health_delta + + @export_range(-3.14, 3.14, 0.001, "radians") var angle_radians + @export_range(0, 360, 1, "degrees") var angle_degrees + @export_range(-8, 8, 2, "suffix:px") var target_offset + [/codeblock] </description> </annotation> <annotation name="@export_subgroup"> @@ -387,17 +527,38 @@ <param index="0" name="name" type="String" /> <param index="1" name="prefix" type="String" default="""" /> <description> + Define a new subgroup for the following exported properties. This helps to organize properties in the Inspector dock. Subgroups work exactly like groups, except they need a parent group to exist. See [annotation @export_group]. + See also [constant PROPERTY_USAGE_SUBGROUP]. + [codeblock] + @export_group("My Properties") + @export var number = 3 + @export var string = "" + + @export_subgroup("My Prefixed Properties", "prefix_") + @export var prefix_number = 3 + @export var prefix_string = "" + [/codeblock] + [b]Note:[/b] Subgroups cannot be nested, they only provide one extra level of depth. Just like the next group ends the previous group, so do the subsequent subgroups. </description> </annotation> <annotation name="@icon"> <return type="void" /> <param index="0" name="icon_path" type="String" /> <description> + Add a custom icon to the current script. The icon is displayed in the Scene dock for every node that the script is attached to. For named classes the icon is also displayed in various editor dialogs. + [codeblock] + @icon("res://path/to/class/icon.svg") + [/codeblock] + [b]Note:[/b] Only the script can have a custom icon. Inner classes are not supported yet. </description> </annotation> <annotation name="@onready"> <return type="void" /> <description> + Mark the following property as assigned on [Node]'s ready state change. Values for these properties are no assigned immediately upon the node's creation, and instead are computed and stored right before [method Node._ready]. + [codeblock] + @onready var character_name: Label = $Label + [/codeblock] </description> </annotation> <annotation name="@rpc" qualifiers="vararg"> @@ -407,17 +568,34 @@ <param index="2" name="transfer_mode" type="String" default="""" /> <param index="3" name="transfer_channel" type="int" default="0" /> <description> + Mark the following method for remote procedure calls. See [url=$DOCS_URL/tutorials/networking/high_level_multiplayer.html]High-level multiplayer[/url]. + [codeblock] + @rpc() + [/codeblock] </description> </annotation> <annotation name="@tool"> <return type="void" /> <description> + Mark the current script as a tool script, allowing it to be loaded and executed by the editor. See [url=$DOCS_URL/tutorials/plugins/running_code_in_the_editor.html]Running code in the editor[/url]. + [codeblock] + @tool + extends Node + [/codeblock] </description> </annotation> <annotation name="@warning_ignore" qualifiers="vararg"> <return type="void" /> <param index="0" name="warning" type="String" /> <description> + Mark the following statement to ignore the specified warning. See [url=$DOCS_URL/tutorials/scripting/gdscript/warning_system.html]GDScript warning system[/url]. + [codeblock] + func test(): + print("hello") + return + @warning_ignore("unreachable_code") + print("unreachable") + [/codeblock] </description> </annotation> </annotations> diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index a23f19de85..e7299b4d3a 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -46,6 +46,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l bool prev_is_char = false; bool prev_is_number = false; + bool prev_is_binary_op = false; bool in_keyword = false; bool in_word = false; bool in_function_name = false; @@ -84,16 +85,17 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l const int line_length = str.length(); Color prev_color; - if (in_region != -1 && str.length() == 0) { + if (in_region != -1 && line_length == 0) { color_region_cache[p_line] = in_region; } - for (int j = 0; j < str.length(); j++) { + for (int j = 0; j < line_length; j++) { Dictionary highlighter_info; color = font_color; bool is_char = !is_symbol(str[j]); bool is_a_symbol = is_symbol(str[j]); bool is_number = is_digit(str[j]); + bool is_binary_op = false; /* color regions */ if (is_a_symbol || in_region != -1) { @@ -244,7 +246,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l is_hex_notation = false; } - // disallow anything not a 0 or 1 + // disallow anything not a 0 or 1 in binary notation if (is_bin_notation && (is_binary_digit(str[j]))) { is_number = true; } else if (is_bin_notation) { @@ -285,7 +287,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (!in_keyword && is_char && !prev_is_char) { int to = j; - while (to < str.length() && !is_symbol(str[to])) { + while (to < line_length && !is_symbol(str[to])) { to++; } @@ -318,12 +320,12 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_signal_declaration = true; } else { int k = j; - while (k < str.length() && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + while (k < line_length && !is_symbol(str[k]) && !is_whitespace(str[k])) { k++; } // check for space between name and bracket - while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { + while (k < line_length && is_whitespace(str[k])) { k++; } @@ -336,7 +338,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l // Check for lambda. if (in_function_name && previous_text == GDScriptTokenizer::get_token_name(GDScriptTokenizer::Token::FUNC)) { k = j - 1; - while (k > 0 && (str[k] == '\t' || str[k] == ' ')) { + while (k > 0 && is_whitespace(str[k])) { k--; } @@ -349,7 +351,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (!in_function_name && !in_member_variable && !in_keyword && !is_number && in_word) { int k = j; - while (k > 0 && !is_symbol(str[k]) && str[k] != '\t' && str[k] != ' ') { + while (k > 0 && !is_symbol(str[k]) && !is_whitespace(str[k])) { k--; } @@ -378,7 +380,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l if (in_variable_declaration || in_function_args) { int k = j; // Skip space - while (k < str.length() && (str[k] == '\t' || str[k] == ' ')) { + while (k < line_length && is_whitespace(str[k])) { k++; } @@ -395,13 +397,41 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_member_variable = false; } - if (!in_node_path && in_region == -1 && (str[j] == '^')) { + if (j > 0 && (str[j] == '&' || str[j] == '^' || str[j] == '%' || str[j] == '+' || str[j] == '-' || str[j] == '~')) { + int k = j - 1; + while (k > 0 && is_whitespace(str[k])) { + k--; + } + if (!is_symbol(str[k]) || str[k] == '"' || str[k] == '\'' || str[k] == ')' || str[k] == ']' || str[k] == '}') { + is_binary_op = true; + } + } + + // Highlight '+' and '-' like numbers when unary + if ((str[j] == '+' || str[j] == '-' || str[j] == '~') && !is_binary_op) { + is_number = true; + is_a_symbol = false; + } + + // Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand + if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) { + if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) { + is_binary_op = true; + } else if (j == 0 || (j > 0 && str[j - 1] != '&') || prev_is_binary_op) { + in_string_name = true; + } + } else if (in_region != -1 || is_a_symbol) { + in_string_name = false; + } + + // '^^' has no special meaning, so unlike StringName, when binary, use NodePath color for the last caret + if (!in_node_path && in_region == -1 && str[j] == '^' && !is_binary_op && (j == 0 || (j > 0 && str[j - 1] != '^') || prev_is_binary_op)) { in_node_path = true; - } else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%')) { + } else if (in_region != -1 || is_a_symbol) { in_node_path = false; } - if (!in_node_ref && in_region == -1 && (str[j] == '$' || str[j] == '%')) { + if (!in_node_ref && in_region == -1 && (str[j] == '$' || (str[j] == '%' && !is_binary_op))) { in_node_ref = true; } else if (in_region != -1 || (is_a_symbol && str[j] != '/' && str[j] != '%')) { in_node_ref = false; @@ -413,16 +443,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_annotation = false; } - if (!in_string_name && in_region == -1 && str[j] == '&') { - in_string_name = true; - } else if (in_region != -1 || is_a_symbol) { - in_string_name = false; - } - - if (in_node_path) { - next_type = NODE_PATH; - color = node_path_color; - } else if (in_node_ref) { + if (in_node_ref) { next_type = NODE_REF; color = node_ref_color; } else if (in_annotation) { @@ -431,6 +452,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l } else if (in_string_name) { next_type = STRING_NAME; color = string_name_color; + } else if (in_node_path) { + next_type = NODE_PATH; + color = node_path_color; } else if (in_keyword) { next_type = KEYWORD; color = keyword_color; @@ -487,6 +511,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l prev_is_char = is_char; prev_is_number = is_number; + prev_is_binary_op = is_binary_op; if (color != prev_color) { prev_color = color; diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 8a5e93eeff..cf2d6ae9f8 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1538,6 +1538,47 @@ void GDScriptInstance::get_property_list(List<PropertyInfo> *p_properties) const } } +bool GDScriptInstance::property_can_revert(const StringName &p_name) const { + Variant name = p_name; + const Variant *args[1] = { &name }; + + const GDScript *sptr = script.ptr(); + while (sptr) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_can_revert); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK && ret.get_type() == Variant::BOOL && ret.operator bool()) { + return true; + } + } + sptr = sptr->_base; + } + + return false; +} + +bool GDScriptInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const { + Variant name = p_name; + const Variant *args[1] = { &name }; + + const GDScript *sptr = script.ptr(); + while (sptr) { + HashMap<StringName, GDScriptFunction *>::ConstIterator E = sptr->member_functions.find(GDScriptLanguage::get_singleton()->strings._property_get_revert); + if (E) { + Callable::CallError err; + Variant ret = E->value->call(const_cast<GDScriptInstance *>(this), args, 1, err); + if (err.error == Callable::CallError::CALL_OK && ret.get_type() != Variant::NIL) { + r_ret = ret; + return true; + } + } + sptr = sptr->_base; + } + + return false; +} + void GDScriptInstance::get_method_list(List<MethodInfo> *p_list) const { const GDScript *sptr = script.ptr(); while (sptr) { @@ -2248,6 +2289,8 @@ GDScriptLanguage::GDScriptLanguage() { strings._set = StaticCString::create("_set"); strings._get = StaticCString::create("_get"); strings._get_property_list = StaticCString::create("_get_property_list"); + strings._property_can_revert = StaticCString::create("_property_can_revert"); + strings._property_get_revert = StaticCString::create("_property_get_revert"); strings._script_source = StaticCString::create("script/source"); _debug_parse_err_line = -1; _debug_parse_err_file = ""; diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 5123cccddd..e4b12d4ddb 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -287,6 +287,9 @@ public: virtual void get_property_list(List<PropertyInfo> *p_properties) const; virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const; + virtual bool property_can_revert(const StringName &p_name) const; + virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const; + virtual void get_method_list(List<MethodInfo> *p_list) const; virtual bool has_method(const StringName &p_method) const; virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); @@ -423,6 +426,8 @@ public: StringName _set; StringName _get; StringName _get_property_list; + StringName _property_can_revert; + StringName _property_get_revert; StringName _script_source; } strings; diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 6f5397e1a3..6b6ad427a7 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -127,7 +127,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true); register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>); register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); - register_annotation(MethodInfo("@export_placeholder"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); + register_annotation(MethodInfo("@export_placeholder", PropertyInfo(Variant::STRING, "placeholder")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "extra_hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, varray(1.0, ""), true); register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hints")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, varray(""), true); register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index d102970932..6a6de40fbb 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5442,7 +5442,7 @@ void GLTFDocument::_convert_grid_map_to_gltf(GridMap *p_grid_map, GLTFNodeIndex int32_t cell = p_grid_map->get_cell_item( Vector3(cell_location.x, cell_location.y, cell_location.z)); Transform3D cell_xform; - cell_xform.basis.set_orthogonal_index( + cell_xform.basis = p_grid_map->get_basis_with_orthogonal_index( p_grid_map->get_cell_item_orientation( Vector3(cell_location.x, cell_location.y, cell_location.z))); cell_xform.basis.scale(Vector3(p_grid_map->get_cell_scale(), diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 5552b5b009..209890a333 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -39,6 +39,13 @@ Returns an array of [ArrayMesh]es and [Transform3D] references of all bake meshes that exist within the current GridMap. </description> </method> + <method name="get_basis_with_orthogonal_index" qualifiers="const"> + <return type="Basis" /> + <param index="0" name="index" type="int" /> + <description> + Returns one of 24 possible rotations that lie along the vectors (x,y,z) with each component being either -1, 0, or 1. For further details, refer to the Godot source code. + </description> + </method> <method name="get_cell_item" qualifiers="const"> <return type="int" /> <param index="0" name="position" type="Vector3i" /> @@ -46,6 +53,13 @@ The [MeshLibrary] item index located at the given grid coordinates. If the cell is empty, [constant INVALID_CELL_ITEM] will be returned. </description> </method> + <method name="get_cell_item_basis" qualifiers="const"> + <return type="Basis" /> + <param index="0" name="position" type="Vector3i" /> + <description> + Returns the basis that gives the specificed cell its orientation. + </description> + </method> <method name="get_cell_item_orientation" qualifiers="const"> <return type="int" /> <param index="0" name="position" type="Vector3i" /> @@ -80,6 +94,13 @@ Returns whether or not the specified layer of the [member navigation_layers] bitmask is enabled, given a [code]layer_number[/code] between 1 and 32. </description> </method> + <method name="get_orthogonal_index_from_basis" qualifiers="const"> + <return type="int" /> + <param index="0" name="basis" type="Basis" /> + <description> + This function considers a discretization of rotations into 24 points on unit sphere, lying along the vectors (x,y,z) with each component being either -1, 0, or 1, and returns the index (in the range from 0 to 23) of the point best representing the orientation of the object. For further details, refer to the Godot source code. + </description> + </method> <method name="get_used_cells" qualifiers="const"> <return type="Array" /> <description> @@ -121,7 +142,7 @@ <description> Sets the mesh index for the cell referenced by its grid coordinates. A negative item index such as [constant INVALID_CELL_ITEM] will clear the cell. - Optionally, the item's orientation can be passed. For valid orientation values, see [method Basis.get_orthogonal_index]. + Optionally, the item's orientation can be passed. For valid orientation values, see [method get_orthogonal_index_from_basis]. </description> </method> <method name="set_collision_layer_value"> diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index 518e2cf97d..7471bae093 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -94,91 +94,91 @@ void GridMapEditor::_menu_option(int p_option) { case MENU_OPTION_CURSOR_ROTATE_Y: { Basis r; if (input_action == INPUT_PASTE) { - r.set_orthogonal_index(paste_indicator.orientation); + r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); r.rotate(Vector3(0, 1, 0), -Math_PI / 2.0); - paste_indicator.orientation = r.get_orthogonal_index(); + paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } - r.set_orthogonal_index(cursor_rot); + r = node->get_basis_with_orthogonal_index(cursor_rot); r.rotate(Vector3(0, 1, 0), -Math_PI / 2.0); - cursor_rot = r.get_orthogonal_index(); + cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_ROTATE_X: { Basis r; if (input_action == INPUT_PASTE) { - r.set_orthogonal_index(paste_indicator.orientation); + r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); r.rotate(Vector3(1, 0, 0), -Math_PI / 2.0); - paste_indicator.orientation = r.get_orthogonal_index(); + paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } - r.set_orthogonal_index(cursor_rot); + r = node->get_basis_with_orthogonal_index(cursor_rot); r.rotate(Vector3(1, 0, 0), -Math_PI / 2.0); - cursor_rot = r.get_orthogonal_index(); + cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_ROTATE_Z: { Basis r; if (input_action == INPUT_PASTE) { - r.set_orthogonal_index(paste_indicator.orientation); + r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); r.rotate(Vector3(0, 0, 1), -Math_PI / 2.0); - paste_indicator.orientation = r.get_orthogonal_index(); + paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } - r.set_orthogonal_index(cursor_rot); + r = node->get_basis_with_orthogonal_index(cursor_rot); r.rotate(Vector3(0, 0, 1), -Math_PI / 2.0); - cursor_rot = r.get_orthogonal_index(); + cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_BACK_ROTATE_Y: { Basis r; if (input_action == INPUT_PASTE) { - r.set_orthogonal_index(paste_indicator.orientation); + r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); r.rotate(Vector3(0, 1, 0), Math_PI / 2.0); - paste_indicator.orientation = r.get_orthogonal_index(); + paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } - r.set_orthogonal_index(cursor_rot); + r = node->get_basis_with_orthogonal_index(cursor_rot); r.rotate(Vector3(0, 1, 0), Math_PI / 2.0); - cursor_rot = r.get_orthogonal_index(); + cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_BACK_ROTATE_X: { Basis r; if (input_action == INPUT_PASTE) { - r.set_orthogonal_index(paste_indicator.orientation); + r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); r.rotate(Vector3(1, 0, 0), Math_PI / 2.0); - paste_indicator.orientation = r.get_orthogonal_index(); + paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } - r.set_orthogonal_index(cursor_rot); + r = node->get_basis_with_orthogonal_index(cursor_rot); r.rotate(Vector3(1, 0, 0), Math_PI / 2.0); - cursor_rot = r.get_orthogonal_index(); + cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_BACK_ROTATE_Z: { Basis r; if (input_action == INPUT_PASTE) { - r.set_orthogonal_index(paste_indicator.orientation); + r = node->get_basis_with_orthogonal_index(paste_indicator.orientation); r.rotate(Vector3(0, 0, 1), Math_PI / 2.0); - paste_indicator.orientation = r.get_orthogonal_index(); + paste_indicator.orientation = node->get_orthogonal_index_from_basis(r); _update_paste_indicator(); break; } - r.set_orthogonal_index(cursor_rot); + r = node->get_basis_with_orthogonal_index(cursor_rot); r.rotate(Vector3(0, 0, 1), Math_PI / 2.0); - cursor_rot = r.get_orthogonal_index(); + cursor_rot = node->get_orthogonal_index_from_basis(r); _update_cursor_transform(); } break; case MENU_OPTION_CURSOR_CLEAR_ROTATION: { @@ -242,7 +242,7 @@ void GridMapEditor::_menu_option(int p_option) { void GridMapEditor::_update_cursor_transform() { cursor_transform = Transform3D(); cursor_transform.origin = cursor_origin; - cursor_transform.basis.set_orthogonal_index(cursor_rot); + cursor_transform.basis = node->get_basis_with_orthogonal_index(cursor_rot); cursor_transform.basis *= node->get_cell_scale(); cursor_transform = node->get_global_transform() * cursor_transform; @@ -543,7 +543,7 @@ void GridMapEditor::_update_paste_indicator() { xf.scale(scale); xf.origin = (paste_indicator.begin + (paste_indicator.current - paste_indicator.click) + center) * node->get_cell_size(); Basis rot; - rot.set_orthogonal_index(paste_indicator.orientation); + rot = node->get_basis_with_orthogonal_index(paste_indicator.orientation); xf.basis = rot * xf.basis; xf.translate_local((-center * node->get_cell_size()) / scale); @@ -556,7 +556,7 @@ void GridMapEditor::_update_paste_indicator() { xf.translate_local(item.grid_offset * node->get_cell_size()); Basis item_rot; - item_rot.set_orthogonal_index(item.orientation); + item_rot = node->get_basis_with_orthogonal_index(item.orientation); xf.basis = item_rot * xf.basis * node->get_cell_scale(); RenderingServer::get_singleton()->instance_set_transform(item.instance, node->get_global_transform() * xf); @@ -568,7 +568,7 @@ void GridMapEditor::_do_paste() { bool reselect = options->get_popup()->is_item_checked(idx); Basis rot; - rot.set_orthogonal_index(paste_indicator.orientation); + rot = node->get_basis_with_orthogonal_index(paste_indicator.orientation); Vector3 ofs = paste_indicator.current - paste_indicator.click; undo_redo->create_action(TTR("GridMap Paste Selection")); @@ -577,10 +577,10 @@ void GridMapEditor::_do_paste() { Vector3 position = rot.xform(item.grid_offset) + paste_indicator.begin + ofs; Basis orm; - orm.set_orthogonal_index(item.orientation); + orm = node->get_basis_with_orthogonal_index(item.orientation); orm = rot * orm; - undo_redo->add_do_method(node, "set_cell_item", position, item.cell_item, orm.get_orthogonal_index()); + undo_redo->add_do_method(node, "set_cell_item", position, item.cell_item, node->get_orthogonal_index_from_basis(orm)); undo_redo->add_undo_method(node, "set_cell_item", position, node->get_cell_item(position), node->get_cell_item_orientation(position)); } diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 7d80cbef7c..6384446bce 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -428,6 +428,75 @@ int GridMap::get_cell_item_orientation(const Vector3i &p_position) const { return cell_map[key].rot; } +static const Basis _ortho_bases[24] = { + Basis(1, 0, 0, 0, 1, 0, 0, 0, 1), + Basis(0, -1, 0, 1, 0, 0, 0, 0, 1), + Basis(-1, 0, 0, 0, -1, 0, 0, 0, 1), + Basis(0, 1, 0, -1, 0, 0, 0, 0, 1), + Basis(1, 0, 0, 0, 0, -1, 0, 1, 0), + Basis(0, 0, 1, 1, 0, 0, 0, 1, 0), + Basis(-1, 0, 0, 0, 0, 1, 0, 1, 0), + Basis(0, 0, -1, -1, 0, 0, 0, 1, 0), + Basis(1, 0, 0, 0, -1, 0, 0, 0, -1), + Basis(0, 1, 0, 1, 0, 0, 0, 0, -1), + Basis(-1, 0, 0, 0, 1, 0, 0, 0, -1), + Basis(0, -1, 0, -1, 0, 0, 0, 0, -1), + Basis(1, 0, 0, 0, 0, 1, 0, -1, 0), + Basis(0, 0, -1, 1, 0, 0, 0, -1, 0), + Basis(-1, 0, 0, 0, 0, -1, 0, -1, 0), + Basis(0, 0, 1, -1, 0, 0, 0, -1, 0), + Basis(0, 0, 1, 0, 1, 0, -1, 0, 0), + Basis(0, -1, 0, 0, 0, 1, -1, 0, 0), + Basis(0, 0, -1, 0, -1, 0, -1, 0, 0), + Basis(0, 1, 0, 0, 0, -1, -1, 0, 0), + Basis(0, 0, 1, 0, -1, 0, 1, 0, 0), + Basis(0, 1, 0, 0, 0, 1, 1, 0, 0), + Basis(0, 0, -1, 0, 1, 0, 1, 0, 0), + Basis(0, -1, 0, 0, 0, -1, 1, 0, 0) +}; + +Basis GridMap::get_cell_item_basis(const Vector3i &p_position) const { + int orientation = get_cell_item_orientation(p_position); + + if (orientation == -1) { + return Basis(); + } + + return get_basis_with_orthogonal_index(orientation); +} + +Basis GridMap::get_basis_with_orthogonal_index(int p_index) const { + ERR_FAIL_INDEX_V(p_index, 24, Basis()); + + return _ortho_bases[p_index]; +} + +int GridMap::get_orthogonal_index_from_basis(const Basis &p_basis) const { + Basis orth = p_basis; + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + real_t v = orth[i][j]; + if (v > 0.5) { + v = 1.0; + } else if (v < -0.5) { + v = -1.0; + } else { + v = 0; + } + + orth[i][j] = v; + } + } + + for (int i = 0; i < 24; i++) { + if (_ortho_bases[i] == orth) { + return i; + } + } + + return 0; +} + Vector3i GridMap::world_to_map(const Vector3 &p_world_position) const { Vector3 map_position = (p_world_position / cell_size).floor(); return Vector3i(map_position); @@ -529,7 +598,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) { Transform3D xform; - xform.basis.set_orthogonal_index(c.rot); + xform.basis = _ortho_bases[c.rot]; xform.set_origin(cellpos * cell_size + ofs); xform.basis.scale(Vector3(cell_scale, cell_scale, cell_scale)); if (baked_meshes.size() == 0) { @@ -921,6 +990,9 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell_item", "position", "item", "orientation"), &GridMap::set_cell_item, DEFVAL(0)); ClassDB::bind_method(D_METHOD("get_cell_item", "position"), &GridMap::get_cell_item); ClassDB::bind_method(D_METHOD("get_cell_item_orientation", "position"), &GridMap::get_cell_item_orientation); + ClassDB::bind_method(D_METHOD("get_cell_item_basis", "position"), &GridMap::get_cell_item_basis); + ClassDB::bind_method(D_METHOD("get_basis_with_orthogonal_index", "index"), &GridMap::get_basis_with_orthogonal_index); + ClassDB::bind_method(D_METHOD("get_orthogonal_index_from_basis", "basis"), &GridMap::get_orthogonal_index_from_basis); ClassDB::bind_method(D_METHOD("world_to_map", "world_position"), &GridMap::world_to_map); ClassDB::bind_method(D_METHOD("map_to_world", "map_position"), &GridMap::map_to_world); @@ -1025,7 +1097,7 @@ Array GridMap::get_meshes() const { Transform3D xform; - xform.basis.set_orthogonal_index(E.value.rot); + xform.basis = _ortho_bases[E.value.rot]; xform.set_origin(cellpos * cell_size + ofs); xform.basis.scale(Vector3(cell_scale, cell_scale, cell_scale)); @@ -1079,7 +1151,7 @@ void GridMap::make_baked_meshes(bool p_gen_lightmap_uv, float p_lightmap_uv_texe Transform3D xform; - xform.basis.set_orthogonal_index(E.value.rot); + xform.basis = _ortho_bases[E.value.rot]; xform.set_origin(cellpos * cell_size + ofs); xform.basis.scale(Vector3(cell_scale, cell_scale, cell_scale)); diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index 078a1d9de5..00cebd35e9 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -263,6 +263,9 @@ public: void set_cell_item(const Vector3i &p_position, int p_item, int p_rot = 0); int get_cell_item(const Vector3i &p_position) const; int get_cell_item_orientation(const Vector3i &p_position) const; + Basis get_cell_item_basis(const Vector3i &p_position) const; + Basis get_basis_with_orthogonal_index(int p_index) const; + int get_orthogonal_index_from_basis(const Basis &p_basis) const; Vector3i world_to_map(const Vector3 &p_world_position) const; Vector3 map_to_world(const Vector3i &p_map_position) const; diff --git a/modules/mono/.editorconfig b/modules/mono/.editorconfig index c9dcd7724e..9434d0693c 100644 --- a/modules/mono/.editorconfig +++ b/modules/mono/.editorconfig @@ -12,3 +12,32 @@ insert_final_newline = true trim_trailing_whitespace = true max_line_length = 120 csharp_indent_case_contents_when_block = false + +[*.cs] +# CA1707: Identifiers should not contain underscores +# TODO: +# Maybe we could disable this selectively only +# where it's not desired and for generated code. +dotnet_diagnostic.CA1707.severity = none +# CA1711: Identifiers should not have incorrect suffix +# Disable warning for suffixes like EventHandler, Flags, Enum, etc. +dotnet_diagnostic.CA1711.severity = none +# CA1716: Identifiers should not match keywords +# TODO: We should look into this. +dotnet_diagnostic.CA1716.severity = warning +# CA1720: Identifiers should not contain type names +dotnet_diagnostic.CA1720.severity = none +# CA1805: Do not initialize unnecessarily +# Don't tell me what to do. +dotnet_diagnostic.CA1805.severity = none +# CA1304: Specify CultureInfo +# TODO: We should look into this. +dotnet_diagnostic.CA1304.severity = warning +# CA1305: Specify IFormatProvider +# TODO: We should look into this. Disabled for now because it's annoying. +dotnet_diagnostic.CA1305.severity = none +# CA1310: Specify StringComparison for correctness +# TODO: We should look into this. Disabled for now because it's annoying. +dotnet_diagnostic.CA1310.severity = none +# Diagnostics to prevent defensive copies of `in` struct parameters +resharper_possibly_impure_method_call_on_readonly_variable_highlighting = error diff --git a/modules/mono/Directory.Build.props b/modules/mono/Directory.Build.props index fbf864b11b..f7c8a825f9 100644 --- a/modules/mono/Directory.Build.props +++ b/modules/mono/Directory.Build.props @@ -1,3 +1,6 @@ <Project> - <Import Project="$(MSBuildThisFileDirectory)\SdkPackageVersions.props" /> + <PropertyGroup> + <GodotSdkPackageVersionsFilePath>$(MSBuildThisFileDirectory)\SdkPackageVersions.props</GodotSdkPackageVersionsFilePath> + </PropertyGroup> + <Import Project="$(GodotSdkPackageVersionsFilePath)" /> </Project> diff --git a/modules/mono/Directory.Build.targets b/modules/mono/Directory.Build.targets new file mode 100644 index 0000000000..98410b93ae --- /dev/null +++ b/modules/mono/Directory.Build.targets @@ -0,0 +1,22 @@ +<Project> + <PropertyGroup> + <_HasNuGetPackage Condition=" '$(_HasNuGetPackage)' == '' And '$(PackageId)' != '' And '$(GeneratePackageOnBuild.ToLower())' == 'true' ">true</_HasNuGetPackage> + <_HasNuGetPackage Condition=" '$(_HasNuGetPackage)' == '' ">false</_HasNuGetPackage> + </PropertyGroup> + <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack" + Condition=" '$(_HasNuGetPackage)' == 'true' "> + <PropertyGroup> + <GodotSourceRootPath>$(MSBuildThisFileDirectory)\..\..\</GodotSourceRootPath> + <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> + </PropertyGroup> + <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> + </Target> + <Target Name="PushNuGetPackagesToLocalSource" BeforeTargets="Pack" + Condition=" '$(_HasNuGetPackage)' == 'true' And '$(PushNuGetToLocalSource)' != '' "> + <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(PushNuGetToLocalSource)\" /> + </Target> + <Target Name="ClearNuGetLocalPackageCache" BeforeTargets="Pack" + Condition=" '$(_HasNuGetPackage)' == 'true' And '$(ClearNuGetLocalCache.ToLower())' == 'true' "> + <RemoveDir Directories="$(NugetPackageRoot)/$(PackageId.ToLower())/$(PackageVersion)"/> + </Target> +</Project> diff --git a/modules/mono/README.md b/modules/mono/README.md new file mode 100644 index 0000000000..ebbc6b0f80 --- /dev/null +++ b/modules/mono/README.md @@ -0,0 +1,45 @@ +# How to build and run + +1. Build Godot with the module enabled: `module_mono_enabled=yes`. +2. After building Godot, use it to generate the C# glue code: + ```sh + <godot_binary> --generate-mono-glue ./modules/mono/glue + ``` +3. Build the C# solutions: + ```sh + ./modules/mono/build_scripts/build_assemblies.py --godot-output-dir ./bin + ``` + +The paths specified in these examples assume the command is being run from +the Godot source root. + +# How to deal with NuGet packages + +We distribute the API assemblies, our source generators, and our custom +MSBuild project SDK as NuGet packages. This is all transparent to the user, +but it can make things complicated during development. + +In order to use Godot with a development of those packages, we must create +a local NuGet source where MSBuild can find them. This can be done with +the .NET CLI: + +```sh +dotnet nuget add source ~/MyLocalNugetSource --name MyLocalNugetSource +``` + +The Godot NuGet packages must be added to that local source. Additionally, +we must make sure there are no other versions of the package in the NuGet +cache, as MSBuild may pick one of those instead. + +In order to simplify this process, the `build_assemblies.py` script provides +the following `--push-nupkgs-local` option: + +```sh +./modules/mono/build_scripts/build_assemblies.py --godot-output-dir ./bin \ + --push-nupkgs-local ~/MyLocalNugetSource +``` + +This option ensures the packages will be added to the specified local NuGet +source and that conflicting versions of the package are removed from the +NuGet cache. It's recommended to always use this option when building the +C# solutions during development to avoid mistakes. diff --git a/modules/mono/SCsub b/modules/mono/SCsub index d10ebc7b47..7764ba0b45 100644 --- a/modules/mono/SCsub +++ b/modules/mono/SCsub @@ -7,49 +7,14 @@ Import("env_modules") env_mono = env_modules.Clone() -if env_mono["tools"]: - # NOTE: It is safe to generate this file here, since this is still executed serially - import build_scripts.gen_cs_glue_version as gen_cs_glue_version - - gen_cs_glue_version.generate_header("glue/GodotSharp", "glue/cs_glue_version.gen.h") - -# Glue sources -if env_mono["mono_glue"]: - env_mono.Append(CPPDEFINES=["MONO_GLUE_ENABLED"]) - - import os.path - - if not os.path.isfile("glue/mono_glue.gen.cpp"): - raise RuntimeError("Mono glue sources not found. Did you forget to run '--generate-mono-glue'?") - -if env_mono["tools"] or env_mono["target"] != "release": - env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"]) - # Configure Mono mono_configure.configure(env, env_mono) -if env_mono["tools"] and env_mono["mono_glue"] and env_mono["build_cil"]: - # Build Godot API solution - import build_scripts.api_solution_build as api_solution_build - - api_sln_cmd = api_solution_build.build(env_mono) - - # Build GodotTools - import build_scripts.godot_tools_build as godot_tools_build - - godot_tools_build.build(env_mono, api_sln_cmd) - - # Build Godot.NET.Sdk - import build_scripts.godot_net_sdk_build as godot_net_sdk_build - - godot_net_sdk_build.build(env_mono) - # Add sources env_mono.add_source_files(env.modules_sources, "*.cpp") env_mono.add_source_files(env.modules_sources, "glue/*.cpp") -env_mono.add_source_files(env.modules_sources, "glue/mono_glue.gen.cpp") env_mono.add_source_files(env.modules_sources, "mono_gd/*.cpp") env_mono.add_source_files(env.modules_sources, "utils/*.cpp") diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props index bdec051625..65094aa34f 100644 --- a/modules/mono/SdkPackageVersions.props +++ b/modules/mono/SdkPackageVersions.props @@ -1,7 +1,8 @@ <Project> <PropertyGroup> <PackageFloatingVersion_Godot>4.0.*-*</PackageFloatingVersion_Godot> - <PackageVersion_Godot_NET_Sdk>4.0.0-dev6</PackageVersion_Godot_NET_Sdk> - <PackageVersion_Godot_SourceGenerators>4.0.0-dev3</PackageVersion_Godot_SourceGenerators> + <PackageVersion_GodotSharp>4.0.0-dev</PackageVersion_GodotSharp> + <PackageVersion_Godot_NET_Sdk>4.0.0-dev8</PackageVersion_Godot_NET_Sdk> + <PackageVersion_Godot_SourceGenerators>4.0.0-dev8</PackageVersion_Godot_SourceGenerators> </PropertyGroup> </Project> diff --git a/modules/mono/build_scripts/api_solution_build.py b/modules/mono/build_scripts/api_solution_build.py deleted file mode 100644 index 9abac22df6..0000000000 --- a/modules/mono/build_scripts/api_solution_build.py +++ /dev/null @@ -1,80 +0,0 @@ -# Build the Godot API solution - -import os - -from SCons.Script import Dir - - -def build_api_solution(source, target, env): - # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str - - module_dir = env["module_dir"] - - solution_path = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") - - build_config = env["solution_build_config"] - - extra_msbuild_args = ["/p:NoWarn=1591"] # Ignore missing documentation warnings - - from .solution_builder import build_solution - - build_solution(env, solution_path, build_config, extra_msbuild_args=extra_msbuild_args) - - # Copy targets - - core_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharp", "bin", build_config)) - editor_src_dir = os.path.abspath(os.path.join(solution_path, os.pardir, "GodotSharpEditor", "bin", build_config)) - - dst_dir = os.path.abspath(os.path.join(str(target[0]), os.pardir)) - - if not os.path.isdir(dst_dir): - assert not os.path.isfile(dst_dir) - os.makedirs(dst_dir) - - def copy_target(target_path): - from shutil import copy - - filename = os.path.basename(target_path) - - src_path = os.path.join(core_src_dir, filename) - if not os.path.isfile(src_path): - src_path = os.path.join(editor_src_dir, filename) - - copy(src_path, target_path) - - for scons_target in target: - copy_target(str(scons_target)) - - -def build(env_mono): - assert env_mono["tools"] - - target_filenames = [ - "GodotSharp.dll", - "GodotSharp.pdb", - "GodotSharp.xml", - "GodotSharpEditor.dll", - "GodotSharpEditor.pdb", - "GodotSharpEditor.xml", - ] - - depend_cmd = [] - - for build_config in ["Debug", "Release"]: - output_dir = Dir("#bin").abspath - editor_api_dir = os.path.join(output_dir, "GodotSharp", "Api", build_config) - - targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames] - - cmd = env_mono.CommandNoCache( - targets, depend_cmd, build_api_solution, module_dir=os.getcwd(), solution_build_config=build_config - ) - env_mono.AlwaysBuild(cmd) - - # Make the Release build of the API solution depend on the Debug build. - # We do this in order to prevent SCons from building them in parallel, - # which can freak out MSBuild. In many cases, one of the builds would - # hang indefinitely requiring a key to be pressed for it to continue. - depend_cmd = cmd - - return depend_cmd diff --git a/modules/mono/build_scripts/build_assemblies.py b/modules/mono/build_scripts/build_assemblies.py new file mode 100755 index 0000000000..fa3be684bd --- /dev/null +++ b/modules/mono/build_scripts/build_assemblies.py @@ -0,0 +1,329 @@ +#!/usr/bin/python3 + +import os +import os.path +import shlex +import subprocess +from dataclasses import dataclass + + +def find_dotnet_cli(): + if os.name == "nt": + for hint_dir in os.environ["PATH"].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, "dotnet") + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): + return hint_path + ".exe" + else: + for hint_dir in os.environ["PATH"].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, "dotnet") + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + + +def find_msbuild_standalone_windows(): + msbuild_tools_path = find_msbuild_tools_path_reg() + + if msbuild_tools_path: + return os.path.join(msbuild_tools_path, "MSBuild.exe") + + return None + + +def find_msbuild_mono_windows(mono_prefix): + assert mono_prefix is not None + + mono_bin_dir = os.path.join(mono_prefix, "bin") + msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat") + + if os.path.isfile(msbuild_mono): + return msbuild_mono + + return None + + +def find_msbuild_mono_unix(): + import sys + + hint_dirs = [] + if sys.platform == "darwin": + hint_dirs[:0] = [ + "/Library/Frameworks/Mono.framework/Versions/Current/bin", + "/usr/local/var/homebrew/linked/mono/bin", + ] + + for hint_dir in hint_dirs: + hint_path = os.path.join(hint_dir, "msbuild") + if os.path.isfile(hint_path): + return hint_path + elif os.path.isfile(hint_path + ".exe"): + return hint_path + ".exe" + + for hint_dir in os.environ["PATH"].split(os.pathsep): + hint_dir = hint_dir.strip('"') + hint_path = os.path.join(hint_dir, "msbuild") + if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): + return hint_path + if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): + return hint_path + ".exe" + + return None + + +def find_msbuild_tools_path_reg(): + import subprocess + + program_files = os.getenv("PROGRAMFILES(X86)") + if not program_files: + program_files = os.getenv("PROGRAMFILES") + vswhere = os.path.join(program_files, "Microsoft Visual Studio", "Installer", "vswhere.exe") + + vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"] + + try: + lines = subprocess.check_output([vswhere] + vswhere_args).splitlines() + + for line in lines: + parts = line.decode("utf-8").split(":", 1) + + if len(parts) < 2 or parts[0] != "installationPath": + continue + + val = parts[1].strip() + + if not val: + raise ValueError("Value of `installationPath` entry is empty") + + # Since VS2019, the directory is simply named "Current" + msbuild_dir = os.path.join(val, "MSBuild", "Current", "Bin") + if os.path.isdir(msbuild_dir): + return msbuild_dir + + # Directory name "15.0" is used in VS 2017 + return os.path.join(val, "MSBuild", "15.0", "Bin") + + raise ValueError("Cannot find `installationPath` entry") + except ValueError as e: + print("Error reading output from vswhere: " + str(e)) + except OSError: + pass # Fine, vswhere not found + except (subprocess.CalledProcessError, OSError): + pass + + +@dataclass +class ToolsLocation: + dotnet_cli: str = "" + msbuild_standalone: str = "" + msbuild_mono: str = "" + mono_bin_dir: str = "" + + +def find_any_msbuild_tool(mono_prefix): + # Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild + + # Find dotnet CLI + dotnet_cli = find_dotnet_cli() + if dotnet_cli: + return ToolsLocation(dotnet_cli=dotnet_cli) + + # Find standalone MSBuild + if os.name == "nt": + msbuild_standalone = find_msbuild_standalone_windows() + if msbuild_standalone: + return ToolsLocation(msbuild_standalone=msbuild_standalone) + + if mono_prefix: + # Find Mono's MSBuild + if os.name == "nt": + msbuild_mono = find_msbuild_mono_windows(mono_prefix) + if msbuild_mono: + return ToolsLocation(msbuild_mono=msbuild_mono) + else: + msbuild_mono = find_msbuild_mono_unix() + if msbuild_mono: + return ToolsLocation(msbuild_mono=msbuild_mono) + + return None + + +def run_msbuild(tools: ToolsLocation, sln: str, msbuild_args: [str] = None): + if msbuild_args is None: + msbuild_args = [] + + using_msbuild_mono = False + + # Preference order: dotnet CLI > Standalone MSBuild > Mono's MSBuild + if tools.dotnet_cli: + args = [tools.dotnet_cli, "msbuild"] + elif tools.msbuild_standalone: + args = [tools.msbuild_standalone] + elif tools.msbuild_mono: + args = [tools.msbuild_mono] + using_msbuild_mono = True + else: + raise RuntimeError("Path to MSBuild or dotnet CLI not provided.") + + args += [sln] + + if len(msbuild_args) > 0: + args += msbuild_args + + print("Running MSBuild: ", " ".join(shlex.quote(arg) for arg in args), flush=True) + + msbuild_env = os.environ.copy() + + # Needed when running from Developer Command Prompt for VS + if "PLATFORM" in msbuild_env: + del msbuild_env["PLATFORM"] + + if using_msbuild_mono: + # The (Csc/Vbc/Fsc)ToolExe environment variables are required when + # building with Mono's MSBuild. They must point to the batch files + # in Mono's bin directory to make sure they are executed with Mono. + msbuild_env.update( + { + "CscToolExe": os.path.join(tools.mono_bin_dir, "csc.bat"), + "VbcToolExe": os.path.join(tools.mono_bin_dir, "vbc.bat"), + "FscToolExe": os.path.join(tools.mono_bin_dir, "fsharpc.bat"), + } + ) + + return subprocess.call(args, env=msbuild_env) + + +def build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local): + target_filenames = [ + "GodotSharp.dll", + "GodotSharp.pdb", + "GodotSharp.xml", + "GodotSharpEditor.dll", + "GodotSharpEditor.pdb", + "GodotSharpEditor.xml", + "GodotPlugins.dll", + "GodotPlugins.pdb", + "GodotPlugins.runtimeconfig.json", + ] + + for build_config in ["Debug", "Release"]: + editor_api_dir = os.path.join(output_dir, "GodotSharp", "Api", build_config) + + targets = [os.path.join(editor_api_dir, filename) for filename in target_filenames] + + args = ["/restore", "/t:Build", "/p:Configuration=" + build_config, "/p:NoWarn=1591"] + if push_nupkgs_local: + args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] + + sln = os.path.join(module_dir, "glue/GodotSharp/GodotSharp.sln") + exit_code = run_msbuild( + msbuild_tool, + sln=sln, + msbuild_args=args, + ) + if exit_code != 0: + return exit_code + + # Copy targets + + core_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharp", "bin", build_config)) + editor_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotSharpEditor", "bin", build_config)) + plugins_src_dir = os.path.abspath(os.path.join(sln, os.pardir, "GodotPlugins", "bin", build_config, "net6.0")) + + if not os.path.isdir(editor_api_dir): + assert not os.path.isfile(editor_api_dir) + os.makedirs(editor_api_dir) + + def copy_target(target_path): + from shutil import copy + + filename = os.path.basename(target_path) + + src_path = os.path.join(core_src_dir, filename) + if not os.path.isfile(src_path): + src_path = os.path.join(editor_src_dir, filename) + if not os.path.isfile(src_path): + src_path = os.path.join(plugins_src_dir, filename) + + print(f"Copying assembly to {target_path}...") + copy(src_path, target_path) + + for scons_target in targets: + copy_target(scons_target) + + return 0 + + +def build_all(msbuild_tool, module_dir, output_dir, godot_platform, dev_debug, push_nupkgs_local): + # Godot API + exit_code = build_godot_api(msbuild_tool, module_dir, output_dir, push_nupkgs_local) + if exit_code != 0: + return exit_code + + # GodotTools + sln = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln") + args = ["/restore", "/t:Build", "/p:Configuration=" + ("Debug" if dev_debug else "Release")] + ( + ["/p:GodotPlatform=" + godot_platform] if godot_platform else [] + ) + if push_nupkgs_local: + args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] + exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args) + if exit_code != 0: + return exit_code + + # Godot.NET.Sdk + args = ["/restore", "/t:Build", "/p:Configuration=Release"] + if push_nupkgs_local: + args += ["/p:ClearNuGetLocalCache=true", "/p:PushNuGetToLocalSource=" + push_nupkgs_local] + sln = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln") + exit_code = run_msbuild(msbuild_tool, sln=sln, msbuild_args=args) + if exit_code != 0: + return exit_code + + return 0 + + +def main(): + import argparse + import sys + + parser = argparse.ArgumentParser(description="Builds all Godot .NET solutions") + parser.add_argument("--godot-output-dir", type=str, required=True) + parser.add_argument( + "--dev-debug", + action="store_true", + default=False, + help="Build GodotTools and Godot.NET.Sdk with 'Configuration=Debug'", + ) + parser.add_argument("--godot-platform", type=str, default="") + parser.add_argument("--mono-prefix", type=str, default="") + parser.add_argument("--push-nupkgs-local", type=str, default="") + + args = parser.parse_args() + + this_script_dir = os.path.dirname(os.path.realpath(__file__)) + module_dir = os.path.abspath(os.path.join(this_script_dir, os.pardir)) + + output_dir = os.path.abspath(args.godot_output_dir) + + msbuild_tool = find_any_msbuild_tool(args.mono_prefix) + + if msbuild_tool is None: + print("Unable to find MSBuild") + sys.exit(1) + + exit_code = build_all( + msbuild_tool, + module_dir, + output_dir, + args.godot_platform, + args.dev_debug, + args.push_nupkgs_local, + ) + sys.exit(exit_code) + + +if __name__ == "__main__": + main() diff --git a/modules/mono/build_scripts/gen_cs_glue_version.py b/modules/mono/build_scripts/gen_cs_glue_version.py deleted file mode 100644 index 98bbb4d9be..0000000000 --- a/modules/mono/build_scripts/gen_cs_glue_version.py +++ /dev/null @@ -1,20 +0,0 @@ -def generate_header(solution_dir, version_header_dst): - import os - - latest_mtime = 0 - for root, dirs, files in os.walk(solution_dir, topdown=True): - dirs[:] = [d for d in dirs if d not in ["Generated"]] # Ignored generated files - files = [f for f in files if f.endswith(".cs")] - for file in files: - filepath = os.path.join(root, file) - mtime = os.path.getmtime(filepath) - latest_mtime = mtime if mtime > latest_mtime else latest_mtime - - glue_version = int(latest_mtime) # The latest modified time will do for now - - with open(version_header_dst, "w") as version_header: - version_header.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") - version_header.write("#ifndef CS_GLUE_VERSION_H\n") - version_header.write("#define CS_GLUE_VERSION_H\n\n") - version_header.write("#define CS_GLUE_VERSION UINT32_C(" + str(glue_version) + ")\n") - version_header.write("\n#endif // CS_GLUE_VERSION_H\n") diff --git a/modules/mono/build_scripts/godot_net_sdk_build.py b/modules/mono/build_scripts/godot_net_sdk_build.py deleted file mode 100644 index 8c5a60d2db..0000000000 --- a/modules/mono/build_scripts/godot_net_sdk_build.py +++ /dev/null @@ -1,55 +0,0 @@ -# Build Godot.NET.Sdk solution - -import os - -from SCons.Script import Dir - - -def build_godot_net_sdk(source, target, env): - # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str - - module_dir = env["module_dir"] - - solution_path = os.path.join(module_dir, "editor/Godot.NET.Sdk/Godot.NET.Sdk.sln") - build_config = "Release" - - from .solution_builder import build_solution - - extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]] - - build_solution(env, solution_path, build_config, extra_msbuild_args) - # No need to copy targets. The Godot.NET.Sdk csproj takes care of copying them. - - -def get_nupkgs_versions(props_file): - import xml.etree.ElementTree as ET - - tree = ET.parse(props_file) - root = tree.getroot() - - return { - "Godot.NET.Sdk": root.find("./PropertyGroup/PackageVersion_Godot_NET_Sdk").text.strip(), - "Godot.SourceGenerators": root.find("./PropertyGroup/PackageVersion_Godot_SourceGenerators").text.strip(), - } - - -def build(env_mono): - assert env_mono["tools"] - - output_dir = Dir("#bin").abspath - editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools") - nupkgs_dir = os.path.join(editor_tools_dir, "nupkgs") - - module_dir = os.getcwd() - - nupkgs_versions = get_nupkgs_versions(os.path.join(module_dir, "SdkPackageVersions.props")) - - target_filenames = [ - "Godot.NET.Sdk.%s.nupkg" % nupkgs_versions["Godot.NET.Sdk"], - "Godot.SourceGenerators.%s.nupkg" % nupkgs_versions["Godot.SourceGenerators"], - ] - - targets = [os.path.join(nupkgs_dir, filename) for filename in target_filenames] - - cmd = env_mono.CommandNoCache(targets, [], build_godot_net_sdk, module_dir=module_dir) - env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/build_scripts/godot_tools_build.py b/modules/mono/build_scripts/godot_tools_build.py deleted file mode 100644 index 3bbbf29d3b..0000000000 --- a/modules/mono/build_scripts/godot_tools_build.py +++ /dev/null @@ -1,38 +0,0 @@ -# Build GodotTools solution - -import os - -from SCons.Script import Dir - - -def build_godot_tools(source, target, env): - # source and target elements are of type SCons.Node.FS.File, hence why we convert them to str - - module_dir = env["module_dir"] - - solution_path = os.path.join(module_dir, "editor/GodotTools/GodotTools.sln") - build_config = "Debug" if env["target"] == "debug" else "Release" - - from .solution_builder import build_solution - - extra_msbuild_args = ["/p:GodotPlatform=" + env["platform"]] - - build_solution(env, solution_path, build_config, extra_msbuild_args) - # No need to copy targets. The GodotTools csproj takes care of copying them. - - -def build(env_mono, api_sln_cmd): - assert env_mono["tools"] - - output_dir = Dir("#bin").abspath - editor_tools_dir = os.path.join(output_dir, "GodotSharp", "Tools") - - target_filenames = ["GodotTools.dll"] - - if env_mono["target"] == "debug": - target_filenames += ["GodotTools.pdb"] - - targets = [os.path.join(editor_tools_dir, filename) for filename in target_filenames] - - cmd = env_mono.CommandNoCache(targets, api_sln_cmd, build_godot_tools, module_dir=os.getcwd()) - env_mono.AlwaysBuild(cmd) diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py deleted file mode 100644 index 3459244bc2..0000000000 --- a/modules/mono/build_scripts/make_android_mono_config.py +++ /dev/null @@ -1,55 +0,0 @@ -def generate_compressed_config(config_src, output_dir): - import os.path - - # Source file - with open(os.path.join(output_dir, "android_mono_config.gen.cpp"), "w") as cpp: - with open(config_src, "rb") as f: - buf = f.read() - decompr_size = len(buf) - import zlib - - # Use maximum zlib compression level to further reduce file size - # (at the cost of initial build times). - buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION) - compr_size = len(buf) - - bytes_seq_str = "" - for i, buf_idx in enumerate(range(compr_size)): - if i > 0: - bytes_seq_str += ", " - bytes_seq_str += str(buf[buf_idx]) - - cpp.write( - """/* THIS FILE IS GENERATED DO NOT EDIT */ -#include "android_mono_config.h" - -#ifdef ANDROID_ENABLED - -#include "core/io/compression.h" - - -namespace { - -// config -static const int config_compressed_size = %d; -static const int config_uncompressed_size = %d; -static const unsigned char config_compressed_data[] = { %s }; -} // namespace - -String get_godot_android_mono_config() { - Vector<uint8_t> data; - data.resize(config_uncompressed_size); - uint8_t* w = data.ptrw(); - Compression::decompress(w, config_uncompressed_size, config_compressed_data, - config_compressed_size, Compression::MODE_DEFLATE); - String s; - if (s.parse_utf8((const char *)w, data.size()) != OK) { - ERR_FAIL_V(String()); - } - return s; -} - -#endif // ANDROID_ENABLED -""" - % (compr_size, decompr_size, bytes_seq_str) - ) diff --git a/modules/mono/build_scripts/mono_android_config.xml b/modules/mono/build_scripts/mono_android_config.xml deleted file mode 100644 index e79670afd2..0000000000 --- a/modules/mono/build_scripts/mono_android_config.xml +++ /dev/null @@ -1,28 +0,0 @@ -<configuration> - <dllmap wordsize="32" dll="i:cygwin1.dll" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="i:cygwin1.dll" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="libc" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="libc" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="intl" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="intl" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="libintl" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="libintl" target="/system/lib64/libc.so" /> - <dllmap dll="MonoPosixHelper" target="libMonoPosixHelper.so" /> - <dllmap dll="System.Native" target="libmono-native.so" /> - <dllmap wordsize="32" dll="i:msvcrt" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="i:msvcrt" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="i:msvcrt.dll" target="/system/lib/libc.so" /> - <dllmap wordsize="64" dll="i:msvcrt.dll" target="/system/lib64/libc.so" /> - <dllmap wordsize="32" dll="sqlite" target="/system/lib/libsqlite.so" /> - <dllmap wordsize="64" dll="sqlite" target="/system/lib64/libsqlite.so" /> - <dllmap wordsize="32" dll="sqlite3" target="/system/lib/libsqlite.so" /> - <dllmap wordsize="64" dll="sqlite3" target="/system/lib64/libsqlite.so" /> - <dllmap wordsize="32" dll="liblog" target="/system/lib/liblog.so" /> - <dllmap wordsize="64" dll="liblog" target="/system/lib64/liblog.so" /> - <dllmap dll="i:kernel32.dll"> - <dllentry dll="__Internal" name="CopyMemory" target="mono_win32_compat_CopyMemory"/> - <dllentry dll="__Internal" name="FillMemory" target="mono_win32_compat_FillMemory"/> - <dllentry dll="__Internal" name="MoveMemory" target="mono_win32_compat_MoveMemory"/> - <dllentry dll="__Internal" name="ZeroMemory" target="mono_win32_compat_ZeroMemory"/> - </dllmap> -</configuration> diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index e69904c54b..c7f9b39b48 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -1,65 +1,5 @@ import os import os.path -import subprocess - -from SCons.Script import Dir, Environment - -if os.name == "nt": - from . import mono_reg_utils as monoreg - - -android_arch_dirs = { - "armv7": "armeabi-v7a", - "arm64v8": "arm64-v8a", - "x86": "x86", - "x86_64": "x86_64", -} - - -def get_android_out_dir(env): - return os.path.join( - Dir("#platform/android/java/lib/libs").abspath, - "release" if env["target"] == "release" else "debug", - android_arch_dirs[env["android_arch"]], - ) - - -def find_name_in_dir_files(directory, names, prefixes=[""], extensions=[""]): - for extension in extensions: - if extension and not extension.startswith("."): - extension = "." + extension - for prefix in prefixes: - for curname in names: - if os.path.isfile(os.path.join(directory, prefix + curname + extension)): - return curname - return "" - - -def find_file_in_dir(directory, names, prefixes=[""], extensions=[""]): - for extension in extensions: - if extension and not extension.startswith("."): - extension = "." + extension - for prefix in prefixes: - for curname in names: - filename = prefix + curname + extension - if os.path.isfile(os.path.join(directory, filename)): - return filename - return "" - - -def copy_file(src_dir, dst_dir, src_name, dst_name=""): - from shutil import copy - - src_path = os.path.join(Dir(src_dir).abspath, src_name) - dst_dir = Dir(dst_dir).abspath - - if not os.path.isdir(dst_dir): - os.makedirs(dst_dir) - - if dst_name: - copy(src_path, os.path.join(dst_dir, dst_name)) - else: - copy(src_path, dst_dir) def is_desktop(platform): @@ -71,504 +11,261 @@ def is_unix_like(platform): def module_supports_tools_on(platform): - return platform not in ["android", "javascript", "ios"] - - -def find_wasm_src_dir(mono_root): - hint_dirs = [ - os.path.join(mono_root, "src"), - os.path.join(mono_root, "../src"), - ] - for hint_dir in hint_dirs: - if os.path.isfile(os.path.join(hint_dir, "driver.c")): - return hint_dir - return "" + return is_desktop(platform) def configure(env, env_mono): - bits = env["bits"] - is_android = env["platform"] == "android" - is_javascript = env["platform"] == "javascript" - is_ios = env["platform"] == "ios" - is_ios_sim = is_ios and env["arch"] in ["x86", "x86_64"] + # is_android = env["platform"] == "android" + # is_javascript = env["platform"] == "javascript" + # is_ios = env["platform"] == "ios" + # is_ios_sim = is_ios and env["arch"] in ["x86", "x86_64"] tools_enabled = env["tools"] - mono_static = env["mono_static"] - copy_mono_root = env["copy_mono_root"] - - mono_prefix = env["mono_prefix"] - mono_bcl = env["mono_bcl"] - - mono_lib_names = ["mono-2.0-sgen", "monosgen-2.0"] - - if is_android and not env["android_arch"] in android_arch_dirs: - raise RuntimeError("This module does not support the specified 'android_arch': " + env["android_arch"]) if tools_enabled and not module_supports_tools_on(env["platform"]): - # TODO: - # Android: We have to add the data directory to the apk, concretely the Api and Tools folders. raise RuntimeError("This module does not currently support building for this platform with tools enabled") - if is_android and mono_static: - # FIXME: When static linking and doing something that requires libmono-native, we get a dlopen error as 'libmono-native' - # seems to depend on 'libmonosgen-2.0'. Could be fixed by re-directing to '__Internal' with a dllmap or in the dlopen hook. - raise RuntimeError("Statically linking Mono is not currently supported for this platform") - - if not mono_static and (is_javascript or is_ios): - raise RuntimeError("Dynamically linking Mono is not currently supported for this platform") - - if not mono_prefix and (os.getenv("MONO32_PREFIX") or os.getenv("MONO64_PREFIX")): - print( - "WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the" - " 'mono_prefix' SCons parameter instead" - ) - - # Although we don't support building with tools for any platform where we currently use static AOT, - # if these are supported in the future, we won't be using static AOT for them as that would be - # too restrictive for the editor. These builds would probably be made to only use the interpreter. - mono_aot_static = (is_ios and not is_ios_sim) and not env["tools"] - - # Static AOT is only supported on the root domain - mono_single_appdomain = mono_aot_static - - if mono_single_appdomain: - env_mono.Append(CPPDEFINES=["GD_MONO_SINGLE_APPDOMAIN"]) - - if (env["tools"] or env["target"] != "release") and not mono_single_appdomain: + if env["tools"]: env_mono.Append(CPPDEFINES=["GD_MONO_HOT_RELOAD"]) - if env["platform"] == "windows": - mono_root = mono_prefix - - if not mono_root and os.name == "nt": - mono_root = monoreg.find_mono_root_dir(bits) - - if not mono_root: - raise RuntimeError( - "Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter" - ) - - print("Found Mono root directory: " + mono_root) + app_host_dir = find_dotnet_app_host_dir(env) - mono_lib_path = os.path.join(mono_root, "lib") + def check_app_host_file_exists(file): + file_path = os.path.join(app_host_dir, file) + if not os.path.isfile(file_path): + raise RuntimeError("File not found: " + file_path) - env.Append(LIBPATH=mono_lib_path) - env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0")) + # TODO: + # All libnethost does for us is provide a function to find hostfxr. + # If we could handle that logic ourselves we could void linking it. - lib_suffixes = [".lib"] + # nethost file names: + # static: libnethost.a/lib + # shared: libnethost.a/dylib and nethost.dll + check_app_host_file_exists("libnethost.lib" if os.name == "nt" else "libnethost.a") + check_app_host_file_exists("nethost.h") + check_app_host_file_exists("hostfxr.h") + check_app_host_file_exists("coreclr_delegates.h") - if not env.msvc: - # MingW supports both '.a' and '.lib' - lib_suffixes.insert(0, ".a") + env_mono.Prepend(CPPPATH=app_host_dir) - if mono_static: - if env.msvc: - mono_static_lib_name = "libmono-static-sgen" - else: - mono_static_lib_name = "libmonosgen-2.0" + env.Append(LIBPATH=[app_host_dir]) - mono_static_lib_file = find_file_in_dir(mono_lib_path, [mono_static_lib_name], extensions=lib_suffixes) + # Only the editor build links nethost, which is needed to find hostfxr. + # Exported games don't need this logic as hostfxr is bundled with them. + if tools_enabled: + libnethost_path = os.path.join(app_host_dir, "libnethost.lib" if os.name == "nt" else "libnethost.a") - if not mono_static_lib_file: - raise RuntimeError("Could not find static mono library in: " + mono_lib_path) + if env["platform"] == "windows": + env_mono.Append(CPPDEFINES=["NETHOST_USE_AS_STATIC"]) if env.msvc: - env.Append(LINKFLAGS=mono_static_lib_file) - - env.Append(LINKFLAGS="Mincore.lib") - env.Append(LINKFLAGS="msvcrt.lib") - env.Append(LINKFLAGS="LIBCMT.lib") - env.Append(LINKFLAGS="Psapi.lib") + env.Append(LINKFLAGS="libnethost.lib") else: - mono_static_lib_file_path = os.path.join(mono_lib_path, mono_static_lib_file) - env.Append(LINKFLAGS=["-Wl,-whole-archive", mono_static_lib_file_path, "-Wl,-no-whole-archive"]) - - env.Append(LIBS=["psapi"]) - env.Append(LIBS=["version"]) + env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"]) else: - mono_lib_file = find_file_in_dir(mono_lib_path, mono_lib_names, extensions=lib_suffixes) + is_apple = env["platform"] in ["macos", "ios"] + # is_macos = is_apple and not is_ios - if not mono_lib_file: - raise RuntimeError("Could not find mono library in: " + mono_lib_path) + # if is_ios and not is_ios_sim: + # env_mono.Append(CPPDEFINES=["IOS_DEVICE"]) - if env.msvc: - env.Append(LINKFLAGS=mono_lib_file) + if is_apple: + env.Append(LINKFLAGS=["-Wl,-force_load," + libnethost_path]) else: - mono_lib_file_path = os.path.join(mono_lib_path, mono_lib_file) - env.Append(LINKFLAGS=mono_lib_file_path) + env.Append(LINKFLAGS=["-Wl,-whole-archive", libnethost_path, "-Wl,-no-whole-archive"]) - mono_bin_path = os.path.join(mono_root, "bin") - mono_dll_file = find_file_in_dir(mono_bin_path, mono_lib_names, prefixes=["", "lib"], extensions=[".dll"]) +def find_dotnet_app_host_dir(env): + dotnet_version = "6.0" - if not mono_dll_file: - raise RuntimeError("Could not find mono shared library in: " + mono_bin_path) + dotnet_root = env["dotnet_root"] - copy_file(mono_bin_path, "#bin", mono_dll_file) - else: - is_apple = env["platform"] in ["macos", "ios"] - is_macos = is_apple and not is_ios - - sharedlib_ext = ".dylib" if is_apple else ".so" - - mono_root = mono_prefix - mono_lib_path = "" - mono_so_file = "" - - if not mono_root and (is_android or is_javascript or is_ios): - raise RuntimeError( - "Mono installation directory not found; specify one manually with the 'mono_prefix' SCons parameter" - ) - - if not mono_root and is_macos: - # Try with some known directories under macOS - hint_dirs = ["/Library/Frameworks/Mono.framework/Versions/Current", "/usr/local/var/homebrew/linked/mono"] - for hint_dir in hint_dirs: - if os.path.isdir(hint_dir): - mono_root = hint_dir - break - - # We can't use pkg-config to link mono statically, - # but we can still use it to find the mono root directory - if not mono_root and mono_static: - mono_root = pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext) - if not mono_root: - raise RuntimeError( - "Building with mono_static=yes, but failed to find the mono prefix with pkg-config; " - + "specify one manually with the 'mono_prefix' SCons parameter" - ) - - if is_ios and not is_ios_sim: - env_mono.Append(CPPDEFINES=["IOS_DEVICE"]) - - if mono_root: - print("Found Mono root directory: " + mono_root) - - mono_lib_path = os.path.join(mono_root, "lib") - - env.Append(LIBPATH=[mono_lib_path]) - env_mono.Prepend(CPPPATH=os.path.join(mono_root, "include", "mono-2.0")) - - mono_lib = find_name_in_dir_files(mono_lib_path, mono_lib_names, prefixes=["lib"], extensions=[".a"]) - - if not mono_lib: - raise RuntimeError("Could not find mono library in: " + mono_lib_path) - - env_mono.Append(CPPDEFINES=["_REENTRANT"]) - - if mono_static: - if not is_javascript: - env.Append(LINKFLAGS=["-rdynamic"]) - - mono_lib_file = os.path.join(mono_lib_path, "lib" + mono_lib + ".a") - - if is_apple: - if is_macos: - env.Append(LINKFLAGS=["-Wl,-force_load," + mono_lib_file]) - else: - arch = env["arch"] - - def copy_mono_lib(libname_wo_ext): - copy_file( - mono_lib_path, "#bin", libname_wo_ext + ".a", "%s.ios.%s.a" % (libname_wo_ext, arch) - ) - - # Copy Mono libraries to the output folder. These are meant to be bundled with - # the export templates and added to the Xcode project when exporting a game. - copy_mono_lib("lib" + mono_lib) - copy_mono_lib("libmono-native") - copy_mono_lib("libmono-profiler-log") - - if not is_ios_sim: - copy_mono_lib("libmono-ee-interp") - copy_mono_lib("libmono-icall-table") - copy_mono_lib("libmono-ilgen") - else: - assert is_desktop(env["platform"]) or is_android or is_javascript - env.Append(LINKFLAGS=["-Wl,-whole-archive", mono_lib_file, "-Wl,-no-whole-archive"]) - - if is_javascript: - env.Append(LIBS=["mono-icall-table", "mono-native", "mono-ilgen", "mono-ee-interp"]) - - wasm_src_dir = os.path.join(mono_root, "src") - if not os.path.isdir(wasm_src_dir): - raise RuntimeError("Could not find mono wasm src directory") - - # Ideally this should be defined only for 'driver.c', but I can't fight scons for another 2 hours - env_mono.Append(CPPDEFINES=["CORE_BINDINGS"]) - - env_mono.add_source_files( - env.modules_sources, - [ - os.path.join(wasm_src_dir, "driver.c"), - os.path.join(wasm_src_dir, "zlib-helper.c"), - os.path.join(wasm_src_dir, "corebindings.c"), - ], - ) - - env.Append( - LINKFLAGS=[ - "--js-library", - os.path.join(wasm_src_dir, "library_mono.js"), - "--js-library", - os.path.join(wasm_src_dir, "binding_support.js"), - "--js-library", - os.path.join(wasm_src_dir, "dotnet_support.js"), - ] - ) - else: - env.Append(LIBS=[mono_lib]) - - if is_macos: - env.Append(LIBS=["iconv", "pthread"]) - elif is_android: - pass # Nothing - elif is_ios: - pass # Nothing, linking is delegated to the exported Xcode project - elif is_javascript: - env.Append(LIBS=["m", "rt", "dl", "pthread"]) - else: - env.Append(LIBS=["m", "rt", "dl", "pthread"]) - - if not mono_static: - mono_so_file = find_file_in_dir( - mono_lib_path, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext] - ) - - if not mono_so_file: - raise RuntimeError("Could not find mono shared library in: " + mono_lib_path) - else: - assert not mono_static + if not dotnet_root: + dotnet_cmd = find_executable("dotnet") + if dotnet_cmd: + sdk_path = find_dotnet_sdk(dotnet_cmd, dotnet_version) + if sdk_path: + dotnet_root = os.path.abspath(os.path.join(sdk_path, os.pardir)) - # TODO: Add option to force using pkg-config - print("Mono root directory not found. Using pkg-config instead") + if not dotnet_root: + raise RuntimeError("Cannot find .NET Core Sdk") - env.ParseConfig("pkg-config monosgen-2 --libs") - env_mono.ParseConfig("pkg-config monosgen-2 --cflags") + print("Found .NET Core Sdk root directory: " + dotnet_root) - tmpenv = Environment() - tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) - tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L") + dotnet_cmd = os.path.join(dotnet_root, "dotnet.exe" if os.name == "nt" else "dotnet") - for hint_dir in tmpenv["LIBPATH"]: - file_found = find_file_in_dir(hint_dir, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext]) - if file_found: - mono_lib_path = hint_dir - mono_so_file = file_found - break + runtime_identifier = determine_runtime_identifier(env) - if not mono_so_file: - raise RuntimeError("Could not find mono shared library in: " + str(tmpenv["LIBPATH"])) + # TODO: In the future, if it can't be found this way, we want to obtain it + # from the runtime.{runtime_identifier}.Microsoft.NETCore.DotNetAppHost NuGet package. + app_host_version = find_app_host_version(dotnet_cmd, dotnet_version) + if not app_host_version: + raise RuntimeError("Cannot find .NET app host for version: " + dotnet_version) - if not mono_static: - libs_output_dir = get_android_out_dir(env) if is_android else "#bin" - copy_file(mono_lib_path, libs_output_dir, mono_so_file) + def get_runtime_path(): + return os.path.join( + dotnet_root, + "packs", + "Microsoft.NETCore.App.Host." + runtime_identifier, + app_host_version, + "runtimes", + runtime_identifier, + "native", + ) - if not tools_enabled: - if is_desktop(env["platform"]): - if not mono_root: - mono_root = ( - subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip() - ) + app_host_dir = get_runtime_path() - make_template_dir(env, mono_root) - elif is_android: - # Compress Android Mono Config - from . import make_android_mono_config + # Some Linux distros use their distro name as the RID in these paths. + # If the initial generic path doesn't exist, try to get the RID from `dotnet --info`. + # The generic RID should still be the first choice. Some platforms like Windows 10 + # define the RID as `win10-x64` but still use the generic `win-x64` for directory names. + if not app_host_dir or not os.path.isdir(app_host_dir): + runtime_identifier = find_dotnet_cli_rid(dotnet_cmd) + app_host_dir = get_runtime_path() - module_dir = os.getcwd() - config_file_path = os.path.join(module_dir, "build_scripts", "mono_android_config.xml") - make_android_mono_config.generate_compressed_config(config_file_path, "mono_gd/") + return app_host_dir - # Copy the required shared libraries - copy_mono_shared_libs(env, mono_root, None) - elif is_javascript: - pass # No data directory for this platform - elif is_ios: - pass # No data directory for this platform - if copy_mono_root: - if not mono_root: - mono_root = subprocess.check_output(["pkg-config", "mono-2", "--variable=prefix"]).decode("utf8").strip() +def determine_runtime_identifier(env): + names_map = { + "windows": "win", + "macos": "osx", + "linuxbsd": "linux", + "server": "linux", # FIXME: Is server linux only, or also macos? + } - if tools_enabled: - # Only supported for editor builds. - copy_mono_root_files(env, mono_root, mono_bcl) + # .NET RID architectures: x86, x64, arm, or arm64 + platform = env["platform"] -def make_template_dir(env, mono_root): - from shutil import rmtree + if is_desktop(platform): + if env["arch"] in ["arm", "arm32"]: + rid = "arm" + elif env["arch"] == "arm64": + rid = "arm64" + else: + bits = env["bits"] + bit_arch_map = {"64": "x64", "32": "x86"} + rid = bit_arch_map[bits] + return "%s-%s" % (names_map[platform], rid) + else: + raise NotImplementedError() - platform = env["platform"] - target = env["target"] - template_dir_name = "" +def find_app_host_version(dotnet_cmd, search_version_str): + import subprocess + from distutils.version import LooseVersion - assert is_desktop(platform) + search_version = LooseVersion(search_version_str) - template_dir_name = "data.mono.%s.%s.%s" % (platform, env["bits"], target) + try: + env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") + lines = subprocess.check_output([dotnet_cmd, "--list-runtimes"], env=env).splitlines() - output_dir = Dir("#bin").abspath - template_dir = os.path.join(output_dir, template_dir_name) + for line_bytes in lines: + line = line_bytes.decode("utf-8") + if not line.startswith("Microsoft.NETCore.App "): + continue - template_mono_root_dir = os.path.join(template_dir, "Mono") + parts = line.split(" ", 2) + if len(parts) < 3: + continue - if os.path.isdir(template_mono_root_dir): - rmtree(template_mono_root_dir) # Clean first + version_str = parts[1] - # Copy etc/mono/ + version = LooseVersion(version_str) - template_mono_config_dir = os.path.join(template_mono_root_dir, "etc", "mono") - copy_mono_etc_dir(mono_root, template_mono_config_dir, platform) + if version >= search_version: + return version_str + except (subprocess.CalledProcessError, OSError) as e: + import sys - # Copy the required shared libraries + print(e, file=sys.stderr) - copy_mono_shared_libs(env, mono_root, template_mono_root_dir) + return "" -def copy_mono_root_files(env, mono_root, mono_bcl): - from glob import glob - from shutil import copy - from shutil import rmtree +def find_dotnet_sdk(dotnet_cmd, search_version_str): + import subprocess + from distutils.version import LooseVersion - if not mono_root: - raise RuntimeError("Mono installation directory not found") + search_version = LooseVersion(search_version_str) - output_dir = Dir("#bin").abspath - editor_mono_root_dir = os.path.join(output_dir, "GodotSharp", "Mono") + try: + env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") + lines = subprocess.check_output([dotnet_cmd, "--list-sdks"], env=env).splitlines() - if os.path.isdir(editor_mono_root_dir): - rmtree(editor_mono_root_dir) # Clean first + for line_bytes in lines: + line = line_bytes.decode("utf-8") - # Copy etc/mono/ + parts = line.split(" ", 1) + if len(parts) < 2: + continue - editor_mono_config_dir = os.path.join(editor_mono_root_dir, "etc", "mono") - copy_mono_etc_dir(mono_root, editor_mono_config_dir, env["platform"]) + version_str = parts[0] - # Copy the required shared libraries + version = LooseVersion(version_str) - copy_mono_shared_libs(env, mono_root, editor_mono_root_dir) + if version < search_version: + continue - # Copy framework assemblies + path_part = parts[1] + return path_part[1 : path_part.find("]")] + except (subprocess.CalledProcessError, OSError) as e: + import sys - mono_framework_dir = mono_bcl or os.path.join(mono_root, "lib", "mono", "4.5") - mono_framework_facades_dir = os.path.join(mono_framework_dir, "Facades") + print(e, file=sys.stderr) - editor_mono_framework_dir = os.path.join(editor_mono_root_dir, "lib", "mono", "4.5") - editor_mono_framework_facades_dir = os.path.join(editor_mono_framework_dir, "Facades") + return "" - if not os.path.isdir(editor_mono_framework_dir): - os.makedirs(editor_mono_framework_dir) - if not os.path.isdir(editor_mono_framework_facades_dir): - os.makedirs(editor_mono_framework_facades_dir) - for assembly in glob(os.path.join(mono_framework_dir, "*.dll")): - copy(assembly, editor_mono_framework_dir) - for assembly in glob(os.path.join(mono_framework_facades_dir, "*.dll")): - copy(assembly, editor_mono_framework_facades_dir) +def find_dotnet_cli_rid(dotnet_cmd): + import subprocess + try: + env = dict(os.environ, DOTNET_CLI_UI_LANGUAGE="en-US") + lines = subprocess.check_output([dotnet_cmd, "--info"], env=env).splitlines() -def copy_mono_etc_dir(mono_root, target_mono_config_dir, platform): - from distutils.dir_util import copy_tree - from glob import glob - from shutil import copy + for line_bytes in lines: + line = line_bytes.decode("utf-8") + if not line.startswith(" RID:"): + continue - if not os.path.isdir(target_mono_config_dir): - os.makedirs(target_mono_config_dir) + parts = line.split() + if len(parts) < 2: + continue - mono_etc_dir = os.path.join(mono_root, "etc", "mono") - if not os.path.isdir(mono_etc_dir): - mono_etc_dir = "" - etc_hint_dirs = [] - if platform != "windows": - etc_hint_dirs += ["/etc/mono", "/usr/local/etc/mono"] - if "MONO_CFG_DIR" in os.environ: - etc_hint_dirs += [os.path.join(os.environ["MONO_CFG_DIR"], "mono")] - for etc_hint_dir in etc_hint_dirs: - if os.path.isdir(etc_hint_dir): - mono_etc_dir = etc_hint_dir - break - if not mono_etc_dir: - raise RuntimeError("Mono installation etc directory not found") + return parts[1] + except (subprocess.CalledProcessError, OSError) as e: + import sys - copy_tree(os.path.join(mono_etc_dir, "2.0"), os.path.join(target_mono_config_dir, "2.0")) - copy_tree(os.path.join(mono_etc_dir, "4.0"), os.path.join(target_mono_config_dir, "4.0")) - copy_tree(os.path.join(mono_etc_dir, "4.5"), os.path.join(target_mono_config_dir, "4.5")) - if os.path.isdir(os.path.join(mono_etc_dir, "mconfig")): - copy_tree(os.path.join(mono_etc_dir, "mconfig"), os.path.join(target_mono_config_dir, "mconfig")) + print(e, file=sys.stderr) - for file in glob(os.path.join(mono_etc_dir, "*")): - if os.path.isfile(file): - copy(file, target_mono_config_dir) + return "" -def copy_mono_shared_libs(env, mono_root, target_mono_root_dir): - from shutil import copy +ENV_PATH_SEP = ";" if os.name == "nt" else ":" - def copy_if_exists(src, dst): - if os.path.isfile(src): - copy(src, dst) - platform = env["platform"] +def find_executable(name): + is_windows = os.name == "nt" + windows_exts = os.environ["PATHEXT"].split(ENV_PATH_SEP) if is_windows else None + path_dirs = os.environ["PATH"].split(ENV_PATH_SEP) - if platform == "windows": - src_mono_bin_dir = os.path.join(mono_root, "bin") - target_mono_bin_dir = os.path.join(target_mono_root_dir, "bin") + search_dirs = path_dirs + [os.getcwd()] # cwd is last in the list - if not os.path.isdir(target_mono_bin_dir): - os.makedirs(target_mono_bin_dir) + for dir in search_dirs: + path = os.path.join(dir, name) - mono_posix_helper_file = find_file_in_dir( - src_mono_bin_dir, ["MonoPosixHelper"], prefixes=["", "lib"], extensions=[".dll"] - ) - copy( - os.path.join(src_mono_bin_dir, mono_posix_helper_file), - os.path.join(target_mono_bin_dir, "MonoPosixHelper.dll"), - ) + if is_windows: + for extension in windows_exts: + path_with_ext = path + extension - # For newer versions - btls_dll_path = os.path.join(src_mono_bin_dir, "libmono-btls-shared.dll") - if os.path.isfile(btls_dll_path): - copy(btls_dll_path, target_mono_bin_dir) - else: - target_mono_lib_dir = ( - get_android_out_dir(env) if platform == "android" else os.path.join(target_mono_root_dir, "lib") - ) + if os.path.isfile(path_with_ext) and os.access(path_with_ext, os.X_OK): + return path_with_ext + else: + if os.path.isfile(path) and os.access(path, os.X_OK): + return path - if not os.path.isdir(target_mono_lib_dir): - os.makedirs(target_mono_lib_dir) - - lib_file_names = [] - if platform == "macos": - lib_file_names = [ - lib_name + ".dylib" - for lib_name in ["libmono-btls-shared", "libmono-native-compat", "libMonoPosixHelper"] - ] - elif is_unix_like(platform): - lib_file_names = [ - lib_name + ".so" - for lib_name in [ - "libmono-btls-shared", - "libmono-ee-interp", - "libmono-native", - "libMonoPosixHelper", - "libmono-profiler-aot", - "libmono-profiler-coverage", - "libmono-profiler-log", - "libMonoSupportW", - ] - ] - - for lib_file_name in lib_file_names: - copy_if_exists(os.path.join(mono_root, "lib", lib_file_name), target_mono_lib_dir) - - -def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext): - tmpenv = Environment() - tmpenv.AppendENVPath("PKG_CONFIG_PATH", os.getenv("PKG_CONFIG_PATH")) - tmpenv.ParseConfig("pkg-config monosgen-2 --libs-only-L") - for hint_dir in tmpenv["LIBPATH"]: - name_found = find_name_in_dir_files(hint_dir, mono_lib_names, prefixes=["lib"], extensions=[sharedlib_ext]) - if name_found and os.path.isdir(os.path.join(hint_dir, "..", "include", "mono-2.0")): - return os.path.join(hint_dir, "..") return "" diff --git a/modules/mono/build_scripts/mono_reg_utils.py b/modules/mono/build_scripts/mono_reg_utils.py deleted file mode 100644 index 43c1ec8f8a..0000000000 --- a/modules/mono/build_scripts/mono_reg_utils.py +++ /dev/null @@ -1,112 +0,0 @@ -import os -import platform - -if os.name == "nt": - import winreg - - -def _reg_open_key(key, subkey): - try: - return winreg.OpenKey(key, subkey) - except OSError: - if platform.architecture()[0] == "32bit": - bitness_sam = winreg.KEY_WOW64_64KEY - else: - bitness_sam = winreg.KEY_WOW64_32KEY - return winreg.OpenKey(key, subkey, 0, winreg.KEY_READ | bitness_sam) - - -def _reg_open_key_bits(key, subkey, bits): - sam = winreg.KEY_READ - - if platform.architecture()[0] == "32bit": - if bits == "64": - # Force 32bit process to search in 64bit registry - sam |= winreg.KEY_WOW64_64KEY - else: - if bits == "32": - # Force 64bit process to search in 32bit registry - sam |= winreg.KEY_WOW64_32KEY - - return winreg.OpenKey(key, subkey, 0, sam) - - -def _find_mono_in_reg(subkey, bits): - try: - with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey: - value = winreg.QueryValueEx(hKey, "SdkInstallRoot")[0] - return value - except OSError: - return None - - -def _find_mono_in_reg_old(subkey, bits): - try: - with _reg_open_key_bits(winreg.HKEY_LOCAL_MACHINE, subkey, bits) as hKey: - default_clr = winreg.QueryValueEx(hKey, "DefaultCLR")[0] - if default_clr: - return _find_mono_in_reg(subkey + "\\" + default_clr, bits) - return None - except OSError: - return None - - -def find_mono_root_dir(bits): - root_dir = _find_mono_in_reg(r"SOFTWARE\Mono", bits) - if root_dir is not None: - return str(root_dir) - root_dir = _find_mono_in_reg_old(r"SOFTWARE\Novell\Mono", bits) - if root_dir is not None: - return str(root_dir) - return "" - - -def find_msbuild_tools_path_reg(): - import subprocess - - vswhere = os.getenv("PROGRAMFILES(X86)") - if not vswhere: - vswhere = os.getenv("PROGRAMFILES") - vswhere += r"\Microsoft Visual Studio\Installer\vswhere.exe" - - vswhere_args = ["-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"] - - try: - lines = subprocess.check_output([vswhere] + vswhere_args).splitlines() - - for line in lines: - parts = line.decode("utf-8").split(":", 1) - - if len(parts) < 2 or parts[0] != "installationPath": - continue - - val = parts[1].strip() - - if not val: - raise ValueError("Value of `installationPath` entry is empty") - - # Since VS2019, the directory is simply named "Current" - msbuild_dir = os.path.join(val, "MSBuild\\Current\\Bin") - if os.path.isdir(msbuild_dir): - return msbuild_dir - - # Directory name "15.0" is used in VS 2017 - return os.path.join(val, "MSBuild\\15.0\\Bin") - - raise ValueError("Cannot find `installationPath` entry") - except ValueError as e: - print("Error reading output from vswhere: " + e.message) - except subprocess.CalledProcessError as e: - print(e.output) - except OSError as e: - print(e) - - # Try to find 14.0 in the Registry - - try: - subkey = r"SOFTWARE\Microsoft\MSBuild\ToolsVersions\14.0" - with _reg_open_key(winreg.HKEY_LOCAL_MACHINE, subkey) as hKey: - value = winreg.QueryValueEx(hKey, "MSBuildToolsPath")[0] - return value - except OSError: - return "" diff --git a/modules/mono/build_scripts/solution_builder.py b/modules/mono/build_scripts/solution_builder.py deleted file mode 100644 index 6a621c3c8b..0000000000 --- a/modules/mono/build_scripts/solution_builder.py +++ /dev/null @@ -1,145 +0,0 @@ -import os - - -verbose = False - - -def find_dotnet_cli(): - import os.path - - if os.name == "nt": - for hint_dir in os.environ["PATH"].split(os.pathsep): - hint_dir = hint_dir.strip('"') - hint_path = os.path.join(hint_dir, "dotnet") - if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): - return hint_path - if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): - return hint_path + ".exe" - else: - for hint_dir in os.environ["PATH"].split(os.pathsep): - hint_dir = hint_dir.strip('"') - hint_path = os.path.join(hint_dir, "dotnet") - if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): - return hint_path - - -def find_msbuild_unix(): - import os.path - import sys - - hint_dirs = [] - if sys.platform == "darwin": - hint_dirs[:0] = [ - "/Library/Frameworks/Mono.framework/Versions/Current/bin", - "/usr/local/var/homebrew/linked/mono/bin", - ] - - for hint_dir in hint_dirs: - hint_path = os.path.join(hint_dir, "msbuild") - if os.path.isfile(hint_path): - return hint_path - elif os.path.isfile(hint_path + ".exe"): - return hint_path + ".exe" - - for hint_dir in os.environ["PATH"].split(os.pathsep): - hint_dir = hint_dir.strip('"') - hint_path = os.path.join(hint_dir, "msbuild") - if os.path.isfile(hint_path) and os.access(hint_path, os.X_OK): - return hint_path - if os.path.isfile(hint_path + ".exe") and os.access(hint_path + ".exe", os.X_OK): - return hint_path + ".exe" - - return None - - -def find_msbuild_windows(env): - from .mono_reg_utils import find_mono_root_dir, find_msbuild_tools_path_reg - - mono_root = env["mono_prefix"] or find_mono_root_dir(env["bits"]) - - if not mono_root: - raise RuntimeError("Cannot find mono root directory") - - mono_bin_dir = os.path.join(mono_root, "bin") - msbuild_mono = os.path.join(mono_bin_dir, "msbuild.bat") - - msbuild_tools_path = find_msbuild_tools_path_reg() - - if msbuild_tools_path: - return (os.path.join(msbuild_tools_path, "MSBuild.exe"), {}) - - if os.path.isfile(msbuild_mono): - # The (Csc/Vbc/Fsc)ToolExe environment variables are required when - # building with Mono's MSBuild. They must point to the batch files - # in Mono's bin directory to make sure they are executed with Mono. - mono_msbuild_env = { - "CscToolExe": os.path.join(mono_bin_dir, "csc.bat"), - "VbcToolExe": os.path.join(mono_bin_dir, "vbc.bat"), - "FscToolExe": os.path.join(mono_bin_dir, "fsharpc.bat"), - } - return (msbuild_mono, mono_msbuild_env) - - return None - - -def run_command(command, args, env_override=None, name=None): - def cmd_args_to_str(cmd_args): - return " ".join([arg if not " " in arg else '"%s"' % arg for arg in cmd_args]) - - args = [command] + args - - if name is None: - name = os.path.basename(command) - - if verbose: - print("Running '%s': %s" % (name, cmd_args_to_str(args))) - - import subprocess - - try: - if env_override is None: - subprocess.check_call(args) - else: - subprocess.check_call(args, env=env_override) - except subprocess.CalledProcessError as e: - raise RuntimeError("'%s' exited with error code: %s" % (name, e.returncode)) - - -def build_solution(env, solution_path, build_config, extra_msbuild_args=[]): - global verbose - verbose = env["verbose"] - - msbuild_env = os.environ.copy() - - # Needed when running from Developer Command Prompt for VS - if "PLATFORM" in msbuild_env: - del msbuild_env["PLATFORM"] - - msbuild_args = [] - - dotnet_cli = find_dotnet_cli() - - if dotnet_cli: - msbuild_path = dotnet_cli - msbuild_args += ["msbuild"] # `dotnet msbuild` command - else: - # Find MSBuild - if os.name == "nt": - msbuild_info = find_msbuild_windows(env) - if msbuild_info is None: - raise RuntimeError("Cannot find MSBuild executable") - msbuild_path = msbuild_info[0] - msbuild_env.update(msbuild_info[1]) - else: - msbuild_path = find_msbuild_unix() - if msbuild_path is None: - raise RuntimeError("Cannot find MSBuild executable") - - print("MSBuild path: " + msbuild_path) - - # Build solution - - msbuild_args += [solution_path, "/restore", "/t:Build", "/p:Configuration=" + build_config] - msbuild_args += extra_msbuild_args - - run_command(msbuild_path, msbuild_args, env_override=msbuild_env, name="msbuild") diff --git a/modules/mono/config.py b/modules/mono/config.py index d895d2d92d..d156877929 100644 --- a/modules/mono/config.py +++ b/modules/mono/config.py @@ -1,4 +1,6 @@ -supported_platforms = ["windows", "macos", "linuxbsd", "server", "android", "haiku", "javascript", "ios"] +# Prior to .NET Core, we supported these: ["windows", "macos", "linuxbsd", "server", "android", "haiku", "javascript", "ios"] +# Eventually support for each them should be added back (except Haiku if not supported by .NET Core) +supported_platforms = ["windows", "macos", "linuxbsd", "server"] def can_build(env, platform): @@ -13,26 +15,11 @@ def get_opts(platform): return [ PathVariable( - "mono_prefix", - "Path to the Mono installation directory for the target platform and architecture", + "dotnet_root", + "Path to the .NET Sdk installation directory for the target platform and architecture", "", PathVariable.PathAccept, ), - PathVariable( - "mono_bcl", - "Path to a custom Mono BCL (Base Class Library) directory for the target platform", - "", - PathVariable.PathAccept, - ), - BoolVariable("mono_static", "Statically link Mono", default_mono_static), - BoolVariable("mono_glue", "Build with the Mono glue sources", True), - BoolVariable("build_cil", "Build C# solutions", True), - BoolVariable( - "copy_mono_root", "Make a copy of the Mono installation directory to bundle with the editor", True - ), - BoolVariable( - "mono_bundles_zlib", "Specify if the Mono runtime was built with bundled zlib", default_mono_bundles_zlib - ), ] @@ -44,13 +31,6 @@ def configure(env): env.add_module_version_string("mono") - if env["mono_bundles_zlib"]: - # Mono may come with zlib bundled for WASM or on newer version when built with MinGW. - print("This Mono runtime comes with zlib bundled. Disabling 'builtin_zlib'...") - env["builtin_zlib"] = False - thirdparty_zlib_dir = "#thirdparty/zlib/" - env.Prepend(CPPPATH=[thirdparty_zlib_dir]) - def get_doc_classes(): return [ diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 475b483d6c..8b135051c5 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -30,8 +30,6 @@ #include "csharp_script.h" -#include <mono/metadata/threads.h> -#include <mono/metadata/tokentype.h> #include <stdint.h> #include "core/config/project_settings.h" @@ -57,10 +55,8 @@ #endif #include "godotsharp_dirs.h" +#include "managed_callable.h" #include "mono_gd/gd_mono_cache.h" -#include "mono_gd/gd_mono_class.h" -#include "mono_gd/gd_mono_marshal.h" -#include "mono_gd/gd_mono_utils.h" #include "signal_awaiter_utils.h" #include "utils/macros.h" #include "utils/string_utils.h" @@ -69,17 +65,8 @@ #ifdef TOOLS_ENABLED static bool _create_project_solution_if_needed() { - String sln_path = GodotSharpDirs::get_project_sln_path(); - String csproj_path = GodotSharpDirs::get_project_csproj_path(); - - if (!FileAccess::exists(sln_path) || !FileAccess::exists(csproj_path)) { - // A solution does not yet exist, create a new one - - CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr); - return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolution"); - } - - return true; + CRASH_COND(CSharpLanguage::get_singleton()->get_godotsharp_editor() == nullptr); + return CSharpLanguage::get_singleton()->get_godotsharp_editor()->call("CreateProjectSolutionIfNeeded"); } #endif @@ -118,26 +105,21 @@ void CSharpLanguage::init() { } #endif - gdmono = memnew(GDMono); - gdmono->initialize(); - #if defined(TOOLS_ENABLED) && defined(DEBUG_METHODS_ENABLED) - // Generate bindings here, before loading assemblies. 'initialize_load_assemblies' aborts - // the applications if the api assemblies or the main tools assembly is missing, but this - // is not a problem for BindingsGenerator as it only needs the tools project editor assembly. + // Generate the bindings here, before loading assemblies. The Godot assemblies + // may be missing if the glue wasn't generated yet in order to build them. List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); BindingsGenerator::handle_cmdline_args(cmdline_args); #endif -#ifndef MONO_GLUE_ENABLED - print_line("Run this binary with '--generate-mono-glue path/to/modules/mono/glue'"); -#endif + gdmono = memnew(GDMono); + gdmono->initialize(); +#ifdef TOOLS_ENABLED if (gdmono->is_runtime_initialized()) { gdmono->initialize_load_assemblies(); } -#ifdef TOOLS_ENABLED EditorNode::add_init_callback(&_editor_init_callback); #endif } @@ -596,23 +578,19 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() return Vector<StackInfo>(); } _recursion_flag_ = true; - SCOPE_EXIT { _recursion_flag_ = false; }; - - GD_MONO_SCOPE_THREAD_ATTACH; + SCOPE_EXIT { + _recursion_flag_ = false; + }; - if (!gdmono->is_runtime_initialized() || !GDMono::get_singleton()->get_core_api_assembly() || !GDMonoCache::cached_data.corlib_cache_updated) { + if (!gdmono->is_runtime_initialized()) { return Vector<StackInfo>(); } - MonoObject *stack_trace = mono_object_new(mono_domain_get(), CACHED_CLASS(System_Diagnostics_StackTrace)->get_mono_ptr()); - - MonoBoolean need_file_info = true; - void *ctor_args[1] = { &need_file_info }; - - CACHED_METHOD(System_Diagnostics_StackTrace, ctor_bool)->invoke_raw(stack_trace, ctor_args); - Vector<StackInfo> si; - si = stack_trace_get_info(stack_trace); + + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.DebuggingUtils_GetCurrentStackInfo(&si); + } return si; #else @@ -620,63 +598,6 @@ Vector<ScriptLanguage::StackInfo> CSharpLanguage::debug_get_current_stack_info() #endif } -#ifdef DEBUG_ENABLED -Vector<ScriptLanguage::StackInfo> CSharpLanguage::stack_trace_get_info(MonoObject *p_stack_trace) { - // Printing an error here will result in endless recursion, so we must be careful - static thread_local bool _recursion_flag_ = false; - if (_recursion_flag_) { - return Vector<StackInfo>(); - } - _recursion_flag_ = true; - SCOPE_EXIT { _recursion_flag_ = false; }; - - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoException *exc = nullptr; - - MonoArray *frames = CACHED_METHOD_THUNK(System_Diagnostics_StackTrace, GetFrames).invoke(p_stack_trace, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - return Vector<StackInfo>(); - } - - int frame_count = mono_array_length(frames); - - if (frame_count <= 0) { - return Vector<StackInfo>(); - } - - Vector<StackInfo> si; - si.resize(frame_count); - - for (int i = 0; i < frame_count; i++) { - StackInfo &sif = si.write[i]; - MonoObject *frame = mono_array_get(frames, MonoObject *, i); - - MonoString *file_name; - int file_line_num; - MonoString *method_decl; - CACHED_METHOD_THUNK(DebuggingUtils, GetStackFrameInfo).invoke(frame, &file_name, &file_line_num, &method_decl, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - return Vector<StackInfo>(); - } - - // TODO - // what if the StackFrame method is null (method_decl is empty). should we skip this frame? - // can reproduce with a MissingMethodException on internal calls - - sif.file = GDMonoMarshal::mono_string_to_godot(file_name); - sif.line = file_line_num; - sif.func = GDMonoMarshal::mono_string_to_godot(method_decl); - } - - return si; -} -#endif - void CSharpLanguage::post_unsafe_reference(Object *p_obj) { #ifdef DEBUG_ENABLED MutexLock lock(unsafe_object_references_lock); @@ -698,48 +619,36 @@ void CSharpLanguage::pre_unsafe_unreference(Object *p_obj) { } void CSharpLanguage::frame() { - if (gdmono && gdmono->is_runtime_initialized() && gdmono->get_core_api_assembly() != nullptr) { - const Ref<MonoGCHandleRef> &task_scheduler_handle = GDMonoCache::cached_data.task_scheduler_handle; - - if (task_scheduler_handle.is_valid()) { - MonoObject *task_scheduler = task_scheduler_handle->get_target(); - - if (task_scheduler) { - MonoException *exc = nullptr; - CACHED_METHOD_THUNK(GodotTaskScheduler, Activate).invoke(task_scheduler, &exc); - - if (exc) { - GDMonoUtils::debug_unhandled_exception(exc); - } - } - } + if (gdmono && gdmono->is_runtime_initialized() && GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.ScriptManagerBridge_FrameCallback(); } } struct CSharpScriptDepSort { - // must support sorting so inheritance works properly (parent must be reloaded first) + // Must support sorting so inheritance works properly (parent must be reloaded first) bool operator()(const Ref<CSharpScript> &A, const Ref<CSharpScript> &B) const { if (A == B) { - return false; // shouldn't happen but.. + // Shouldn't happen but just in case... + return false; } - GDMonoClass *I = B->base; + const Script *I = B->get_base_script().ptr(); while (I) { - if (I == A->script_class) { + if (I == A.ptr()) { // A is a base of B return true; } - I = I->get_parent_class(); + I = I->get_base_script().ptr(); } - return false; // not a base + // A isn't a base of B + return false; } }; void CSharpLanguage::reload_all_scripts() { #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { - GD_MONO_SCOPE_THREAD_ATTACH; reload_assemblies(false); } #endif @@ -756,7 +665,6 @@ void CSharpLanguage::reload_tool_script(const Ref<Script> &p_script, bool p_soft #ifdef GD_MONO_HOT_RELOAD if (is_assembly_reloading_needed()) { - GD_MONO_SCOPE_THREAD_ATTACH; reload_assemblies(p_soft_reload); } #endif @@ -768,28 +676,28 @@ bool CSharpLanguage::is_assembly_reloading_needed() { return false; } - GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); - - String appname_safe = ProjectSettings::get_singleton()->get_safe_project_name(); - - appname_safe += ".dll"; + String assembly_path = gdmono->get_project_assembly_path(); - if (proj_assembly) { - String proj_asm_path = proj_assembly->get_path(); - - if (!FileAccess::exists(proj_asm_path)) { - // Maybe it wasn't loaded from the default path, so check this as well - proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe); - if (!FileAccess::exists(proj_asm_path)) { - return false; // No assembly to load - } + if (!assembly_path.is_empty()) { + if (!FileAccess::exists(assembly_path)) { + return false; // No assembly to load } - if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) { + if (FileAccess::get_modified_time(assembly_path) <= gdmono->get_project_assembly_modified_time()) { return false; // Already up to date } } else { - if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) { + String assembly_name = ProjectSettings::get_singleton()->get_setting("dotnet/project/assembly_name"); + + if (assembly_name.is_empty()) { + assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); + } + + assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() + .plus_file(assembly_name + ".dll"); + assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path); + + if (!FileAccess::exists(assembly_path)) { return false; // No assembly to load } } @@ -802,6 +710,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { return; } + // TODO: + // Currently, this reloads all scripts, including those whose class is not part of the + // assembly load context being unloaded. As such, we unnecessarily reload GodotTools. + + print_verbose(".NET: Reloading assemblies..."); + // There is no soft reloading with Mono. It's always hard reloading. List<Ref<CSharpScript>> scripts; @@ -824,18 +738,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) { ManagedCallable *managed_callable = elem->self(); - MonoDelegate *delegate = (MonoDelegate *)managed_callable->delegate_handle.get_target(); + ERR_CONTINUE(managed_callable->delegate_handle.value == nullptr); Array serialized_data; - MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); - MonoException *exc = nullptr; - bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate).invoke(delegate, managed_serialized_data, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - continue; - } + bool success = GDMonoCache::managed_callbacks.DelegateUtils_TrySerializeDelegateWithGCHandle( + managed_callable->delegate_handle, &serialized_data); if (success) { ManagedCallable::instances_pending_reload.insert(managed_callable, serialized_data); @@ -864,17 +772,12 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { // If someone removes a script from a node, deletes the script, builds, adds a script to the // same node, then builds again, the script might have no path and also no script_class. In // that case, we can't (and don't need to) reload it. - if (script->get_path().is_empty() && !script->script_class) { + if (script->get_path().is_empty() && !script->valid) { continue; } to_reload.push_back(script); - if (script->get_path().is_empty()) { - script->tied_class_name_for_reload = script->script_class->get_name_for_lookup(); - script->tied_class_namespace_for_reload = script->script_class->get_namespace(); - } - // Script::instances are deleted during managed object disposal, which happens on domain finalize. // Only placeholders are kept. Therefore we need to keep a copy before that happens. @@ -907,17 +810,20 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { CSharpInstance *csi = static_cast<CSharpInstance *>(obj->get_script_instance()); - // Call OnBeforeSerialize - if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) { - obj->get_script_instance()->call(string_names.on_before_serialize); - } + // Call OnBeforeSerialize and save instance info - // Save instance info CSharpScript::StateBackup state; - // TODO: Proper state backup (Not only variants, serialize managed state of scripts) - csi->get_properties_state_for_reloading(state.properties); - csi->get_event_signals_state_for_reloading(state.event_signals); + Dictionary properties; + + GDMonoCache::managed_callbacks.CSharpInstanceBridge_SerializeState( + csi->get_gchandle_intptr(), &properties, &state.event_signals); + + for (const Variant *s = properties.next(nullptr); s != nullptr; s = properties.next(s)) { + StringName name = *s; + Variant value = properties[*s]; + state.properties.push_back(Pair<StringName, Variant>(name, value)); + } owners_map[obj->get_instance_id()] = state; } @@ -934,7 +840,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } // Do domain reload - if (gdmono->reload_scripts_domain() != OK) { + if (gdmono->reload_project_assemblies() != OK) { // Failed to reload the scripts domain // Make sure to add the scripts back to their owners before returning for (Ref<CSharpScript> &scr : to_reload) { @@ -965,6 +871,9 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { scr->pending_reload_state.erase(obj_id); } + + scr->pending_reload_instances.clear(); + scr->pending_reload_state.clear(); } return; @@ -976,53 +885,27 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { #ifdef TOOLS_ENABLED script->exports_invalidated = true; #endif - script->signals_invalidated = true; if (!script->get_path().is_empty()) { script->reload(p_soft_reload); if (!script->valid) { script->pending_reload_instances.clear(); + script->pending_reload_state.clear(); continue; } } else { - const StringName &class_namespace = script->tied_class_namespace_for_reload; - const StringName &class_name = script->tied_class_name_for_reload; - GDMonoAssembly *project_assembly = gdmono->get_project_assembly(); - - // Search in project and tools assemblies first as those are the most likely to have the class - GDMonoClass *script_class = (project_assembly ? project_assembly->get_class(class_namespace, class_name) : nullptr); - -#ifdef TOOLS_ENABLED - if (!script_class) { - GDMonoAssembly *tools_assembly = gdmono->get_tools_assembly(); - script_class = (tools_assembly ? tools_assembly->get_class(class_namespace, class_name) : nullptr); - } -#endif + bool success = GDMonoCache::managed_callbacks.ScriptManagerBridge_TryReloadRegisteredScriptWithClass(script.ptr()); - if (!script_class) { - script_class = gdmono->get_class(class_namespace, class_name); - } - - if (!script_class) { - // The class was removed, can't reload - script->pending_reload_instances.clear(); - continue; - } - - bool obj_type = CACHED_CLASS(GodotObject)->is_assignable_from(script_class); - if (!obj_type) { - // The class no longer inherits Godot.Object, can't reload + if (!success) { + // Couldn't reload script->pending_reload_instances.clear(); + script->pending_reload_state.clear(); continue; } - - GDMonoClass *native = GDMonoUtils::get_class_native_base(script_class); - - CSharpScript::initialize_for_managed_type(script, script_class, native); } - StringName native_name = NATIVE_GDMONOCLASS_NAME(script->native); + StringName native_name = script->get_instance_base_type(); { for (const ObjectID &obj_id : script->pending_reload_instances) { @@ -1087,57 +970,25 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { ERR_CONTINUE(!obj->get_script_instance()); - // TODO: Restore serialized state - CSharpScript::StateBackup &state_backup = script->pending_reload_state[obj_id]; - for (const Pair<StringName, Variant> &G : state_backup.properties) { - obj->get_script_instance()->set(G.first, G.second); - } - CSharpInstance *csi = CAST_CSHARP_INSTANCE(obj->get_script_instance()); if (csi) { - for (const Pair<StringName, Array> &G : state_backup.event_signals) { - const StringName &name = G.first; - const Array &serialized_data = G.second; - - HashMap<StringName, CSharpScript::EventSignal>::Iterator match = script->event_signals.find(name); + Dictionary properties; - if (!match) { - // The event or its signal attribute were removed - continue; - } - - const CSharpScript::EventSignal &event_signal = match->value; - - MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); - MonoDelegate *delegate = nullptr; - - MonoException *exc = nullptr; - bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegate).invoke(managed_serialized_data, &delegate, &exc); - - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - continue; - } - - if (success) { - ERR_CONTINUE(delegate == nullptr); - event_signal.field->set_value(csi->get_mono_object(), (MonoObject *)delegate); - } else if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Failed to deserialize event signal delegate\n"); - } + for (const Pair<StringName, Variant> &G : state_backup.properties) { + properties[G.first] = G.second; } - // Call OnAfterDeserialization - if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) { - obj->get_script_instance()->call(string_names.on_after_deserialize); - } + // Restore serialized state and call OnAfterDeserialization + GDMonoCache::managed_callbacks.CSharpInstanceBridge_DeserializeState( + csi->get_gchandle_intptr(), &properties, &state_backup.event_signals); } } script->pending_reload_instances.clear(); + script->pending_reload_state.clear(); } // Deserialize managed callables @@ -1148,20 +999,14 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { ManagedCallable *managed_callable = elem.key; const Array &serialized_data = elem.value; - MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); - MonoDelegate *delegate = nullptr; - - MonoException *exc = nullptr; - bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TryDeserializeDelegate).invoke(managed_serialized_data, &delegate, &exc); + GCHandleIntPtr delegate = { nullptr }; - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - continue; - } + bool success = GDMonoCache::managed_callbacks.DelegateUtils_TryDeserializeDelegateWithGCHandle( + &serialized_data, &delegate); if (success) { - ERR_CONTINUE(delegate == nullptr); - managed_callable->set_delegate(delegate); + ERR_CONTINUE(delegate.value == nullptr); + managed_callable->delegate_handle = delegate; } else if (OS::get_singleton()->is_stdout_verbose()) { OS::get_singleton()->print("Failed to deserialize delegate\n"); } @@ -1180,60 +1025,6 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) { } #endif -void CSharpLanguage::lookup_script_for_class(GDMonoClass *p_class) { - if (!p_class->has_attribute(CACHED_CLASS(ScriptPathAttribute))) { - return; - } - - MonoObject *attr = p_class->get_attribute(CACHED_CLASS(ScriptPathAttribute)); - String path = CACHED_FIELD(ScriptPathAttribute, path)->get_string_value(attr); - - dotnet_script_lookup_map[path] = DotNetScriptLookupInfo( - p_class->get_namespace(), p_class->get_name(), p_class); -} - -void CSharpLanguage::lookup_scripts_in_assembly(GDMonoAssembly *p_assembly) { - if (p_assembly->has_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute))) { - MonoObject *attr = p_assembly->get_attribute(CACHED_CLASS(AssemblyHasScriptsAttribute)); - bool requires_lookup = CACHED_FIELD(AssemblyHasScriptsAttribute, requiresLookup)->get_bool_value(attr); - - if (requires_lookup) { - // This is supported for scenarios where specifying all types would be cumbersome, - // such as when disabling C# source generators (for whatever reason) or when using a - // language other than C# that has nothing similar to source generators to automate it. - MonoImage *image = p_assembly->get_image(); - - int rows = mono_image_get_table_rows(image, MONO_TABLE_TYPEDEF); - - for (int i = 1; i < rows; i++) { - // We don't search inner classes, only top-level. - MonoClass *mono_class = mono_class_get(image, (i + 1) | MONO_TOKEN_TYPE_DEF); - - if (!mono_class_is_assignable_from(CACHED_CLASS_RAW(GodotObject), mono_class)) { - continue; - } - - GDMonoClass *current = p_assembly->get_class(mono_class); - if (current) { - lookup_script_for_class(current); - } - } - } else { - // This is the most likely scenario as we use C# source generators - MonoArray *script_types = (MonoArray *)CACHED_FIELD(AssemblyHasScriptsAttribute, scriptTypes)->get_value(attr); - - int length = mono_array_length(script_types); - - for (int i = 0; i < length; i++) { - MonoReflectionType *reftype = mono_array_get(script_types, MonoReflectionType *, i); - ManagedType type = ManagedType::from_reftype(reftype); - ERR_CONTINUE(!type.type_class); - lookup_script_for_class(type.type_class); - } - } - } -} - void CSharpLanguage::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("cs"); } @@ -1248,22 +1039,6 @@ bool CSharpLanguage::overrides_external_editor() { } #endif -void CSharpLanguage::thread_enter() { -#if 0 - if (gdmono->is_runtime_initialized()) { - GDMonoUtils::attach_current_thread(); - } -#endif -} - -void CSharpLanguage::thread_exit() { -#if 0 - if (gdmono->is_runtime_initialized()) { - GDMonoUtils::detach_current_thread(); - } -#endif -} - bool CSharpLanguage::debug_break_parse(const String &p_file, int p_line, const String &p_error) { // Not a parser error in our case, but it's still used for other type of errors if (EngineDebugger::is_active() && Thread::get_caller_id() == Thread::get_main_id()) { @@ -1289,49 +1064,35 @@ bool CSharpLanguage::debug_break(const String &p_error, bool p_allow_continue) { } } -void CSharpLanguage::_on_scripts_domain_unloaded() { - for (KeyValue<Object *, CSharpScriptBinding> &E : script_bindings) { - CSharpScriptBinding &script_binding = E.value; - script_binding.gchandle.release(); - script_binding.inited = false; - } - +void CSharpLanguage::_on_scripts_domain_about_to_unload() { #ifdef GD_MONO_HOT_RELOAD { MutexLock lock(ManagedCallable::instances_mutex); for (SelfList<ManagedCallable> *elem = ManagedCallable::instances.first(); elem; elem = elem->next()) { ManagedCallable *managed_callable = elem->self(); - managed_callable->delegate_handle.release(); - managed_callable->delegate_invoke = nullptr; + managed_callable->release_delegate_handle(); } } #endif - - dotnet_script_lookup_map.clear(); } #ifdef TOOLS_ENABLED void CSharpLanguage::_editor_init_callback() { - register_editor_internal_calls(); - - // Initialize GodotSharpEditor - - GDMonoClass *editor_klass = GDMono::get_singleton()->get_tools_assembly()->get_class("GodotTools", "GodotSharpEditor"); - CRASH_COND(editor_klass == nullptr); + // Load GodotTools and initialize GodotSharpEditor - MonoObject *mono_object = mono_object_new(mono_domain_get(), editor_klass->get_mono_ptr()); - CRASH_COND(mono_object == nullptr); + int32_t interop_funcs_size = 0; + const void **interop_funcs = godotsharp::get_editor_interop_funcs(interop_funcs_size); - MonoException *exc = nullptr; - GDMonoUtils::runtime_object_init(mono_object, editor_klass, &exc); - UNHANDLED_EXCEPTION(exc); + Object *editor_plugin_obj = GDMono::get_singleton()->get_plugin_callbacks().LoadToolsAssemblyCallback( + GodotSharpDirs::get_data_editor_tools_dir().plus_file("GodotTools.dll").utf16(), + interop_funcs, interop_funcs_size); + CRASH_COND(editor_plugin_obj == nullptr); - EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>( - GDMonoMarshal::mono_object_to_variant(mono_object).operator Object *()); + EditorPlugin *godotsharp_editor = Object::cast_to<EditorPlugin>(editor_plugin_obj); CRASH_COND(godotsharp_editor == nullptr); - // Enable it as a plugin + // Add plugin to EditorNode and enable it EditorNode::add_editor_plugin(godotsharp_editor); ED_SHORTCUT("mono/build_solution", TTR("Build Solution"), KeyModifierMask::ALT | Key::B); godotsharp_editor->enable_plugin(); @@ -1352,24 +1113,24 @@ void CSharpLanguage::release_script_gchandle(MonoGCHandleData &p_gchandle) { } } -void CSharpLanguage::release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle) { - uint32_t pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(p_expected_obj); // We might lock after this, so pin it - - if (!p_gchandle.is_released()) { // Do not lock unnecessarily +void CSharpLanguage::release_script_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, MonoGCHandleData &r_gchandle) { + if (!r_gchandle.is_released() && r_gchandle.get_intptr() == p_gchandle_to_free) { // Do not lock unnecessarily MutexLock lock(get_singleton()->script_gchandle_release_mutex); - - MonoObject *target = p_gchandle.get_target(); - - // We release the gchandle if it points to the MonoObject* we expect (otherwise it was - // already released and could have been replaced) or if we can't get its target MonoObject* - // (which doesn't necessarily mean it was released, and we want it released in order to - // avoid locking other threads unnecessarily). - if (target == p_expected_obj || target == nullptr) { - p_gchandle.release(); + if (!r_gchandle.is_released() && r_gchandle.get_intptr() == p_gchandle_to_free) { + r_gchandle.release(); } } +} - GDMonoUtils::free_gchandle(pinned_gchandle); +void CSharpLanguage::release_binding_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, CSharpScriptBinding &r_script_binding) { + MonoGCHandleData &gchandle = r_script_binding.gchandle; + if (!gchandle.is_released() && gchandle.get_intptr() == p_gchandle_to_free) { // Do not lock unnecessarily + MutexLock lock(get_singleton()->script_gchandle_release_mutex); + if (!gchandle.is_released() && gchandle.get_intptr() == p_gchandle_to_free) { + gchandle.release(); + r_script_binding.inited = false; // Here too, to be thread safe + } + } } CSharpLanguage::CSharpLanguage() { @@ -1401,18 +1162,23 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b ERR_FAIL_NULL_V(classinfo, false); type_name = classinfo->name; - GDMonoClass *type_class = GDMonoUtils::type_get_proxy_class(type_name); + bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), type_name); + ERR_FAIL_COND_V_MSG(!parent_is_object_class, false, + "Type inherits from native type '" + type_name + "', so it can't be instantiated in object of type: '" + p_object->get_class() + "'."); - ERR_FAIL_NULL_V(type_class, false); +#ifdef DEBUG_ENABLED + CRASH_COND(!r_script_binding.gchandle.is_released()); +#endif - MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(type_class, type_name, p_object); + GCHandleIntPtr strong_gchandle = + GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding( + &type_name, p_object); - ERR_FAIL_NULL_V(mono_object, false); + ERR_FAIL_NULL_V(strong_gchandle.value, false); r_script_binding.inited = true; r_script_binding.type_name = type_name; - r_script_binding.wrapper_class = type_class; // cache - r_script_binding.gchandle = MonoGCHandleData::new_strong_handle(mono_object); + r_script_binding.gchandle = MonoGCHandleData(strong_gchandle, gdmono::GCHandleType::STRONG_HANDLE); r_script_binding.owner = p_object; // Tie managed to unmanaged @@ -1455,7 +1221,7 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin if (GDMono::get_singleton() == nullptr) { #ifdef DEBUG_ENABLED - CRASH_COND(!csharp_lang->script_bindings.is_empty()); + CRASH_COND(csharp_lang && !csharp_lang->script_bindings.is_empty()); #endif // Mono runtime finalized, all the gchandle bindings were already released return; @@ -1465,8 +1231,6 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin return; // inside CSharpLanguage::finish(), all the gchandle bindings are released there } - GD_MONO_ASSERT_THREAD_ATTACHED; - { MutexLock lock(csharp_lang->language_bind_mutex); @@ -1477,11 +1241,11 @@ void CSharpLanguage::_instance_binding_free_callback(void *, void *, void *p_bin if (script_binding.inited) { // Set the native instance field to IntPtr.Zero, if not yet garbage collected. // This is done to avoid trying to dispose the native instance from Dispose(bool). - MonoObject *mono_object = script_binding.gchandle.get_target(); - if (mono_object) { - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, nullptr); - } + GDMonoCache::managed_callbacks.ScriptManagerBridge_SetGodotObjectPtr( + script_binding.gchandle.get_intptr(), nullptr); + script_binding.gchandle.release(); + script_binding.inited = false; } csharp_lang->script_bindings.erase(data); @@ -1510,41 +1274,49 @@ GDNativeBool CSharpLanguage::_instance_binding_reference_callback(void *p_token, if (p_reference) { // Refcount incremented if (refcount > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 - GD_MONO_SCOPE_THREAD_ATTACH; - // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. - MonoObject *target = gchandle.get_target(); - if (!target) { + // Release the current weak handle and replace it with a strong handle. + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle = { nullptr }; + bool create_weak = false; + bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType( + old_gchandle, &new_gchandle, create_weak); + + if (!target_alive) { return false; // Called after the managed side was collected, so nothing to do here } - // Release the current weak handle and replace it with a strong handle. - MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(target); - gchandle.release(); - gchandle = strong_gchandle; + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::STRONG_HANDLE); } return false; } else { // Refcount decremented if (refcount == 1 && !gchandle.is_released() && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 - GD_MONO_SCOPE_THREAD_ATTACH; - // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. - MonoObject *target = gchandle.get_target(); - if (!target) { + // Release the current strong handle and replace it with a weak handle. + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle = { nullptr }; + bool create_weak = true; + bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType( + old_gchandle, &new_gchandle, create_weak); + + if (!target_alive) { return refcount == 0; // Called after the managed side was collected, so nothing to do here } - // Release the current strong handle and replace it with a weak handle. - MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(target); - gchandle.release(); - gchandle = weak_gchandle; + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::WEAK_HANDLE); return false; } @@ -1589,214 +1361,160 @@ void CSharpLanguage::set_instance_binding(Object *p_object, void *p_binding) { bool CSharpLanguage::has_instance_binding(Object *p_object) { return p_object->has_instance_binding(get_singleton()); } +void CSharpLanguage::tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, const StringName *p_native_name, bool p_ref_counted) { + // This method should not fail -CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { - CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); - - RefCounted *rc = Object::cast_to<RefCounted>(p_owner); + CRASH_COND(!p_unmanaged); - instance->base_ref_counted = rc != nullptr; - instance->owner = p_owner; - instance->gchandle = p_gchandle; + // All mono objects created from the managed world (e.g.: 'new Player()') + // need to have a CSharpScript in order for their methods to be callable from the unmanaged side - if (instance->base_ref_counted) { - instance->_reference_owner_unsafe(); - } + RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged); - p_script->instances.insert(p_owner); + CRASH_COND(p_ref_counted != (bool)rc); - return instance; -} + MonoGCHandleData gchandle = MonoGCHandleData(p_gchandle_intptr, + p_ref_counted ? gdmono::GCHandleType::WEAK_HANDLE : gdmono::GCHandleType::STRONG_HANDLE); -MonoObject *CSharpInstance::get_mono_object() const { - ERR_FAIL_COND_V(gchandle.is_released(), nullptr); - return gchandle.get_target(); -} + // If it's just a wrapper Godot class and not a custom inheriting class, then attach a + // script binding instead. One of the advantages of this is that if a script is attached + // later and it's not a C# script, then the managed object won't have to be disposed. + // Another reason for doing this is that this instance could outlive CSharpLanguage, which would + // be problematic when using a script. See: https://github.com/godotengine/godot/issues/25621 -Object *CSharpInstance::get_owner() { - return owner; -} - -bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { - ERR_FAIL_COND_V(!script.is_valid(), false); - - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL_V(mono_object, false); + CSharpScriptBinding script_binding; - GDMonoClass *top = script->script_class; + script_binding.inited = true; + script_binding.type_name = *p_native_name; + script_binding.gchandle = gchandle; + script_binding.owner = p_unmanaged; - while (top && top != script->native) { - GDMonoField *field = top->get_field(p_name); + if (p_ref_counted) { + // Unsafe refcount increment. The managed instance also counts as a reference. + // This way if the unmanaged world has no references to our owner + // but the managed instance is alive, the refcount will be 1 instead of 0. + // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) - if (field) { - field->set_value_from_variant(mono_object, p_value); - return true; + // May not me referenced yet, so we must use init_ref() instead of reference() + if (rc->init_ref()) { + CSharpLanguage::get_singleton()->post_unsafe_reference(rc); } + } - GDMonoProperty *property = top->get_property(p_name); - - if (property) { - property->set_value_from_variant(mono_object, p_value); - return true; - } + // The object was just created, no script instance binding should have been attached + CRASH_COND(CSharpLanguage::has_instance_binding(p_unmanaged)); - top = top->get_parent_class(); + void *data; + { + MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex()); + data = (void *)CSharpLanguage::get_singleton()->insert_script_binding(p_unmanaged, script_binding); } - // Call _set + // Should be thread safe because the object was just created and nothing else should be referencing it + CSharpLanguage::set_instance_binding(p_unmanaged, data); +} - top = script->script_class; +void CSharpLanguage::tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted) { + // This method should not fail - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_set), 2); + Ref<CSharpScript> script = *p_script; + // We take care of destructing this reference here, so the managed code won't need to do another P/Invoke call + p_script->~Ref(); - if (method) { - Variant name = p_name; - const Variant *args[2] = { &name, &p_value }; + CRASH_COND(!p_unmanaged); - MonoObject *ret = method->invoke(mono_object, args); + // All mono objects created from the managed world (e.g.: 'new Player()') + // need to have a CSharpScript in order for their methods to be callable from the unmanaged side - if (ret && GDMonoMarshal::unbox<MonoBoolean>(ret)) { - return true; - } + RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged); - break; - } + CRASH_COND(p_ref_counted != (bool)rc); - top = top->get_parent_class(); - } + MonoGCHandleData gchandle = MonoGCHandleData(p_gchandle_intptr, + p_ref_counted ? gdmono::GCHandleType::WEAK_HANDLE : gdmono::GCHandleType::STRONG_HANDLE); - return false; -} + CRASH_COND(script.is_null()); -bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { - ERR_FAIL_COND_V(!script.is_valid(), false); - - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL_V(mono_object, false); + CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(p_unmanaged, script.ptr(), gchandle); - GDMonoClass *top = script->script_class; + p_unmanaged->set_script_and_instance(script, csharp_instance); - while (top && top != script->native) { - GDMonoField *field = top->get_field(p_name); + csharp_instance->connect_event_signals(); +} - if (field) { - MonoObject *value = field->get_value(mono_object); - r_ret = GDMonoMarshal::mono_object_to_variant(value); - return true; - } +void CSharpLanguage::tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged) { + // This method should not fail - GDMonoProperty *property = top->get_property(p_name); + CRASH_COND(!p_unmanaged); - if (property) { - MonoException *exc = nullptr; - MonoObject *value = property->get_value(mono_object, &exc); - if (exc) { - r_ret = Variant(); - GDMonoUtils::set_pending_exception(exc); - } else { - r_ret = GDMonoMarshal::mono_object_to_variant(value); - } - return true; - } + CSharpInstance *instance = CAST_CSHARP_INSTANCE(p_unmanaged->get_script_instance()); - top = top->get_parent_class(); + if (!instance) { + // Native bindings don't need post-setup + return; } - // Call _get + CRASH_COND(!instance->gchandle.is_released()); - top = script->script_class; - - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get), 1); - - if (method) { - Variant name = p_name; - const Variant *args[1] = { &name }; - - MonoObject *ret = method->invoke(mono_object, args); - - if (ret) { - r_ret = GDMonoMarshal::mono_object_to_variant(ret); - return true; - } + // Tie managed to unmanaged + instance->gchandle = MonoGCHandleData(p_gchandle_intptr, gdmono::GCHandleType::STRONG_HANDLE); - break; - } + if (instance->base_ref_counted) { + instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) + } - top = top->get_parent_class(); + { + MutexLock lock(CSharpLanguage::get_singleton()->get_script_instances_mutex()); + // instances is a set, so it's safe to insert multiple times (e.g.: from _internal_new_managed) + instance->script->instances.insert(instance->owner); } - return false; + instance->connect_event_signals(); } -void CSharpInstance::get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state) { - List<PropertyInfo> property_list; - get_property_list(&property_list); - - for (const PropertyInfo &prop_info : property_list) { - Pair<StringName, Variant> state_pair; - state_pair.first = prop_info.name; +CSharpInstance *CSharpInstance::create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle) { + CSharpInstance *instance = memnew(CSharpInstance(Ref<CSharpScript>(p_script))); - ManagedType managedType; + RefCounted *rc = Object::cast_to<RefCounted>(p_owner); - GDMonoField *field = nullptr; - GDMonoClass *top = script->script_class; - while (top && top != script->native) { - field = top->get_field(state_pair.first); - if (field) { - break; - } + instance->base_ref_counted = rc != nullptr; + instance->owner = p_owner; + instance->gchandle = p_gchandle; - top = top->get_parent_class(); - } - if (!field) { - continue; // Properties ignored. We get the property baking fields instead. - } + if (instance->base_ref_counted) { + instance->_reference_owner_unsafe(); + } - managedType = field->get_type(); + p_script->instances.insert(p_owner); - if (GDMonoMarshal::managed_to_variant_type(managedType) != Variant::NIL) { // If we can marshal it - if (get(state_pair.first, state_pair.second)) { - r_state.push_back(state_pair); - } - } - } + return instance; } -void CSharpInstance::get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state) { - MonoObject *owner_managed = get_mono_object(); - ERR_FAIL_NULL(owner_managed); +Object *CSharpInstance::get_owner() { + return owner; +} - for (const KeyValue<StringName, CSharpScript::EventSignal> &E : script->event_signals) { - const CSharpScript::EventSignal &event_signal = E.value; +bool CSharpInstance::set(const StringName &p_name, const Variant &p_value) { + ERR_FAIL_COND_V(!script.is_valid(), false); - MonoDelegate *delegate_field_value = (MonoDelegate *)event_signal.field->get_value(owner_managed); - if (!delegate_field_value) { - continue; // Empty - } + return GDMonoCache::managed_callbacks.CSharpInstanceBridge_Set( + gchandle.get_intptr(), &p_name, &p_value); +} - Array serialized_data; - MonoObject *managed_serialized_data = GDMonoMarshal::variant_to_mono_object(serialized_data); +bool CSharpInstance::get(const StringName &p_name, Variant &r_ret) const { + ERR_FAIL_COND_V(!script.is_valid(), false); - MonoException *exc = nullptr; - bool success = (bool)CACHED_METHOD_THUNK(DelegateUtils, TrySerializeDelegate).invoke(delegate_field_value, managed_serialized_data, &exc); + Variant ret_value; - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - continue; - } + bool ret = GDMonoCache::managed_callbacks.CSharpInstanceBridge_Get( + gchandle.get_intptr(), &p_name, &ret_value); - if (success) { - r_state.push_back(Pair<StringName, Array>(event_signal.field->get_name(), serialized_data)); - } else if (OS::get_singleton()->is_stdout_verbose()) { - OS::get_singleton()->print("Failed to serialize event signal delegate\n"); - } + if (ret) { + r_ret = ret_value; + return true; } + + return false; } void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { @@ -1807,30 +1525,25 @@ void CSharpInstance::get_property_list(List<PropertyInfo> *p_properties) const { ERR_FAIL_COND(!script.is_valid()); - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL(mono_object); - - GDMonoClass *top = script->script_class; + StringName method = SNAME("_get_property_list"); - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(CACHED_STRING_NAME(_get_property_list), 0); + Variant ret; + Callable::CallError call_error; + bool ok = GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &method, nullptr, 0, &call_error, &ret); - if (method) { - MonoObject *ret = method->invoke(mono_object); - - if (ret) { - Array array = Array(GDMonoMarshal::mono_object_to_variant(ret)); - for (int i = 0, size = array.size(); i < size; i++) { - props.push_back(PropertyInfo::from_dict(array.get(i))); - } + // CALL_ERROR_INVALID_METHOD would simply mean it was not overridden + if (call_error.error != Callable::CallError::CALL_ERROR_INVALID_METHOD) { + if (call_error.error != Callable::CallError::CALL_OK) { + ERR_PRINT("Error calling '_get_property_list': " + Variant::get_call_error_text(method, nullptr, 0, call_error)); + } else if (!ok) { + ERR_PRINT("Unexpected error calling '_get_property_list'"); + } else { + Array array = ret; + for (int i = 0, size = array.size(); i < size; i++) { + p_properties->push_back(PropertyInfo::from_dict(array.get(i))); } - - break; } - - top = top->get_parent_class(); } for (const PropertyInfo &prop : props) { @@ -1853,84 +1566,79 @@ Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool * return Variant::NIL; } -void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { - if (!script->is_valid() || !script->script_class) { - return; - } - - GD_MONO_SCOPE_THREAD_ATTACH; - - // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. - GDMonoClass *top = script->script_class; +bool CSharpInstance::property_can_revert(const StringName &p_name) const { + ERR_FAIL_COND_V(!script.is_valid(), false); - while (top && top != script->native) { - const Vector<GDMonoMethod *> &methods = top->get_all_methods(); - for (int i = 0; i < methods.size(); ++i) { - MethodInfo minfo = methods[i]->get_method_info(); - if (minfo.name != CACHED_STRING_NAME(dotctor)) { - p_list->push_back(minfo); - } - } + Variant name_arg = p_name; + const Variant *args[1] = { &name_arg }; - top = top->get_parent_class(); - } -} + Variant ret; + Callable::CallError call_error; + GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &CACHED_STRING_NAME(_property_can_revert), args, 1, &call_error, &ret); -bool CSharpInstance::has_method(const StringName &p_method) const { - if (!script.is_valid()) { + if (call_error.error != Callable::CallError::CALL_OK) { return false; } - GD_MONO_SCOPE_THREAD_ATTACH; + return (bool)ret; +} - GDMonoClass *top = script->script_class; +bool CSharpInstance::property_get_revert(const StringName &p_name, Variant &r_ret) const { + ERR_FAIL_COND_V(!script.is_valid(), false); - while (top && top != script->native) { - if (top->has_fetched_method_unknown_params(p_method)) { - return true; - } + Variant name_arg = p_name; + const Variant *args[1] = { &name_arg }; + + Variant ret; + Callable::CallError call_error; + GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &CACHED_STRING_NAME(_property_get_revert), args, 1, &call_error, &ret); - top = top->get_parent_class(); + if (call_error.error != Callable::CallError::CALL_OK) { + return false; } - return false; + r_ret = ret; + return true; } -Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - ERR_FAIL_COND_V(!script.is_valid(), Variant()); - - GD_MONO_SCOPE_THREAD_ATTACH; - - MonoObject *mono_object = get_mono_object(); - - if (!mono_object) { - r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_FAIL_V(Variant()); +void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { + if (!script->is_valid() || !script->valid) { + return; } - GDMonoClass *top = script->script_class; + const CSharpScript *top = script.ptr(); + while (top != nullptr) { + for (const CSharpScript::CSharpMethodInfo &E : top->methods) { + p_list->push_back(E.method_info); + } - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(p_method, p_argcount); + top = top->base_script.ptr(); + } +} - if (method) { - MonoObject *return_value = method->invoke(mono_object, p_args); +bool CSharpInstance::has_method(const StringName &p_method) const { + if (!script.is_valid()) { + return false; + } - r_error.error = Callable::CallError::CALL_OK; + if (!GDMonoCache::godot_api_cache_updated) { + return false; + } - if (return_value) { - return GDMonoMarshal::mono_object_to_variant(return_value); - } else { - return Variant(); - } - } + return GDMonoCache::managed_callbacks.CSharpInstanceBridge_HasMethodUnknownParams( + gchandle.get_intptr(), &p_method); +} - top = top->get_parent_class(); - } +Variant CSharpInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + ERR_FAIL_COND_V(!script.is_valid(), Variant()); - r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; + Variant ret; + GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &p_method, p_args, p_argcount, &r_error, &ret); - return Variant(); + return ret; } bool CSharpInstance::_reference_owner_unsafe() { @@ -1976,48 +1684,29 @@ bool CSharpInstance::_unreference_owner_unsafe() { return static_cast<RefCounted *>(owner)->unreference(); } -MonoObject *CSharpInstance::_internal_new_managed() { - // Search the constructor first, to fail with an error if it's not found before allocating anything else. - GDMonoMethod *ctor = script->script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - ERR_FAIL_NULL_V_MSG(ctor, nullptr, - "Cannot create script instance because the class does not define a parameterless constructor: '" + script->get_path() + "'."); - +bool CSharpInstance::_internal_new_managed() { CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); - ERR_FAIL_NULL_V(owner, nullptr); - ERR_FAIL_COND_V(script.is_null(), nullptr); + ERR_FAIL_NULL_V(owner, false); + ERR_FAIL_COND_V(script.is_null(), false); - MonoObject *mono_object = mono_object_new(mono_domain_get(), script->script_class->get_mono_ptr()); + bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance( + script.ptr(), owner, nullptr, 0); - if (!mono_object) { + if (!ok) { // Important to clear this before destroying the script instance here script = Ref<CSharpScript>(); - - bool die = _unreference_owner_unsafe(); - // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. - CRASH_COND(die); - owner = nullptr; - ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); - } - - // Tie managed to unmanaged - gchandle = MonoGCHandleData::new_strong_handle(mono_object); - - if (base_ref_counted) { - _reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) + return false; } - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, owner); - - // Construct - ctor->invoke_raw(mono_object, nullptr); + CRASH_COND(gchandle.is_released()); - return mono_object; + return true; } -void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { +void CSharpInstance::mono_object_disposed(GCHandleIntPtr p_gchandle_to_free) { // Must make sure event signals are not left dangling disconnect_event_signals(); @@ -2025,10 +1714,10 @@ void CSharpInstance::mono_object_disposed(MonoObject *p_obj) { CRASH_COND(base_ref_counted); CRASH_COND(gchandle.is_released()); #endif - CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); + CSharpLanguage::get_singleton()->release_script_gchandle_thread_safe(p_gchandle_to_free, gchandle); } -void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) { +void CSharpInstance::mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_free, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance) { #ifdef DEBUG_ENABLED CRASH_COND(!base_ref_counted); CRASH_COND(gchandle.is_released()); @@ -2044,20 +1733,20 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f r_delete_owner = true; } else { r_delete_owner = false; - CSharpLanguage::get_singleton()->release_script_gchandle(p_obj, gchandle); + CSharpLanguage::get_singleton()->release_script_gchandle_thread_safe(p_gchandle_to_free, gchandle); if (!p_is_finalizer) { // If the native instance is still alive and Dispose() was called // (instead of the finalizer), then we remove the script instance. r_remove_script_instance = true; + // TODO: Last usage of 'is_finalizing_scripts_domain'. It should be replaced with a check to determine if the load context is being unloaded. } else if (!GDMono::get_singleton()->is_finalizing_scripts_domain()) { // If the native instance is still alive and this is called from the finalizer, // then it was referenced from another thread before the finalizer could // unreference and delete it, so we want to keep it. // GC.ReRegisterForFinalize(this) is not safe because the objects referenced by 'this' // could have already been collected. Instead we will create a new managed instance here. - MonoObject *new_managed = _internal_new_managed(); - if (!new_managed) { + if (!_internal_new_managed()) { r_remove_script_instance = true; } } @@ -2065,17 +1754,20 @@ void CSharpInstance::mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_f } void CSharpInstance::connect_event_signals() { - for (const KeyValue<StringName, CSharpScript::EventSignal> &E : script->event_signals) { - const CSharpScript::EventSignal &event_signal = E.value; + CSharpScript *top = script.ptr(); + while (top != nullptr) { + for (CSharpScript::EventSignalInfo &signal : top->get_script_event_signals()) { + String signal_name = signal.name; - StringName signal_name = event_signal.field->get_name(); + // TODO: Use pooling for ManagedCallable instances. + EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, signal_name)); - // TODO: Use pooling for ManagedCallable instances. - EventSignalCallable *event_signal_callable = memnew(EventSignalCallable(owner, &event_signal)); + Callable callable(event_signal_callable); + connected_event_signals.push_back(callable); + owner->connect(signal_name, callable); + } - Callable callable(event_signal_callable); - connected_event_signals.push_back(callable); - owner->connect(signal_name, callable); + top = top->base_script.ptr(); } } @@ -2097,16 +1789,25 @@ void CSharpInstance::refcount_incremented() { RefCounted *rc_owner = Object::cast_to<RefCounted>(owner); if (rc_owner->reference_get_count() > 1 && gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 - GD_MONO_SCOPE_THREAD_ATTACH; - // The reference count was increased after the managed side was the only one referencing our owner. // This means the owner is being referenced again by the unmanaged side, // so the owner must hold the managed side alive again to avoid it from being GCed. // Release the current weak handle and replace it with a strong handle. - MonoGCHandleData strong_gchandle = MonoGCHandleData::new_strong_handle(gchandle.get_target()); - gchandle.release(); - gchandle = strong_gchandle; + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle = { nullptr }; + bool create_weak = false; + bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType( + old_gchandle, &new_gchandle, create_weak); + + if (!target_alive) { + return; // Called after the managed side was collected, so nothing to do here + } + + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::STRONG_HANDLE); } } @@ -2121,15 +1822,24 @@ bool CSharpInstance::refcount_decremented() { int refcount = rc_owner->reference_get_count(); if (refcount == 1 && !gchandle.is_weak()) { // The managed side also holds a reference, hence 1 instead of 0 - GD_MONO_SCOPE_THREAD_ATTACH; - // If owner owner is no longer referenced by the unmanaged side, // the managed instance takes responsibility of deleting the owner when GCed. // Release the current strong handle and replace it with a weak handle. - MonoGCHandleData weak_gchandle = MonoGCHandleData::new_weak_handle(gchandle.get_target()); - gchandle.release(); - gchandle = weak_gchandle; + + GCHandleIntPtr old_gchandle = gchandle.get_intptr(); + gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function) + + GCHandleIntPtr new_gchandle = { nullptr }; + bool create_weak = true; + bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType( + old_gchandle, &new_gchandle, create_weak); + + if (!target_alive) { + return refcount == 0; // Called after the managed side was collected, so nothing to do here + } + + gchandle = MonoGCHandleData(new_gchandle, gdmono::GCHandleType::WEAK_HANDLE); return false; } @@ -2144,8 +1854,6 @@ const Variant CSharpInstance::get_rpc_config() const { } void CSharpInstance::notification(int p_notification) { - GD_MONO_SCOPE_THREAD_ATTACH; - if (p_notification == Object::NOTIFICATION_PREDELETE) { // When NOTIFICATION_PREDELETE is sent, we also take the chance to call Dispose(). // It's safe to call Dispose() multiple times and NOTIFICATION_PREDELETE is guaranteed @@ -2165,15 +1873,8 @@ void CSharpInstance::notification(int p_notification) { _call_notification(p_notification); - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL(mono_object); - - MonoException *exc = nullptr; - GDMonoUtils::dispose(mono_object, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - } + GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( + gchandle.get_intptr(), /* okIfNull */ false); return; } @@ -2182,62 +1883,29 @@ void CSharpInstance::notification(int p_notification) { } void CSharpInstance::_call_notification(int p_notification) { - GD_MONO_ASSERT_THREAD_ATTACHED; - - MonoObject *mono_object = get_mono_object(); - ERR_FAIL_NULL(mono_object); - - // Custom version of _call_multilevel, optimized for _notification + Variant arg = p_notification; + const Variant *args[1] = { &arg }; + StringName method_name = SNAME("_notification"); - int32_t arg = p_notification; - void *args[1] = { &arg }; - StringName method_name = CACHED_STRING_NAME(_notification); + Callable::CallError call_error; - GDMonoClass *top = script->script_class; - - while (top && top != script->native) { - GDMonoMethod *method = top->get_method(method_name, 1); - - if (method) { - method->invoke_raw(mono_object, args); - return; - } - - top = top->get_parent_class(); - } + Variant ret; + GDMonoCache::managed_callbacks.CSharpInstanceBridge_Call( + gchandle.get_intptr(), &method_name, args, 1, &call_error, &ret); } String CSharpInstance::to_string(bool *r_valid) { - GD_MONO_SCOPE_THREAD_ATTACH; + String res; + bool valid; - MonoObject *mono_object = get_mono_object(); + GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallToString( + gchandle.get_intptr(), &res, &valid); - if (mono_object == nullptr) { - if (r_valid) { - *r_valid = false; - } - return String(); - } - - MonoException *exc = nullptr; - MonoString *result = GDMonoUtils::object_to_string(mono_object, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - if (r_valid) { - *r_valid = false; - } - return String(); - } - - if (result == nullptr) { - if (r_valid) { - *r_valid = false; - } - return String(); + if (r_valid) { + *r_valid = valid; } - return GDMonoMarshal::mono_string_to_godot(result); + return res; } Ref<Script> CSharpInstance::get_script() const { @@ -2253,8 +1921,6 @@ CSharpInstance::CSharpInstance(const Ref<CSharpScript> &p_script) : } CSharpInstance::~CSharpInstance() { - GD_MONO_SCOPE_THREAD_ATTACH; - destructing_script_instance = true; // Must make sure event signals are not left dangling @@ -2268,16 +1934,8 @@ CSharpInstance::~CSharpInstance() { // we must call Dispose here, because Dispose calls owner->set_script_instance(nullptr) // and that would mess up with the new script instance if called later. - MonoObject *mono_object = gchandle.get_target(); - - if (mono_object) { - MonoException *exc = nullptr; - GDMonoUtils::dispose(mono_object, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - } - } + GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( + gchandle.get_intptr(), /* okIfNull */ true); } gchandle.release(); // Make sure the gchandle is released @@ -2341,63 +1999,8 @@ void CSharpScript::_update_exports_values(HashMap<StringName, Variant> &values, propnames.push_back(prop_info); } - if (base_cache.is_valid()) { - base_cache->_update_exports_values(values, propnames); - } -} - -void CSharpScript::_update_member_info_no_exports() { - if (exports_invalidated) { - GD_MONO_ASSERT_THREAD_ATTACHED; - - exports_invalidated = false; - - member_info.clear(); - - GDMonoClass *top = script_class; - List<PropertyInfo> props; - - while (top && top != native) { - PropertyInfo prop_info; - bool exported; - - const Vector<GDMonoField *> &fields = top->get_all_fields(); - - for (int i = fields.size() - 1; i >= 0; i--) { - GDMonoField *field = fields[i]; - - if (_get_member_export(field, /* inspect export: */ false, prop_info, exported)) { - StringName member_name = field->get_name(); - - member_info[member_name] = prop_info; - props.push_front(prop_info); - exported_members_defval_cache[member_name] = Variant(); - } - } - - const Vector<GDMonoProperty *> &properties = top->get_all_properties(); - - for (int i = properties.size() - 1; i >= 0; i--) { - GDMonoProperty *property = properties[i]; - - if (_get_member_export(property, /* inspect export: */ false, prop_info, exported)) { - StringName member_name = property->get_name(); - - member_info[member_name] = prop_info; - props.push_front(prop_info); - exported_members_defval_cache[member_name] = Variant(); - } - } - - exported_members_cache.push_back(PropertyInfo(Variant::NIL, top->get_name(), PROPERTY_HINT_NONE, get_path(), PROPERTY_USAGE_CATEGORY)); - for (const PropertyInfo &E : props) { - exported_members_cache.push_back(E); - } - - props.clear(); - - top = top->get_parent_class(); - } + if (base_script.is_valid()) { + base_script->_update_exports_values(values, propnames); } } #endif @@ -2419,166 +2022,65 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda if (exports_invalidated) #endif { - GD_MONO_SCOPE_THREAD_ATTACH; +#ifdef TOOLS_ENABLED + exports_invalidated = false; +#endif changed = true; member_info.clear(); #ifdef TOOLS_ENABLED - MonoObject *tmp_object = nullptr; - Object *tmp_native = nullptr; - uint32_t tmp_pinned_gchandle = 0; - - if (is_editor) { - exports_invalidated = false; - - exported_members_cache.clear(); - exported_members_defval_cache.clear(); - - // Here we create a temporary managed instance of the class to get the initial values - tmp_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); - - if (!tmp_object) { - ERR_PRINT("Failed to allocate temporary MonoObject."); - return false; - } - - tmp_pinned_gchandle = GDMonoUtils::new_strong_gchandle_pinned(tmp_object); // pin it (not sure if needed) - - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), 0); - - ERR_FAIL_NULL_V_MSG(ctor, false, - "Cannot construct temporary MonoObject because the class does not define a parameterless constructor: '" + get_path() + "'."); - - MonoException *ctor_exc = nullptr; - ctor->invoke(tmp_object, nullptr, &ctor_exc); - - tmp_native = GDMonoMarshal::unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(tmp_object)); - - if (ctor_exc) { - // TODO: Should we free 'tmp_native' if the exception was thrown after its creation? - - GDMonoUtils::free_gchandle(tmp_pinned_gchandle); - tmp_object = nullptr; - - ERR_PRINT("Exception thrown from constructor of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(ctor_exc); - return false; - } - } + exported_members_cache.clear(); + exported_members_defval_cache.clear(); #endif - GDMonoClass *top = script_class; - List<PropertyInfo> props; - - while (top && top != native) { - PropertyInfo prop_info; - bool exported; - - const Vector<GDMonoField *> &fields = top->get_all_fields(); - - for (int i = fields.size() - 1; i >= 0; i--) { - GDMonoField *field = fields[i]; - - if (_get_member_export(field, /* inspect export: */ true, prop_info, exported)) { - StringName member_name = field->get_name(); - - member_info[member_name] = prop_info; - - if (exported) { + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyInfoList(this, + [](CSharpScript *p_script, const String *p_current_class_name, GDMonoCache::godotsharp_property_info *p_props, int32_t p_count) { #ifdef TOOLS_ENABLED - if (is_editor) { - props.push_front(prop_info); - - if (tmp_object) { - exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object)); - } - } + p_script->exported_members_cache.push_back(PropertyInfo( + Variant::NIL, *p_current_class_name, PROPERTY_HINT_NONE, + p_script->get_path(), PROPERTY_USAGE_CATEGORY)); #endif -#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED) - exported_members_names.insert(member_name); -#endif - } - } - } + for (int i = 0; i < p_count; i++) { + const GDMonoCache::godotsharp_property_info &prop = p_props[i]; - const Vector<GDMonoProperty *> &properties = top->get_all_properties(); + StringName name = *reinterpret_cast<const StringName *>(&prop.name); + String hint_string = *reinterpret_cast<const String *>(&prop.hint_string); - for (int i = properties.size() - 1; i >= 0; i--) { - GDMonoProperty *property = properties[i]; + PropertyInfo pinfo(prop.type, name, prop.hint, hint_string, prop.usage); - if (_get_member_export(property, /* inspect export: */ true, prop_info, exported)) { - StringName member_name = property->get_name(); + p_script->member_info[name] = pinfo; - member_info[member_name] = prop_info; + if (prop.exported) { - if (exported) { #ifdef TOOLS_ENABLED - if (is_editor) { - props.push_front(prop_info); - if (tmp_object) { - MonoException *exc = nullptr; - MonoObject *ret = property->get_value(tmp_object, &exc); - if (exc) { - exported_members_defval_cache[member_name] = Variant(); - GDMonoUtils::debug_print_unhandled_exception(exc); - } else { - exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(ret); - } - } - } + p_script->exported_members_cache.push_back(pinfo); #endif #if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED) - exported_members_names.insert(member_name); + p_script->exported_members_names.insert(name); #endif - } - } - } - -#ifdef TOOLS_ENABLED - exported_members_cache.push_back(PropertyInfo(Variant::NIL, top->get_name(), PROPERTY_HINT_NONE, get_path(), PROPERTY_USAGE_CATEGORY)); - - for (const PropertyInfo &E : props) { - exported_members_cache.push_back(E); - } - - props.clear(); -#endif // TOOLS_ENABLED - - top = top->get_parent_class(); - } + } + } + }); #ifdef TOOLS_ENABLED - if (is_editor) { - // Need to check this here, before disposal - bool base_ref_counted = Object::cast_to<RefCounted>(tmp_native) != nullptr; - - // Dispose the temporary managed instance + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetPropertyDefaultValues(this, + [](CSharpScript *p_script, GDMonoCache::godotsharp_property_def_val_pair *p_def_vals, int32_t p_count) { + for (int i = 0; i < p_count; i++) { + const GDMonoCache::godotsharp_property_def_val_pair &def_val_pair = p_def_vals[i]; - MonoException *exc = nullptr; - GDMonoUtils::dispose(tmp_object, &exc); + StringName name = *reinterpret_cast<const StringName *>(&def_val_pair.name); + Variant value = *reinterpret_cast<const Variant *>(&def_val_pair.value); - if (exc) { - ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:"); - GDMonoUtils::debug_print_unhandled_exception(exc); - } - - GDMonoUtils::free_gchandle(tmp_pinned_gchandle); - tmp_object = nullptr; - - if (tmp_native && !base_ref_counted) { - Node *node = Object::cast_to<Node>(tmp_native); - if (node && node->is_inside_tree()) { - ERR_PRINT("Temporary instance was added to the scene tree."); - } else { - memdelete(tmp_native); - } - } - } + p_script->exported_members_defval_cache[name] = value; + } + }); #endif + } } #ifdef TOOLS_ENABLED @@ -2605,374 +2107,6 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda return changed; } -void CSharpScript::load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class) { - // no need to load the script's signals more than once - if (!signals_invalidated) { - return; - } - - // make sure this classes signals are empty when loading for the first time - _signals.clear(); - event_signals.clear(); - - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = p_class; - while (top && top != p_native_class) { - const Vector<GDMonoClass *> &delegates = top->get_all_delegates(); - for (int i = delegates.size() - 1; i >= 0; --i) { - GDMonoClass *delegate = delegates[i]; - - if (!delegate->has_attribute(CACHED_CLASS(SignalAttribute))) { - continue; - } - - // Arguments are accessibles as arguments of .Invoke method - GDMonoMethod *invoke_method = delegate->get_method(mono_get_delegate_invoke(delegate->get_mono_ptr())); - - Vector<SignalParameter> parameters; - if (_get_signal(top, invoke_method, parameters)) { - _signals[delegate->get_name()] = parameters; - } - } - - List<StringName> found_event_signals; - - void *iter = nullptr; - MonoEvent *raw_event = nullptr; - while ((raw_event = mono_class_get_events(top->get_mono_ptr(), &iter)) != nullptr) { - MonoCustomAttrInfo *event_attrs = mono_custom_attrs_from_event(top->get_mono_ptr(), raw_event); - if (event_attrs) { - if (mono_custom_attrs_has_attr(event_attrs, CACHED_CLASS(SignalAttribute)->get_mono_ptr())) { - String event_name = String::utf8(mono_event_get_name(raw_event)); - found_event_signals.push_back(StringName(event_name)); - } - - mono_custom_attrs_free(event_attrs); - } - } - - const Vector<GDMonoField *> &fields = top->get_all_fields(); - for (int i = 0; i < fields.size(); i++) { - GDMonoField *field = fields[i]; - - GDMonoClass *field_class = field->get_type().type_class; - - if (!mono_class_is_delegate(field_class->get_mono_ptr())) { - continue; - } - - if (!found_event_signals.find(field->get_name())) { - continue; - } - - GDMonoMethod *invoke_method = field_class->get_method(mono_get_delegate_invoke(field_class->get_mono_ptr())); - - Vector<SignalParameter> parameters; - if (_get_signal(top, invoke_method, parameters)) { - event_signals[field->get_name()] = { field, invoke_method, parameters }; - } - } - - top = top->get_parent_class(); - } - - signals_invalidated = false; -} - -bool CSharpScript::_get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> ¶ms) { - GD_MONO_ASSERT_THREAD_ATTACHED; - - Vector<StringName> names; - Vector<ManagedType> types; - p_delegate_invoke->get_parameter_names(names); - p_delegate_invoke->get_parameter_types(types); - - for (int i = 0; i < names.size(); ++i) { - SignalParameter arg; - arg.name = names[i]; - - bool nil_is_variant = false; - arg.type = GDMonoMarshal::managed_to_variant_type(types[i], &nil_is_variant); - - if (arg.type == Variant::NIL) { - if (nil_is_variant) { - arg.nil_is_variant = true; - } else { - ERR_PRINT("Unknown type of signal parameter: '" + arg.name + "' in '" + p_class->get_full_name() + "'."); - return false; - } - } - - params.push_back(arg); - } - - return true; -} - -/** - * Returns false if there was an error, otherwise true. - * If there was an error, r_prop_info and r_exported are not assigned any value. - */ -bool CSharpScript::_get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported) { - GD_MONO_ASSERT_THREAD_ATTACHED; - - // Goddammit, C++. All I wanted was some nested functions. -#define MEMBER_FULL_QUALIFIED_NAME(m_member) \ - (m_member->get_enclosing_class()->get_full_name() + "." + (String)m_member->get_name()) - - if (p_member->is_static()) { -#ifdef TOOLS_ENABLED - if (p_member->has_attribute(CACHED_CLASS(ExportAttribute))) { - ERR_PRINT("Cannot export member because it is static: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); - } -#endif - return false; - } - - if (member_info.has(p_member->get_name())) { - return false; - } - - ManagedType type; - - if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_FIELD) { - type = static_cast<GDMonoField *>(p_member)->get_type(); - } else if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { - type = static_cast<GDMonoProperty *>(p_member)->get_type(); - } else { - CRASH_NOW(); - } - - bool exported = p_member->has_attribute(CACHED_CLASS(ExportAttribute)); - - if (p_member->get_member_type() == IMonoClassMember::MEMBER_TYPE_PROPERTY) { - GDMonoProperty *property = static_cast<GDMonoProperty *>(p_member); - if (!property->has_getter()) { -#ifdef TOOLS_ENABLED - if (exported) { - ERR_PRINT("Cannot export a property without a getter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); - } -#endif - return false; - } - if (!property->has_setter()) { -#ifdef TOOLS_ENABLED - if (exported) { - ERR_PRINT("Cannot export a property without a setter: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); - } -#endif - return false; - } - } - - bool nil_is_variant = false; - Variant::Type variant_type = GDMonoMarshal::managed_to_variant_type(type, &nil_is_variant); - - if (!p_inspect_export || !exported) { - r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_SCRIPT_VARIABLE); - r_exported = false; - return true; - } - -#ifdef TOOLS_ENABLED - MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); -#endif - - PropertyHint hint = PROPERTY_HINT_NONE; - String hint_string; - - if (variant_type == Variant::NIL && !nil_is_variant) { -#ifdef TOOLS_ENABLED - ERR_PRINT("Unknown exported member type: '" + MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); -#endif - return false; - } - -#ifdef TOOLS_ENABLED - int hint_res = _try_get_member_export_hint(p_member, type, variant_type, /* allow_generics: */ true, hint, hint_string); - - ERR_FAIL_COND_V_MSG(hint_res == -1, false, - "Error while trying to determine information about the exported member: '" + - MEMBER_FULL_QUALIFIED_NAME(p_member) + "'."); - - if (hint_res == 0) { - hint = PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)); - hint_string = CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); - } -#endif - - uint32_t prop_usage = PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE; - - if (variant_type == Variant::NIL) { - // System.Object (Variant) - prop_usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } - - r_prop_info = PropertyInfo(variant_type, (String)p_member->get_name(), hint, hint_string, prop_usage); - r_exported = true; - - return true; - -#undef MEMBER_FULL_QUALIFIED_NAME -} - -#ifdef TOOLS_ENABLED -int CSharpScript::_try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string) { - if (p_variant_type == Variant::NIL) { - // System.Object (Variant) - return 1; - } - - GD_MONO_ASSERT_THREAD_ATTACHED; - - if (p_variant_type == Variant::INT && p_type.type_encoding == MONO_TYPE_VALUETYPE && mono_class_is_enum(p_type.type_class->get_mono_ptr())) { - MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); - r_hint = GDMonoUtils::Marshal::type_has_flags_attribute(reftype) ? PROPERTY_HINT_FLAGS : PROPERTY_HINT_ENUM; - - Vector<MonoClassField *> fields = p_type.type_class->get_enum_fields(); - - MonoType *enum_basetype = mono_class_enum_basetype(p_type.type_class->get_mono_ptr()); - - String name_only_hint_string; - - // True: enum Foo { Bar, Baz, Quux } - // True: enum Foo { Bar = 0, Baz = 1, Quux = 2 } - // False: enum Foo { Bar = 0, Baz = 7, Quux = 5 } - bool uses_default_values = true; - - for (int i = 0; i < fields.size(); i++) { - MonoClassField *field = fields[i]; - - if (i > 0) { - r_hint_string += ","; - name_only_hint_string += ","; - } - - String enum_field_name = String::utf8(mono_field_get_name(field)); - r_hint_string += enum_field_name; - name_only_hint_string += enum_field_name; - - // TODO: - // Instead of using mono_field_get_value_object, we can do this without boxing. Check the - // internal mono functions: ves_icall_System_Enum_GetEnumValuesAndNames and the get_enum_field. - - MonoObject *val_obj = mono_field_get_value_object(mono_domain_get(), field, nullptr); - - ERR_FAIL_NULL_V_MSG(val_obj, -1, "Failed to get '" + enum_field_name + "' constant enum value."); - - bool r_error; - uint64_t val = GDMonoUtils::unbox_enum_value(val_obj, enum_basetype, r_error); - ERR_FAIL_COND_V_MSG(r_error, -1, "Failed to unbox '" + enum_field_name + "' constant enum value."); - - unsigned int expected_val = r_hint == PROPERTY_HINT_FLAGS ? 1 << i : i; - if (val != expected_val) { - uses_default_values = false; - } - - r_hint_string += ":"; - r_hint_string += String::num_uint64(val); - } - - if (uses_default_values) { - // If we use the format NAME:VAL, that's what the editor displays. - // That's annoying if the user is not using custom values for the enum constants. - // This may not be needed in the future if the editor is changed to not display values. - r_hint_string = name_only_hint_string; - } - } else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(GodotResource)->is_assignable_from(p_type.type_class)) { - GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class); - CRASH_COND(field_native_class == nullptr); - - r_hint = PROPERTY_HINT_RESOURCE_TYPE; - r_hint_string = String(NATIVE_GDMONOCLASS_NAME(field_native_class)); - } else if (p_variant_type == Variant::OBJECT && CACHED_CLASS(Node)->is_assignable_from(p_type.type_class)) { - GDMonoClass *field_native_class = GDMonoUtils::get_class_native_base(p_type.type_class); - CRASH_COND(field_native_class == nullptr); - - r_hint = PROPERTY_HINT_NODE_TYPE; - r_hint_string = String(NATIVE_GDMONOCLASS_NAME(field_native_class)); - } else if (p_allow_generics && p_variant_type == Variant::ARRAY) { - // Nested arrays are not supported in the inspector - - ManagedType elem_type; - - if (!GDMonoMarshal::try_get_array_element_type(p_type, elem_type)) { - return 0; - } - - Variant::Type elem_variant_type = GDMonoMarshal::managed_to_variant_type(elem_type); - - PropertyHint elem_hint = PROPERTY_HINT_NONE; - String elem_hint_string; - - ERR_FAIL_COND_V_MSG(elem_variant_type == Variant::NIL, -1, "Unknown array element type."); - - bool preset_hint = false; - if (elem_variant_type == Variant::STRING) { - MonoObject *attr = p_member->get_attribute(CACHED_CLASS(ExportAttribute)); - if (PropertyHint(CACHED_FIELD(ExportAttribute, hint)->get_int_value(attr)) == PROPERTY_HINT_ENUM) { - r_hint_string = itos(elem_variant_type) + "/" + itos(PROPERTY_HINT_ENUM) + ":" + CACHED_FIELD(ExportAttribute, hintString)->get_string_value(attr); - preset_hint = true; - } - } - - if (!preset_hint) { - int hint_res = _try_get_member_export_hint(p_member, elem_type, elem_variant_type, /* allow_generics: */ false, elem_hint, elem_hint_string); - - ERR_FAIL_COND_V_MSG(hint_res == -1, -1, "Error while trying to determine information about the array element type."); - - // Format: type/hint:hint_string - r_hint_string = itos(elem_variant_type) + "/" + itos(elem_hint) + ":" + elem_hint_string; - } - - r_hint = PROPERTY_HINT_TYPE_STRING; - - } else if (p_allow_generics && p_variant_type == Variant::DICTIONARY) { - // TODO: Dictionaries are not supported in the inspector - } else { - return 0; - } - - return 1; -} -#endif - -Variant CSharpScript::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - if (unlikely(GDMono::get_singleton() == nullptr)) { - // Probably not the best error but eh. - r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - return Variant(); - } - - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = script_class; - - while (top && top != native) { - GDMonoMethod *method = top->get_method(p_method, p_argcount); - - if (method && method->is_static()) { - MonoObject *result = method->invoke(nullptr, p_args); - - if (result) { - return GDMonoMarshal::mono_object_to_variant(result); - } else { - return Variant(); - } - } - - top = top->get_parent_class(); - } - - // No static method found. Try regular instance calls - return Script::callp(p_method, p_args, p_argcount, r_error); -} - -void CSharpScript::_resource_path_changed() { - _update_name(); -} - bool CSharpScript::_get(const StringName &p_name, Variant &r_ret) const { if (p_name == CSharpLanguage::singleton->string_names._script_source) { r_ret = get_source_code(); @@ -3000,107 +2134,107 @@ void CSharpScript::_bind_methods() { ClassDB::bind_vararg_method(METHOD_FLAGS_DEFAULT, "new", &CSharpScript::_new, MethodInfo("new")); } -Ref<CSharpScript> CSharpScript::create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native) { - // This method should not fail, only assertions allowed - - CRASH_COND(p_class == nullptr); +void CSharpScript::reload_registered_script(Ref<CSharpScript> p_script) { + // IMPORTANT: + // This method must be called only after the CSharpScript and its associated type + // have been added to the script bridge map in the ScriptManagerBridge C# class. + // Other than that, it's the same as `CSharpScript::reload`. - // TODO OPTIMIZE: Cache the 'CSharpScript' associated with this 'p_class' instead of allocating a new one every time - Ref<CSharpScript> script = memnew(CSharpScript); + // This method should not fail, only assertions allowed. - initialize_for_managed_type(script, p_class, p_native); - - return script; -} - -void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native) { - // This method should not fail, only assertions allowed - - CRASH_COND(p_class == nullptr); - - p_script->name = p_class->get_name(); - p_script->script_class = p_class; - p_script->native = p_native; - - CRASH_COND(p_script->native == nullptr); + // Unlike `reload`, we print an error rather than silently returning, + // as we can assert this won't be called a second time until invalidated. + ERR_FAIL_COND(!p_script->reload_invalidated); p_script->valid = true; p_script->reload_invalidated = false; update_script_class_info(p_script); -#ifdef TOOLS_ENABLED - p_script->_update_member_info_no_exports(); -#endif + p_script->_update_exports(); } // Extract information about the script using the mono class. void CSharpScript::update_script_class_info(Ref<CSharpScript> p_script) { - GDMonoClass *base = p_script->script_class->get_parent_class(); + bool tool = false; - // `base` should only be set if the script is a user defined type. - if (base != p_script->native) { - p_script->base = base; - } + // TODO: Use GDNative godot_dictionary + Array methods_array; + methods_array.~Array(); + Dictionary rpc_functions_dict; + rpc_functions_dict.~Dictionary(); + Dictionary signals_dict; + signals_dict.~Dictionary(); - p_script->tool = p_script->script_class->has_attribute(CACHED_CLASS(ToolAttribute)); + Ref<CSharpScript> base_script; + GDMonoCache::managed_callbacks.ScriptManagerBridge_UpdateScriptClassInfo( + p_script.ptr(), &tool, &methods_array, &rpc_functions_dict, &signals_dict, &base_script); - if (!p_script->tool) { - GDMonoClass *nesting_class = p_script->script_class->get_nesting_class(); - p_script->tool = nesting_class && nesting_class->has_attribute(CACHED_CLASS(ToolAttribute)); - } + p_script->tool = tool; -#ifdef TOOLS_ENABLED - if (!p_script->tool) { - p_script->tool = p_script->script_class->get_assembly() == GDMono::get_singleton()->get_tools_assembly(); - } -#endif + p_script->rpc_config.clear(); + p_script->rpc_config = rpc_functions_dict; -#ifdef DEBUG_ENABLED - // For debug builds, we must fetch from all native base methods as well. - // Native base methods must be fetched before the current class. - // Not needed if the script class itself is a native class. + // Methods - if (p_script->script_class != p_script->native) { - GDMonoClass *native_top = p_script->native; - while (native_top) { - native_top->fetch_methods_with_godot_api_checks(p_script->native); + p_script->methods.clear(); - if (native_top == CACHED_CLASS(GodotObject)) { - break; - } + p_script->methods.resize(methods_array.size()); + int push_index = 0; + + for (int i = 0; i < methods_array.size(); i++) { + Dictionary method_info_dict = methods_array[i]; - native_top = native_top->get_parent_class(); + StringName name = method_info_dict["name"]; + + MethodInfo mi; + mi.name = name; + + Array params = method_info_dict["params"]; + + for (int j = 0; j < params.size(); j++) { + Dictionary param = params[j]; + + Variant::Type param_type = (Variant::Type)(int)param["type"]; + PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]); + arg_info.usage = (uint32_t)param["usage"]; + mi.arguments.push_back(arg_info); } + + p_script->methods.set(push_index++, CSharpMethodInfo{ name, mi }); } -#endif - p_script->script_class->fetch_methods_with_godot_api_checks(p_script->native); + // Event signals - p_script->rpc_config.clear(); + // Performance is not critical here as this will be replaced with source generators. - GDMonoClass *top = p_script->script_class; - while (top && top != p_script->native) { - // Fetch methods from base classes as well - top->fetch_methods_with_godot_api_checks(p_script->native); + p_script->event_signals.clear(); - // Update RPC info - { - Vector<GDMonoMethod *> methods = top->get_all_methods(); - for (int i = 0; i < methods.size(); i++) { - if (!methods[i]->is_static()) { - const Variant rpc_config = p_script->_member_get_rpc_config(methods[i]); - if (rpc_config.get_type() != Variant::NIL) { - p_script->rpc_config[methods[i]->get_name()] = rpc_config; - } - } - } + // Sigh... can't we just have capacity? + p_script->event_signals.resize(signals_dict.size()); + push_index = 0; + + for (const Variant *s = signals_dict.next(nullptr); s != nullptr; s = signals_dict.next(s)) { + StringName name = *s; + + MethodInfo mi; + mi.name = name; + + Array params = signals_dict[*s]; + + for (int i = 0; i < params.size(); i++) { + Dictionary param = params[i]; + + Variant::Type param_type = (Variant::Type)(int)param["type"]; + PropertyInfo arg_info = PropertyInfo(param_type, (String)param["name"]); + arg_info.usage = (uint32_t)param["usage"]; + mi.arguments.push_back(arg_info); } - top = top->get_parent_class(); + p_script->event_signals.set(push_index++, EventSignalInfo{ name, mi }); } - p_script->load_script_signals(p_script->script_class, p_script->native); + p_script->base_script = base_script; } bool CSharpScript::can_instantiate() const { @@ -3113,43 +2247,22 @@ bool CSharpScript::can_instantiate() const { // FIXME Need to think this through better. // For tool scripts, this will never fire if the class is not found. That's because we // don't know if it's a tool script if we can't find the class to access the attributes. - if (extra_cond && !script_class) { - if (GDMono::get_singleton()->get_project_assembly() == nullptr) { - // The project assembly is not loaded - ERR_FAIL_V_MSG(false, "Cannot instance script because the project assembly is not loaded. Script: '" + get_path() + "'."); - } else { - // The project assembly is loaded, but the class could not found - ERR_FAIL_V_MSG(false, "Cannot instance script because the class '" + name + "' could not be found. Script: '" + get_path() + "'."); - } + if (extra_cond && !valid) { + ERR_FAIL_V_MSG(false, "Cannot instance script because the associated class could not be found. Script: '" + get_path() + "'."); } return valid && extra_cond; } StringName CSharpScript::get_instance_base_type() const { - if (native) { - return native->get_name(); - } else { - return StringName(); - } + StringName native_name; + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name); + return native_name; } CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error) { - GD_MONO_ASSERT_THREAD_ATTACHED; - /* STEP 1, CREATE */ - // Search the constructor first, to fail with an error if it's not found before allocating anything else. - GDMonoMethod *ctor = script_class->get_method(CACHED_STRING_NAME(dotctor), p_argcount); - if (ctor == nullptr) { - ERR_FAIL_COND_V_MSG(p_argcount == 0, nullptr, - "Cannot create script instance. The class '" + script_class->get_full_name() + - "' does not define a parameterless constructor." + - (get_path().is_empty() ? String() : " Path: '" + get_path() + "'.")); - - ERR_FAIL_V_MSG(nullptr, "Constructor not found."); - } - Ref<RefCounted> ref; if (p_is_ref_counted) { // Hold it alive. Important if we have to dispose a script instance binding before creating the CSharpInstance. @@ -3163,15 +2276,8 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); if (script_binding.inited && !script_binding.gchandle.is_released()) { - MonoObject *mono_object = script_binding.gchandle.get_target(); - if (mono_object) { - MonoException *exc = nullptr; - GDMonoUtils::dispose(mono_object, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - } - } + GDMonoCache::managed_callbacks.CSharpInstanceBridge_CallDispose( + script_binding.gchandle.get_intptr(), /* okIfNull */ true); script_binding.gchandle.release(); // Just in case script_binding.inited = false; @@ -3185,38 +2291,19 @@ CSharpInstance *CSharpScript::_create_instance(const Variant **p_args, int p_arg /* STEP 2, INITIALIZE AND CONSTRUCT */ - MonoObject *mono_object = mono_object_new(mono_domain_get(), script_class->get_mono_ptr()); + bool ok = GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance( + this, p_owner, p_args, p_argcount); - if (!mono_object) { + if (!ok) { // Important to clear this before destroying the script instance here instance->script = Ref<CSharpScript>(); instance->owner = nullptr; - - bool die = instance->_unreference_owner_unsafe(); - // Not ok for the owner to die here. If there is a situation where this can happen, it will be considered a bug. - CRASH_COND(die); - p_owner->set_script_instance(nullptr); - r_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; - ERR_FAIL_V_MSG(nullptr, "Failed to allocate memory for the object."); - } - - // Tie managed to unmanaged - instance->gchandle = MonoGCHandleData::new_strong_handle(mono_object); - if (instance->base_ref_counted) { - instance->_reference_owner_unsafe(); // Here, after assigning the gchandle (for the refcount_incremented callback) - } - - { - MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); - instances.insert(instance->owner); + return nullptr; } - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, instance->owner); - - // Construct - ctor->invoke(mono_object, p_args); + CRASH_COND(instance->gchandle.is_released()); /* STEP 3, PARTY */ @@ -3232,11 +2319,12 @@ Variant CSharpScript::_new(const Variant **p_args, int p_argcount, Callable::Cal r_error.error = Callable::CallError::CALL_OK; - ERR_FAIL_NULL_V(native, Variant()); + StringName native_name; + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name); - GD_MONO_SCOPE_THREAD_ATTACH; + ERR_FAIL_COND_V(native_name == StringName(), Variant()); - Object *owner = ClassDB::instantiate(NATIVE_GDMONOCLASS_NAME(native)); + Object *owner = ClassDB::instantiate(native_name); Ref<RefCounted> ref; RefCounted *r = Object::cast_to<RefCounted>(owner); @@ -3264,18 +2352,18 @@ ScriptInstance *CSharpScript::instance_create(Object *p_this) { CRASH_COND(!valid); #endif - GD_MONO_SCOPE_THREAD_ATTACH; + StringName native_name; + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetScriptNativeName(this, &native_name); - if (native) { - StringName native_name = NATIVE_GDMONOCLASS_NAME(native); - if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { - if (EngineDebugger::is_active()) { - CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, - "Script inherits from native type '" + String(native_name) + - "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'"); - } - ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'."); + ERR_FAIL_COND_V(native_name == StringName(), nullptr); + + if (!ClassDB::is_parent_class(p_this->get_class_name(), native_name)) { + if (EngineDebugger::is_active()) { + CSharpLanguage::get_singleton()->debug_break_parse(get_path(), 0, + "Script inherits from native type '" + String(native_name) + + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'"); } + ERR_FAIL_V_MSG(nullptr, "Script inherits from native type '" + String(native_name) + "', so it can't be instantiated in object of type: '" + p_this->get_class() + "'."); } Callable::CallError unchecked_error; @@ -3317,54 +2405,43 @@ void CSharpScript::set_source_code(const String &p_code) { } void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { - if (!script_class) { + if (!valid) { return; } - GD_MONO_SCOPE_THREAD_ATTACH; - - // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. - GDMonoClass *top = script_class; - - while (top && top != native) { - const Vector<GDMonoMethod *> &methods = top->get_all_methods(); - for (int i = 0; i < methods.size(); ++i) { - MethodInfo minfo = methods[i]->get_method_info(); - if (minfo.name != CACHED_STRING_NAME(dotctor)) { - p_list->push_back(methods[i]->get_method_info()); - } + const CSharpScript *top = this; + while (top != nullptr) { + for (const CSharpMethodInfo &E : top->methods) { + p_list->push_back(E.method_info); } - top = top->get_parent_class(); + top = top->base_script.ptr(); } } bool CSharpScript::has_method(const StringName &p_method) const { - if (!script_class) { + if (!valid) { return false; } - GD_MONO_SCOPE_THREAD_ATTACH; + for (const CSharpMethodInfo &E : methods) { + if (E.name == p_method) { + return true; + } + } - return script_class->has_fetched_method_unknown_params(p_method); + return false; } MethodInfo CSharpScript::get_method_info(const StringName &p_method) const { - if (!script_class) { + if (!valid) { return MethodInfo(); } - GD_MONO_SCOPE_THREAD_ATTACH; - - GDMonoClass *top = script_class; - - while (top && top != native) { - GDMonoMethod *params = top->get_fetched_method_unknown_params(p_method); - if (params) { - return params->get_method_info(); + for (const CSharpMethodInfo &E : methods) { + if (E.name == p_method) { + return E.method_info; } - - top = top->get_parent_class(); } return MethodInfo(); @@ -3379,30 +2456,15 @@ Error CSharpScript::reload(bool p_keep_state) { // That's done separately via domain reloading. reload_invalidated = false; - GD_MONO_SCOPE_THREAD_ATTACH; - - const DotNetScriptLookupInfo *lookup_info = - CSharpLanguage::get_singleton()->lookup_dotnet_script(get_path()); - - if (lookup_info) { - GDMonoClass *klass = lookup_info->script_class; - if (klass) { - ERR_FAIL_COND_V(!CACHED_CLASS(GodotObject)->is_assignable_from(klass), FAILED); - script_class = klass; - } - } + String script_path = get_path(); - valid = script_class != nullptr; + valid = GDMonoCache::managed_callbacks.ScriptManagerBridge_AddScriptBridge(this, &script_path); - if (script_class) { + if (valid) { #ifdef DEBUG_ENABLED - print_verbose("Found class " + script_class->get_full_name() + " for script " + get_path()); + print_verbose("Found class for script " + get_path()); #endif - native = GDMonoUtils::get_class_native_base(script_class); - - CRASH_COND(native == nullptr); - update_script_class_info(this); _update_exports(); @@ -3424,8 +2486,8 @@ bool CSharpScript::get_property_default_value(const StringName &p_property, Vari return true; } - if (base_cache.is_valid()) { - return base_cache->get_property_default_value(p_property, r_value); + if (base_script.is_valid()) { + return base_script->get_property_default_value(p_property, r_value); } #endif @@ -3439,48 +2501,39 @@ void CSharpScript::update_exports() { } bool CSharpScript::has_script_signal(const StringName &p_signal) const { - return event_signals.has(p_signal) || _signals.has(p_signal); -} - -void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { - for (const KeyValue<StringName, Vector<SignalParameter>> &E : _signals) { - MethodInfo mi; - mi.name = E.key; - - const Vector<SignalParameter> ¶ms = E.value; - for (int i = 0; i < params.size(); i++) { - const SignalParameter ¶m = params[i]; + if (!valid) { + return false; + } - PropertyInfo arg_info = PropertyInfo(param.type, param.name); - if (param.type == Variant::NIL && param.nil_is_variant) { - arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } + if (!GDMonoCache::godot_api_cache_updated) { + return false; + } - mi.arguments.push_back(arg_info); + for (const EventSignalInfo &signal : event_signals) { + if (signal.name == p_signal) { + return true; } - - r_signals->push_back(mi); } - for (const KeyValue<StringName, EventSignal> &E : event_signals) { - MethodInfo mi; - mi.name = E.key; - - const EventSignal &event_signal = E.value; - const Vector<SignalParameter> ¶ms = event_signal.parameters; - for (int i = 0; i < params.size(); i++) { - const SignalParameter ¶m = params[i]; + return false; +} - PropertyInfo arg_info = PropertyInfo(param.type, param.name); - if (param.type == Variant::NIL && param.nil_is_variant) { - arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } +void CSharpScript::get_script_signal_list(List<MethodInfo> *r_signals) const { + if (!valid) { + return; + } - mi.arguments.push_back(arg_info); - } + for (const EventSignalInfo &signal : get_script_event_signals()) { + r_signals->push_back(signal.method_info); + } +} - r_signals->push_back(mi); +Vector<CSharpScript::EventSignalInfo> CSharpScript::get_script_event_signals() const { + if (!valid) { + return Vector<EventSignalInfo>(); } + + return event_signals; } bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { @@ -3489,38 +2542,47 @@ bool CSharpScript::inherits_script(const Ref<Script> &p_script) const { return false; } - if (script_class == nullptr || cs->script_class == nullptr) { + if (!valid || !cs->valid) { return false; } - if (script_class == cs->script_class) { - return true; + if (!GDMonoCache::godot_api_cache_updated) { + return false; } - return cs->script_class->is_assignable_from(script_class); + return GDMonoCache::managed_callbacks.ScriptManagerBridge_ScriptIsOrInherits(this, cs.ptr()); } Ref<Script> CSharpScript::get_base_script() const { - // TODO search in metadata file once we have it, not important any way? - return Ref<Script>(); + return base_script; } void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const { - List<PropertyInfo> props; - #ifdef TOOLS_ENABLED - for (const PropertyInfo &E : exported_members_cache) { - props.push_back(E); + const CSharpScript *top = this; + while (top != nullptr) { + for (const PropertyInfo &E : top->exported_members_cache) { + r_list->push_back(E); + } + + top = top->base_script.ptr(); } #else - for (const KeyValue<StringName, PropertyInfo> &E : member_info) { - props.push_front(E.value); - } -#endif // TOOLS_ENABLED + const CSharpScript *top = this; + while (top != nullptr) { + List<PropertyInfo> props; - for (const PropertyInfo &prop : props) { - r_list->push_back(prop); + for (const KeyValue<StringName, PropertyInfo> &E : top->member_info) { + props.push_front(E.value); + } + + for (const PropertyInfo &prop : props) { + r_list->push_back(prop); + } + + top = top->base_script.ptr(); } +#endif } int CSharpScript::get_member_line(const StringName &p_member) const { @@ -3528,22 +2590,6 @@ int CSharpScript::get_member_line(const StringName &p_member) const { return -1; } -Variant CSharpScript::_member_get_rpc_config(IMonoClassMember *p_member) const { - Variant out; - - MonoObject *rpc_attribute = p_member->get_attribute(CACHED_CLASS(RPCAttribute)); - if (rpc_attribute != nullptr) { - Dictionary rpc_config; - rpc_config["rpc_mode"] = CACHED_PROPERTY(RPCAttribute, Mode)->get_int_value(rpc_attribute); - rpc_config["call_local"] = CACHED_PROPERTY(RPCAttribute, CallLocal)->get_bool_value(rpc_attribute); - rpc_config["transfer_mode"] = CACHED_PROPERTY(RPCAttribute, TransferMode)->get_int_value(rpc_attribute); - rpc_config["channel"] = CACHED_PROPERTY(RPCAttribute, TransferChannel)->get_int_value(rpc_attribute); - out = rpc_config; - } - - return out; -} - const Variant CSharpScript::get_rpc_config() const { return rpc_config; } @@ -3564,29 +2610,15 @@ Error CSharpScript::load_source_code(const String &p_path) { return OK; } -void CSharpScript::_update_name() { - String path = get_path(); - - if (!path.is_empty()) { - name = get_path().get_file().get_basename(); - } -} - void CSharpScript::_clear() { tool = false; valid = false; reload_invalidated = true; - - base = nullptr; - native = nullptr; - script_class = nullptr; } CSharpScript::CSharpScript() { _clear(); - _update_name(); - #ifdef DEBUG_ENABLED { MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); @@ -3600,6 +2632,10 @@ CSharpScript::~CSharpScript() { MutexLock lock(CSharpLanguage::get_singleton()->script_instances_mutex); CSharpLanguage::get_singleton()->script_list.remove(&this->script_list); #endif + + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.ScriptManagerBridge_RemoveScriptBridge(this); + } } void CSharpScript::get_members(HashSet<StringName> *p_members) { @@ -3621,9 +2657,13 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const // TODO ignore anything inside bin/ and obj/ in tools builds? - CSharpScript *script = memnew(CSharpScript); + Ref<CSharpScript> script; - Ref<CSharpScript> scriptres(script); + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.ScriptManagerBridge_GetOrCreateScriptBridgeForPath(&p_path, &script); + } else { + script = Ref<CSharpScript>(memnew(CSharpScript)); + } #if defined(DEBUG_ENABLED) || defined(TOOLS_ENABLED) Error err = script->load_source_code(p_path); @@ -3638,7 +2678,7 @@ Ref<Resource> ResourceFormatLoaderCSharpScript::load(const String &p_path, const *r_error = OK; } - return scriptres; + return script; } void ResourceFormatLoaderCSharpScript::get_recognized_extensions(List<String> *p_extensions) const { @@ -3701,14 +2741,7 @@ bool ResourceFormatSaverCSharpScript::recognize(const Ref<Resource> &p_resource) } CSharpLanguage::StringNameCache::StringNameCache() { - _signal_callback = StaticCString::create("_signal_callback"); - _set = StaticCString::create("_set"); - _get = StaticCString::create("_get"); - _get_property_list = StaticCString::create("_get_property_list"); - _notification = StaticCString::create("_notification"); + _property_can_revert = StaticCString::create("_property_can_revert"); + _property_get_revert = StaticCString::create("_property_get_revert"); _script_source = StaticCString::create("script/source"); - on_before_serialize = StaticCString::create("OnBeforeSerialize"); - on_after_deserialize = StaticCString::create("OnAfterDeserialize"); - dotctor = StaticCString::create(".ctor"); - delegate_invoke_method_name = StaticCString::create("Invoke"); } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 48129e69cb..3509a5c87d 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -39,8 +39,6 @@ #include "mono_gc_handle.h" #include "mono_gd/gd_mono.h" -#include "mono_gd/gd_mono_header.h" -#include "mono_gd/gd_mono_internals.h" #ifdef TOOLS_ENABLED #include "editor/editor_plugin.h" @@ -67,48 +65,17 @@ TScriptInstance *cast_script_instance(ScriptInstance *p_inst) { #define CAST_CSHARP_INSTANCE(m_inst) (cast_script_instance<CSharpInstance, CSharpLanguage>(m_inst)) -struct DotNetScriptLookupInfo { - String class_namespace; - String class_name; - GDMonoClass *script_class = nullptr; - - DotNetScriptLookupInfo() {} // Required by HashMap... - - DotNetScriptLookupInfo(const String &p_class_namespace, const String &p_class_name, GDMonoClass *p_script_class) : - class_namespace(p_class_namespace), class_name(p_class_name), script_class(p_script_class) { - } -}; - class CSharpScript : public Script { GDCLASS(CSharpScript, Script); -public: - struct SignalParameter { - String name; - Variant::Type type; - bool nil_is_variant = false; - }; - - struct EventSignal { - GDMonoField *field = nullptr; - GDMonoMethod *invoke_method = nullptr; - Vector<SignalParameter> parameters; - }; - -private: friend class CSharpInstance; friend class CSharpLanguage; - friend struct CSharpScriptDepSort; bool tool = false; bool valid = false; bool reload_invalidated = false; - GDMonoClass *base = nullptr; - GDMonoClass *native = nullptr; - GDMonoClass *script_class = nullptr; - - Ref<CSharpScript> base_cache; // TODO what's this for? + Ref<CSharpScript> base_script; HashSet<Object *> instances; @@ -118,26 +85,32 @@ private: // Replace with buffer containing the serialized state of managed scripts. // Keep variant state backup to use only with script instance placeholders. List<Pair<StringName, Variant>> properties; - List<Pair<StringName, Array>> event_signals; + Dictionary event_signals; }; HashSet<ObjectID> pending_reload_instances; RBMap<ObjectID, StateBackup> pending_reload_state; - StringName tied_class_name_for_reload; - StringName tied_class_namespace_for_reload; #endif String source; - StringName name; SelfList<CSharpScript> script_list = this; - HashMap<StringName, Vector<SignalParameter>> _signals; - HashMap<StringName, EventSignal> event_signals; - bool signals_invalidated = true; - Dictionary rpc_config; + struct EventSignalInfo { + StringName name; // MethodInfo stores a string... + MethodInfo method_info; + }; + + struct CSharpMethodInfo { + StringName name; // MethodInfo stores a string... + MethodInfo method_info; + }; + + Vector<EventSignalInfo> event_signals; + Vector<CSharpMethodInfo> methods; + #ifdef TOOLS_ENABLED List<PropertyInfo> exported_members_cache; // members_cache HashMap<StringName, Variant> exported_members_defval_cache; // member_default_values_cache @@ -146,7 +119,6 @@ private: bool placeholder_fallback_enabled = false; bool exports_invalidated = true; void _update_exports_values(HashMap<StringName, Variant> &values, List<PropertyInfo> &propnames); - void _update_member_info_no_exports(); void _placeholder_erased(PlaceHolderScriptInstance *p_placeholder) override; #endif @@ -158,39 +130,24 @@ private: void _clear(); - void _update_name(); - - void load_script_signals(GDMonoClass *p_class, GDMonoClass *p_native_class); - bool _get_signal(GDMonoClass *p_class, GDMonoMethod *p_delegate_invoke, Vector<SignalParameter> ¶ms); - bool _update_exports(PlaceHolderScriptInstance *p_instance_to_update = nullptr); - bool _get_member_export(IMonoClassMember *p_member, bool p_inspect_export, PropertyInfo &r_prop_info, bool &r_exported); -#ifdef TOOLS_ENABLED - static int _try_get_member_export_hint(IMonoClassMember *p_member, ManagedType p_type, Variant::Type p_variant_type, bool p_allow_generics, PropertyHint &r_hint, String &r_hint_string); -#endif - CSharpInstance *_create_instance(const Variant **p_args, int p_argcount, Object *p_owner, bool p_is_ref_counted, Callable::CallError &r_error); Variant _new(const Variant **p_args, int p_argcount, Callable::CallError &r_error); // Do not use unless you know what you are doing - friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); - static Ref<CSharpScript> create_for_managed_type(GDMonoClass *p_class, GDMonoClass *p_native); static void update_script_class_info(Ref<CSharpScript> p_script); - static void initialize_for_managed_type(Ref<CSharpScript> p_script, GDMonoClass *p_class, GDMonoClass *p_native); - - Variant _member_get_rpc_config(IMonoClassMember *p_member) const; protected: static void _bind_methods(); - Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; - void _resource_path_changed() override; bool _get(const StringName &p_name, Variant &r_ret) const; bool _set(const StringName &p_name, const Variant &p_value); void _get_property_list(List<PropertyInfo> *p_properties) const; public: + static void reload_registered_script(Ref<CSharpScript> p_script); + bool can_instantiate() const override; StringName get_instance_base_type() const override; ScriptInstance *instance_create(Object *p_this) override; @@ -214,14 +171,20 @@ public: bool has_script_signal(const StringName &p_signal) const override; void get_script_signal_list(List<MethodInfo> *r_signals) const override; + Vector<EventSignalInfo> get_script_event_signals() const; + bool get_property_default_value(const StringName &p_property, Variant &r_value) const override; void get_script_property_list(List<PropertyInfo> *r_list) const override; void update_exports() override; void get_members(HashSet<StringName> *p_members) override; - bool is_tool() const override { return tool; } - bool is_valid() const override { return valid; } + bool is_tool() const override { + return tool; + } + bool is_valid() const override { + return valid; + } bool inherits_script(const Ref<Script> &p_script) const override; @@ -237,7 +200,9 @@ public: const Variant get_rpc_config() const override; #ifdef TOOLS_ENABLED - bool is_placeholder_fallback_enabled() const override { return placeholder_fallback_enabled; } + bool is_placeholder_fallback_enabled() const override { + return placeholder_fallback_enabled; + } #endif Error load_source_code(const String &p_path); @@ -270,22 +235,18 @@ class CSharpInstance : public ScriptInstance { bool _unreference_owner_unsafe(); /* - * If nullptr is returned, the caller must destroy the script instance by removing it from its owner. + * If false is returned, the caller must destroy the script instance by removing it from its owner. */ - MonoObject *_internal_new_managed(); + bool _internal_new_managed(); // Do not use unless you know what you are doing - friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *); static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle); - void get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state); - void get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state); - public: - MonoObject *get_mono_object() const; - _FORCE_INLINE_ bool is_destructing_script_instance() { return destructing_script_instance; } + _FORCE_INLINE_ GCHandleIntPtr get_gchandle_intptr() { return gchandle.get_intptr(); } + Object *get_owner() override; bool set(const StringName &p_name, const Variant &p_value) override; @@ -293,17 +254,20 @@ public: void get_property_list(List<PropertyInfo> *p_properties) const override; Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override; + bool property_can_revert(const StringName &p_name) const override; + bool property_get_revert(const StringName &p_name, Variant &r_ret) const override; + void get_method_list(List<MethodInfo> *p_list) const override; bool has_method(const StringName &p_method) const override; Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; - void mono_object_disposed(MonoObject *p_obj); + void mono_object_disposed(GCHandleIntPtr p_gchandle_to_free); /* * If 'r_delete_owner' is set to true, the caller must memdelete the script instance's owner. Otherwise, if * 'r_remove_script_instance' is set to true, the caller must destroy the script instance by removing it from its owner. */ - void mono_object_disposed_baseref(MonoObject *p_obj, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance); + void mono_object_disposed_baseref(GCHandleIntPtr p_gchandle_to_free, bool p_is_finalizer, bool &r_delete_owner, bool &r_remove_script_instance); void connect_event_signals(); void disconnect_event_signals(); @@ -329,7 +293,6 @@ public: struct CSharpScriptBinding { bool inited = false; StringName type_name; - GDMonoClass *wrapper_class = nullptr; MonoGCHandleData gchandle; Object *owner = nullptr; @@ -367,33 +330,22 @@ class CSharpLanguage : public ScriptLanguage { ManagedCallableMiddleman *managed_callable_middleman = memnew(ManagedCallableMiddleman); struct StringNameCache { - StringName _signal_callback; - StringName _set; - StringName _get; - StringName _get_property_list; - StringName _notification; + StringName _property_can_revert; + StringName _property_get_revert; StringName _script_source; - StringName dotctor; // .ctor - StringName on_before_serialize; // OnBeforeSerialize - StringName on_after_deserialize; // OnAfterDeserialize - StringName delegate_invoke_method_name; StringNameCache(); }; int lang_idx = -1; - HashMap<String, DotNetScriptLookupInfo> dotnet_script_lookup_map; - - void lookup_script_for_class(GDMonoClass *p_class); - // For debug_break and debug_break_parse int _debug_parse_err_line = -1; String _debug_parse_err_file; String _debug_error; friend class GDMono; - void _on_scripts_domain_unloaded(); + void _on_scripts_domain_about_to_unload(); #ifdef TOOLS_ENABLED EditorPlugin *godotsharp_editor = nullptr; @@ -415,21 +367,35 @@ public: StringNameCache string_names; - const Mutex &get_language_bind_mutex() { return language_bind_mutex; } + const Mutex &get_language_bind_mutex() { + return language_bind_mutex; + } + const Mutex &get_script_instances_mutex() { + return script_instances_mutex; + } - _FORCE_INLINE_ int get_language_index() { return lang_idx; } + _FORCE_INLINE_ int get_language_index() { + return lang_idx; + } void set_language_index(int p_idx); - _FORCE_INLINE_ const StringNameCache &get_string_names() { return string_names; } + _FORCE_INLINE_ const StringNameCache &get_string_names() { + return string_names; + } - _FORCE_INLINE_ static CSharpLanguage *get_singleton() { return singleton; } + _FORCE_INLINE_ static CSharpLanguage *get_singleton() { + return singleton; + } #ifdef TOOLS_ENABLED - _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { return godotsharp_editor; } + _FORCE_INLINE_ EditorPlugin *get_godotsharp_editor() const { + return godotsharp_editor; + } #endif static void release_script_gchandle(MonoGCHandleData &p_gchandle); - static void release_script_gchandle(MonoObject *p_expected_obj, MonoGCHandleData &p_gchandle); + static void release_script_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, MonoGCHandleData &r_gchandle); + static void release_binding_gchandle_thread_safe(GCHandleIntPtr p_gchandle_to_free, CSharpScriptBinding &r_script_binding); bool debug_break(const String &p_error, bool p_allow_continue = true); bool debug_break_parse(const String &p_file, int p_line, const String &p_error); @@ -439,12 +405,8 @@ public: void reload_assemblies(bool p_soft_reload); #endif - _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { return managed_callable_middleman; } - - void lookup_scripts_in_assembly(GDMonoAssembly *p_assembly); - - const DotNetScriptLookupInfo *lookup_dotnet_script(const String &p_script_path) const { - return dotnet_script_lookup_map.getptr(p_script_path); + _FORCE_INLINE_ ManagedCallableMiddleman *get_managed_callable_middleman() const { + return managed_callable_middleman; } String get_name() const override; @@ -474,7 +436,9 @@ public: Script *create_script() const override; bool has_named_classes() const override; bool supports_builtin_mode() const override; - /* TODO? */ int find_function(const String &p_function, const String &p_code) const override { return -1; } + /* TODO? */ int find_function(const String &p_function, const String &p_code) const override { + return -1; + } String make_function(const String &p_class, const String &p_name, const PackedStringArray &p_args) const override; virtual String _get_indentation() const; /* TODO? */ void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const override {} @@ -489,14 +453,20 @@ public: /* TODO */ void debug_get_stack_level_locals(int p_level, List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {} /* TODO */ void debug_get_stack_level_members(int p_level, List<String> *p_members, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {} /* TODO */ void debug_get_globals(List<String> *p_locals, List<Variant> *p_values, int p_max_subitems, int p_max_depth) override {} - /* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override { return ""; } + /* TODO */ String debug_parse_stack_level_expression(int p_level, const String &p_expression, int p_max_subitems, int p_max_depth) override { + return ""; + } Vector<StackInfo> debug_get_current_stack_info() override; /* PROFILING FUNCTIONS */ /* TODO */ void profiling_start() override {} /* TODO */ void profiling_stop() override {} - /* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; } - /* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override { return 0; } + /* TODO */ int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max) override { + return 0; + } + /* TODO */ int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max) override { + return 0; + } void frame() override; @@ -515,16 +485,12 @@ public: bool overrides_external_editor() override; #endif - /* THREAD ATTACHING */ - void thread_enter() override; - void thread_exit() override; - RBMap<Object *, CSharpScriptBinding>::Element *insert_script_binding(Object *p_object, const CSharpScriptBinding &p_script_binding); bool setup_csharp_script_binding(CSharpScriptBinding &r_script_binding, Object *p_object); -#ifdef DEBUG_ENABLED - Vector<StackInfo> stack_trace_get_info(MonoObject *p_stack_trace); -#endif + static void tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, const StringName *p_native_name, bool p_ref_counted); + static void tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted); + static void tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged); void post_unsafe_reference(Object *p_obj); void pre_unsafe_unreference(Object *p_obj); diff --git a/modules/mono/doc_classes/GodotSharp.xml b/modules/mono/doc_classes/GodotSharp.xml index b981542801..faf3512da7 100644 --- a/modules/mono/doc_classes/GodotSharp.xml +++ b/modules/mono/doc_classes/GodotSharp.xml @@ -10,55 +10,10 @@ <tutorials> </tutorials> <methods> - <method name="attach_thread"> - <return type="void" /> - <description> - Attaches the current thread to the Mono runtime. - </description> - </method> - <method name="detach_thread"> - <return type="void" /> - <description> - Detaches the current thread from the Mono runtime. - </description> - </method> - <method name="get_domain_id"> - <return type="int" /> - <description> - Returns the current MonoDomain ID. - [b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash. - </description> - </method> - <method name="get_scripts_domain_id"> - <return type="int" /> - <description> - Returns the scripts MonoDomain's ID. This will be the same MonoDomain ID as [method get_domain_id], unless the scripts domain isn't loaded. - [b]Note:[/b] The Mono runtime must be initialized for this method to work (use [method is_runtime_initialized] to check). If the Mono runtime isn't initialized at the time this method is called, the engine will crash. - </description> - </method> - <method name="is_domain_finalizing_for_unload"> - <return type="bool" /> - <param index="0" name="domain_id" type="int" /> - <description> - Returns [code]true[/code] if the domain is being finalized, [code]false[/code] otherwise. - </description> - </method> <method name="is_runtime_initialized"> <return type="bool" /> <description> - Returns [code]true[/code] if the Mono runtime is initialized, [code]false[/code] otherwise. - </description> - </method> - <method name="is_runtime_shutting_down"> - <return type="bool" /> - <description> - Returns [code]true[/code] if the Mono runtime is shutting down, [code]false[/code] otherwise. - </description> - </method> - <method name="is_scripts_domain_loaded"> - <return type="bool" /> - <description> - Returns [code]true[/code] if the scripts domain is loaded, [code]false[/code] otherwise. + Returns [code]true[/code] if the .NET runtime is initialized, [code]false[/code] otherwise. </description> </method> </methods> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln index d1868f52ef..03a7dc453c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}" EndProject diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj index 4e9e7184da..013b210ff4 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj @@ -26,16 +26,8 @@ <None Include="Sdk\Sdk.props" Pack="true" PackagePath="Sdk" /> <None Include="Sdk\Sdk.targets" Pack="true" PackagePath="Sdk" /> <!-- SdkPackageVersions.props --> - <None Include="..\..\..\SdkPackageVersions.props" Pack="true" PackagePath="Sdk"> + <None Include="$(GodotSdkPackageVersionsFilePath)" Pack="true" PackagePath="Sdk"> <Link>Sdk\SdkPackageVersions.props</Link> </None> </ItemGroup> - - <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"> - <PropertyGroup> - <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath> - <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> - </PropertyGroup> - <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> - </Target> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props index 5a499742e9..652b9e8e43 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props @@ -95,21 +95,4 @@ <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants> </PropertyGroup> - - <!-- Godot API references --> - <ItemGroup> - <!-- - TODO: - We should consider a nuget package for reference assemblies. This is difficult because the - Godot scripting API is continuaslly breaking backwards compatibility even in patch releases. - --> - <Reference Include="GodotSharp"> - <Private>false</Private> - <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath> - </Reference> - <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' "> - <Private>false</Private> - <HintPath>$(GodotProjectDir).godot\mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath> - </Reference> - </ItemGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets index 397ede9644..aad4ea4553 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets @@ -19,4 +19,10 @@ <ItemGroup Condition=" '$(DisableImplicitGodotGeneratorReferences)' != 'true' "> <PackageReference Include="Godot.SourceGenerators" Version="$(PackageFloatingVersion_Godot)" /> </ItemGroup> + + <!-- Godot API references --> + <ItemGroup Condition=" '$(DisableImplicitGodotSharpReferences)' != 'true' "> + <PackageReference Include="GodotSharp" Version="$(PackageVersion_GodotSharp)" /> + <PackageReference Include="GodotSharpEditor" Version="$(PackageVersion_GodotSharp)" Condition=" '$(Configuration)' == 'Debug' " /> + </ItemGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs new file mode 100644 index 0000000000..764ba8f121 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/EventSignals.cs @@ -0,0 +1,7 @@ +namespace Godot.SourceGenerators.Sample; + +public partial class EventSignals : Godot.Object +{ + [Signal] + public delegate void MySignalEventHandler(string str, int num); +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs new file mode 100644 index 0000000000..ac9f59aa99 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs @@ -0,0 +1,109 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +#pragma warning disable CS0169 +#pragma warning disable CS0414 + +namespace Godot.SourceGenerators.Sample +{ + [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")] + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public partial class ExportedFields : Godot.Object + { + [Export] private Boolean field_Boolean = true; + [Export] private Char field_Char = 'f'; + [Export] private SByte field_SByte = 10; + [Export] private Int16 field_Int16 = 10; + [Export] private Int32 field_Int32 = 10; + [Export] private Int64 field_Int64 = 10; + [Export] private Byte field_Byte = 10; + [Export] private UInt16 field_UInt16 = 10; + [Export] private UInt32 field_UInt32 = 10; + [Export] private UInt64 field_UInt64 = 10; + [Export] private Single field_Single = 10; + [Export] private Double field_Double = 10; + [Export] private String field_String = "foo"; + + // Godot structs + [Export] private Vector2 field_Vector2 = new(10f, 10f); + [Export] private Vector2i field_Vector2i = Vector2i.Up; + [Export] private Rect2 field_Rect2 = new(new Vector2(10f, 10f), new Vector2(10f, 10f)); + [Export] private Rect2i field_Rect2i = new(new Vector2i(10, 10), new Vector2i(10, 10)); + [Export] private Transform2D field_Transform2D = Transform2D.Identity; + [Export] private Vector3 field_Vector3 = new(10f, 10f, 10f); + [Export] private Vector3i field_Vector3i = Vector3i.Back; + [Export] private Basis field_Basis = new Basis(Quaternion.Identity); + [Export] private Quaternion field_Quaternion = new Quaternion(Basis.Identity); + [Export] private Transform3D field_Transform3D = Transform3D.Identity; + [Export] private Vector4 field_Vector4 = new(10f, 10f, 10f, 10f); + [Export] private Vector4i field_Vector4i = Vector4i.One; + [Export] private Projection field_Projection = Projection.Identity; + [Export] private AABB field_AABB = new AABB(10f, 10f, 10f, new Vector3(1f, 1f, 1f)); + [Export] private Color field_Color = Colors.Aquamarine; + [Export] private Plane field_Plane = Plane.PlaneXZ; + [Export] private Callable field_Callable = new Callable(Engine.GetMainLoop(), "_process"); + [Export] private SignalInfo field_SignalInfo = new SignalInfo(Engine.GetMainLoop(), "property_list_changed"); + + // Enums + [SuppressMessage("ReSharper", "UnusedMember.Local")] + enum MyEnum + { + A, + B, + C + } + + [Export] private MyEnum field_Enum = MyEnum.C; + + [Flags] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + enum MyFlagsEnum + { + A, + B, + C + } + + [Export] private MyFlagsEnum field_FlagsEnum = MyFlagsEnum.C; + + // Arrays + [Export] private Byte[] field_ByteArray = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Int32[] field_Int32Array = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Int64[] field_Int64Array = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Single[] field_SingleArray = { 0f, 1f, 2f, 3f, 4f, 5f, 6f }; + [Export] private Double[] field_DoubleArray = { 0d, 1d, 2d, 3d, 4d, 5d, 6d }; + [Export] private String[] field_StringArray = { "foo", "bar" }; + [Export(PropertyHint.Enum, "A,B,C")] private String[] field_StringArrayEnum = { "foo", "bar" }; + [Export] private Vector2[] field_Vector2Array = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right }; + [Export] private Vector3[] field_Vector3Array = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right }; + [Export] private Color[] field_ColorArray = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige }; + [Export] private Godot.Object[] field_GodotObjectOrDerivedArray = { null }; + [Export] private StringName[] field_StringNameArray = { "foo", "bar" }; + [Export] private NodePath[] field_NodePathArray = { "foo", "bar" }; + [Export] private RID[] field_RIDArray = { default, default, default }; + + // Variant + [Export] private Variant field_Variant = "foo"; + + // Classes + [Export] private Godot.Object field_GodotObjectOrDerived; + [Export] private Godot.Texture field_GodotResourceTexture; + [Export] private StringName field_StringName = new StringName("foo"); + [Export] private NodePath field_NodePath = new NodePath("foo"); + [Export] private RID field_RID; + + [Export] private Godot.Collections.Dictionary field_GodotDictionary = + new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } }; + + [Export] private Godot.Collections.Array field_GodotArray = + new() { "foo", 10, Vector2.Up, Colors.Chocolate }; + + [Export] private Godot.Collections.Dictionary<string, bool> field_GodotGenericDictionary = + new() { { "foo", true }, { "bar", false } }; + + [Export] private Godot.Collections.Array<int> field_GodotGenericArray = + new() { 0, 1, 2, 3, 4, 5, 6 }; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs new file mode 100644 index 0000000000..4a0e8075f0 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs @@ -0,0 +1,109 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +#pragma warning disable CS0169 +#pragma warning disable CS0414 + +namespace Godot.SourceGenerators.Sample +{ + [SuppressMessage("ReSharper", "BuiltInTypeReferenceStyle")] + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + [SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeEvident")] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public partial class ExportedProperties : Godot.Object + { + [Export] private Boolean property_Boolean { get; set; } = true; + [Export] private Char property_Char { get; set; } = 'f'; + [Export] private SByte property_SByte { get; set; } = 10; + [Export] private Int16 property_Int16 { get; set; } = 10; + [Export] private Int32 property_Int32 { get; set; } = 10; + [Export] private Int64 property_Int64 { get; set; } = 10; + [Export] private Byte property_Byte { get; set; } = 10; + [Export] private UInt16 property_UInt16 { get; set; } = 10; + [Export] private UInt32 property_UInt32 { get; set; } = 10; + [Export] private UInt64 property_UInt64 { get; set; } = 10; + [Export] private Single property_Single { get; set; } = 10; + [Export] private Double property_Double { get; set; } = 10; + [Export] private String property_String { get; set; } = "foo"; + + // Godot structs + [Export] private Vector2 property_Vector2 { get; set; } = new(10f, 10f); + [Export] private Vector2i property_Vector2i { get; set; } = Vector2i.Up; + [Export] private Rect2 property_Rect2 { get; set; } = new(new Vector2(10f, 10f), new Vector2(10f, 10f)); + [Export] private Rect2i property_Rect2i { get; set; } = new(new Vector2i(10, 10), new Vector2i(10, 10)); + [Export] private Transform2D property_Transform2D { get; set; } = Transform2D.Identity; + [Export] private Vector3 property_Vector3 { get; set; } = new(10f, 10f, 10f); + [Export] private Vector3i property_Vector3i { get; set; } = Vector3i.Back; + [Export] private Basis property_Basis { get; set; } = new Basis(Quaternion.Identity); + [Export] private Quaternion property_Quaternion { get; set; } = new Quaternion(Basis.Identity); + [Export] private Transform3D property_Transform3D { get; set; } = Transform3D.Identity; + [Export] private Vector4 property_Vector4 { get; set; } = new(10f, 10f, 10f, 10f); + [Export] private Vector4i property_Vector4i { get; set; } = Vector4i.One; + [Export] private Projection property_Projection { get; set; } = Projection.Identity; + [Export] private AABB property_AABB { get; set; } = new AABB(10f, 10f, 10f, new Vector3(1f, 1f, 1f)); + [Export] private Color property_Color { get; set; } = Colors.Aquamarine; + [Export] private Plane property_Plane { get; set; } = Plane.PlaneXZ; + [Export] private Callable property_Callable { get; set; } = new Callable(Engine.GetMainLoop(), "_process"); + [Export] private SignalInfo property_SignalInfo { get; set; } = new SignalInfo(Engine.GetMainLoop(), "property_list_changed"); + + // Enums + [SuppressMessage("ReSharper", "UnusedMember.Local")] + enum MyEnum + { + A, + B, + C + } + + [Export] private MyEnum property_Enum { get; set; } = MyEnum.C; + + [Flags] + [SuppressMessage("ReSharper", "UnusedMember.Local")] + enum MyFlagsEnum + { + A, + B, + C + } + + [Export] private MyFlagsEnum property_FlagsEnum { get; set; } = MyFlagsEnum.C; + + // Arrays + [Export] private Byte[] property_ByteArray { get; set; } = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Int32[] property_Int32Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Int64[] property_Int64Array { get; set; } = { 0, 1, 2, 3, 4, 5, 6 }; + [Export] private Single[] property_SingleArray { get; set; } = { 0f, 1f, 2f, 3f, 4f, 5f, 6f }; + [Export] private Double[] property_DoubleArray { get; set; } = { 0d, 1d, 2d, 3d, 4d, 5d, 6d }; + [Export] private String[] property_StringArray { get; set; } = { "foo", "bar" }; + [Export(PropertyHint.Enum, "A,B,C")] private String[] property_StringArrayEnum { get; set; } = { "foo", "bar" }; + [Export] private Vector2[] property_Vector2Array { get; set; } = { Vector2.Up, Vector2.Down, Vector2.Left, Vector2.Right }; + [Export] private Vector3[] property_Vector3Array { get; set; } = { Vector3.Up, Vector3.Down, Vector3.Left, Vector3.Right }; + [Export] private Color[] property_ColorArray { get; set; } = { Colors.Aqua, Colors.Aquamarine, Colors.Azure, Colors.Beige }; + [Export] private Godot.Object[] property_GodotObjectOrDerivedArray { get; set; } = { null }; + [Export] private StringName[] field_StringNameArray { get; set; } = { "foo", "bar" }; + [Export] private NodePath[] field_NodePathArray { get; set; } = { "foo", "bar" }; + [Export] private RID[] field_RIDArray { get; set; } = { default, default, default }; + + // Variant + [Export] private Variant property_Variant { get; set; } = "foo"; + + // Classes + [Export] private Godot.Object property_GodotObjectOrDerived { get; set; } + [Export] private Godot.Texture property_GodotResourceTexture { get; set; } + [Export] private StringName property_StringName { get; set; } = new StringName("foo"); + [Export] private NodePath property_NodePath { get; set; } = new NodePath("foo"); + [Export] private RID property_RID { get; set; } + + [Export] private Godot.Collections.Dictionary property_GodotDictionary { get; set; } = + new() { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } }; + + [Export] private Godot.Collections.Array property_GodotArray { get; set; } = + new() { "foo", 10, Vector2.Up, Colors.Chocolate }; + + [Export] private Godot.Collections.Dictionary<string, bool> property_GodotGenericDictionary { get; set; } = + new() { { "foo", true }, { "bar", false } }; + + [Export] private Godot.Collections.Array<int> property_GodotGenericArray { get; set; } = + new() { 0, 1, 2, 3, 4, 5, 6 }; + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs index 2ddb8880c2..b21b035b4d 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs @@ -1,16 +1,21 @@ +#pragma warning disable CS0169 + namespace Godot.SourceGenerators.Sample { partial class Generic<T> : Godot.Object { + private int _field; } // Generic again but different generic parameters partial class Generic<T, R> : Godot.Object { + private int _field; } // Generic again but without generic parameters partial class Generic : Godot.Object { + private int _field; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj index 24f7909861..8e78e0385d 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj @@ -1,12 +1,14 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>netstandard2.1</TargetFramework> + <TargetFramework>net6.0</TargetFramework> </PropertyGroup> <PropertyGroup> <!-- $(GodotProjectDir) would normally be defined by the Godot.NET.Sdk --> <GodotProjectDir>$(MSBuildProjectDirectory)</GodotProjectDir> + <!-- For compiling GetGodotPropertyDefaultValues. --> + <DefineConstants>$(DefineConstants);TOOLS</DefineConstants> </PropertyGroup> <PropertyGroup> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs new file mode 100644 index 0000000000..618ba24abc --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Methods.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Godot.SourceGenerators.Sample; + +[SuppressMessage("ReSharper", "RedundantNameQualifier")] +public partial class Methods : Godot.Object +{ + private void MethodWithOverload() + { + } + + private void MethodWithOverload(int a) + { + } + + private void MethodWithOverload(int a, int b) + { + } + + // Should be ignored. The previous one is picked. + // ReSharper disable once UnusedMember.Local + private void MethodWithOverload(float a, float b) + { + } + + // Generic methods should be ignored. + // ReSharper disable once UnusedMember.Local + private void GenericMethod<T>(T t) + { + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs new file mode 100644 index 0000000000..a1667dbb8f --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs @@ -0,0 +1,36 @@ +#pragma warning disable CS0169 + +namespace Godot.SourceGenerators.Sample +{ + public partial class ScriptBoilerplate : Node + { + private NodePath _nodePath; + private int _velocity; + + public override void _Process(float delta) + { + _ = delta; + + base._Process(delta); + } + + public int Bazz(StringName name) + { + _ = name; + return 1; + } + + public void IgnoreThisMethodWithByRefParams(ref int a) + { + _ = a; + } + } + + partial struct OuterClass + { + public partial class NesterClass : RefCounted + { + public override Variant _Get(StringName property) => default; + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 4867c986e6..c1ae993251 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -1,3 +1,4 @@ +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -14,9 +15,8 @@ namespace Godot.SourceGenerators "Missing partial modifier on declaration of type '" + $"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'"; - string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " + - "declared with the partial modifier or annotated with the " + - $"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'."; + string description = $"{message}. Subclasses of '{GodotClasses.Object}' " + + "must be declared with the partial modifier."; context.ReportDiagnostic(Diagnostic.Create( new DiagnosticDescriptor(id: "GODOT-G0001", @@ -29,5 +29,191 @@ namespace Godot.SourceGenerators cds.GetLocation(), cds.SyntaxTree.FilePath)); } + + public static void ReportNonPartialGodotScriptOuterClass( + GeneratorExecutionContext context, + TypeDeclarationSyntax outerTypeDeclSyntax + ) + { + var outerSymbol = context.Compilation + .GetSemanticModel(outerTypeDeclSyntax.SyntaxTree) + .GetDeclaredSymbol(outerTypeDeclSyntax); + + string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ? + namedTypeSymbol.FullQualifiedName() : + "type not found"; + + string message = + $"Missing partial modifier on declaration of type '{fullQualifiedName}', " + + $"which contains one or more subclasses of '{GodotClasses.Object}'"; + + string description = $"{message}. Subclasses of '{GodotClasses.Object}' and their " + + "containing types must be declared with the partial modifier."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-G0002", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + outerTypeDeclSyntax.GetLocation(), + outerTypeDeclSyntax.SyntaxTree.FilePath)); + } + + public static void ReportExportedMemberIsStatic( + GeneratorExecutionContext context, + ISymbol exportedMemberSymbol + ) + { + var locations = exportedMemberSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + bool isField = exportedMemberSymbol is IFieldSymbol; + + string message = $"Attempted to export static {(isField ? "field" : "property")}: " + + $"'{exportedMemberSymbol.ToDisplayString()}'"; + + string description = $"{message}. Only instance fields and properties can be exported." + + " Remove the 'static' modifier or the '[Export]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-G0101", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportExportedMemberTypeNotSupported( + GeneratorExecutionContext context, + ISymbol exportedMemberSymbol + ) + { + var locations = exportedMemberSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + bool isField = exportedMemberSymbol is IFieldSymbol; + + string message = $"The type of the exported {(isField ? "field" : "property")} " + + $"is not supported: '{exportedMemberSymbol.ToDisplayString()}'"; + + string description = $"{message}. Use a supported type or remove the '[Export]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-G0102", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportExportedMemberIsReadOnly( + GeneratorExecutionContext context, + ISymbol exportedMemberSymbol + ) + { + var locations = exportedMemberSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + bool isField = exportedMemberSymbol is IFieldSymbol; + + string message = $"The exported {(isField ? "field" : "property")} " + + $"is read-only: '{exportedMemberSymbol.ToDisplayString()}'"; + + string description = isField ? + $"{message}. Exported fields cannot be read-only." : + $"{message}. Exported properties must be writable."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-G0103", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportExportedMemberIsWriteOnly( + GeneratorExecutionContext context, + ISymbol exportedMemberSymbol + ) + { + var locations = exportedMemberSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + + string message = $"The exported property is write-only: '{exportedMemberSymbol.ToDisplayString()}'"; + + string description = $"{message}. Exported properties must be readable."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-G0104", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportSignalDelegateMissingSuffix( + GeneratorExecutionContext context, + INamedTypeSymbol delegateSymbol) + { + var locations = delegateSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + + string message = "The name of the delegate must end with 'EventHandler': " + + delegateSymbol.ToDisplayString() + + $". Did you mean '{delegateSymbol.Name}EventHandler'?"; + + string description = $"{message}. Rename the delegate accordingly or remove the '[Signal]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-G0201", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } + + public static void ReportSignalDelegateSignatureNotSupported( + GeneratorExecutionContext context, + INamedTypeSymbol delegateSymbol) + { + var locations = delegateSymbol.Locations; + var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault(); + + string message = "The delegate signature of the signal " + + $"is not supported: '{delegateSymbol.ToDisplayString()}'"; + + string description = $"{message}. Use supported types only or remove the '[Signal]' attribute."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-G0202", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + location, + location?.SourceTree?.FilePath)); + } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index e16f72f43a..bac4708165 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -13,30 +15,65 @@ namespace Godot.SourceGenerators ) => context.AnalyzerConfigOptions.GlobalOptions .TryGetValue("build_property." + property, out value); - private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName) - { - if (symbol == null) - return false; + public static bool AreGodotSourceGeneratorsDisabled(this GeneratorExecutionContext context) + => context.TryGetGlobalAnalyzerProperty("GodotSourceGenerators", out string? toggle) && + toggle != null && + toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase); + + public static bool IsGodotToolsProject(this GeneratorExecutionContext context) + => context.TryGetGlobalAnalyzerProperty("IsGodotToolsProject", out string? toggle) && + toggle != null && + toggle.Equals("true", StringComparison.OrdinalIgnoreCase); - while (true) + public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyName, string typeFullName) + { + while (symbol != null) { - if (symbol.ToString() == baseName) + if (symbol.ContainingAssembly.Name == assemblyName && + symbol.ToString() == typeFullName) { return true; } - if (symbol.BaseType != null) - { - symbol = symbol.BaseType; - continue; - } - - break; + symbol = symbol.BaseType; } return false; } + public static INamedTypeSymbol? GetGodotScriptNativeClass(this INamedTypeSymbol classTypeSymbol) + { + var symbol = classTypeSymbol; + + while (symbol != null) + { + if (symbol.ContainingAssembly.Name == "GodotSharp") + return symbol; + + symbol = symbol.BaseType; + } + + return null; + } + + public static string? GetGodotScriptNativeClassName(this INamedTypeSymbol classTypeSymbol) + { + var nativeType = classTypeSymbol.GetGodotScriptNativeClass(); + + if (nativeType == null) + return null; + + var godotClassNameAttr = nativeType.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.IsGodotClassNameAttribute() ?? false); + + string? godotClassName = null; + + if (godotClassNameAttr is { ConstructorArguments: { Length: > 0 } }) + godotClassName = godotClassNameAttr.ConstructorArguments[0].Value?.ToString(); + + return godotClassName ?? nativeType.Name; + } + private static bool IsGodotScriptClass( this ClassDeclarationSyntax cds, Compilation compilation, out INamedTypeSymbol? symbol @@ -47,7 +84,7 @@ namespace Godot.SourceGenerators var classTypeSymbol = sm.GetDeclaredSymbol(cds); if (classTypeSymbol?.BaseType == null - || !classTypeSymbol.BaseType.InheritsFrom(GodotClasses.Object)) + || !classTypeSymbol.BaseType.InheritsFrom("GodotSharp", GodotClasses.Object)) { symbol = null; return false; @@ -69,21 +106,171 @@ namespace Godot.SourceGenerators } } - public static bool IsPartial(this ClassDeclarationSyntax cds) + public static bool IsNested(this TypeDeclarationSyntax cds) + => cds.Parent is TypeDeclarationSyntax; + + public static bool IsPartial(this TypeDeclarationSyntax cds) => cds.Modifiers.Any(SyntaxKind.PartialKeyword); - public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol) - => symbol.GetAttributes().Any(attr => - attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr); + public static bool AreAllOuterTypesPartial( + this TypeDeclarationSyntax cds, + out TypeDeclarationSyntax? typeMissingPartial + ) + { + SyntaxNode? outerSyntaxNode = cds.Parent; + + while (outerSyntaxNode is TypeDeclarationSyntax outerTypeDeclSyntax) + { + if (!outerTypeDeclSyntax.IsPartial()) + { + typeMissingPartial = outerTypeDeclSyntax; + return false; + } + + outerSyntaxNode = outerSyntaxNode.Parent; + } + + typeMissingPartial = null; + return true; + } + + public static string GetDeclarationKeyword(this INamedTypeSymbol namedTypeSymbol) + { + string? keyword = namedTypeSymbol.DeclaringSyntaxReferences + .OfType<TypeDeclarationSyntax>().FirstOrDefault()? + .Keyword.Text; + + return keyword ?? namedTypeSymbol.TypeKind switch + { + TypeKind.Interface => "interface", + TypeKind.Struct => "struct", + _ => "class" + }; + } private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = SymbolDisplayFormat.FullyQualifiedFormat .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); - public static string FullQualifiedName(this INamedTypeSymbol symbol) + public static string FullQualifiedName(this ITypeSymbol symbol) => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + public static string NameWithTypeParameters(this INamedTypeSymbol symbol) + { + return symbol.IsGenericType ? + string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") : + symbol.Name; + } + public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol) => namespaceSymbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + + public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName) + => qualifiedName + // AddSource() doesn't support angle brackets + .Replace("<", "(Of ") + .Replace(">", ")"); + + public static bool IsGodotExportAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GodotClasses.ExportAttr; + + public static bool IsGodotSignalAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GodotClasses.SignalAttr; + + public static bool IsGodotClassNameAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GodotClasses.GodotClassNameAttr; + + public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GodotClasses.SystemFlagsAttr; + + public static GodotMethodData? HasGodotCompatibleSignature( + this IMethodSymbol method, + MarshalUtils.TypeCache typeCache + ) + { + if (method.IsGenericMethod) + return null; + + var retSymbol = method.ReturnType; + var retType = method.ReturnsVoid ? + null : + MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache); + + if (retType == null && !method.ReturnsVoid) + return null; + + var parameters = method.Parameters; + + var paramTypes = parameters + // Currently we don't support `ref`, `out`, `in`, `ref readonly` parameters (and we never may) + .Where(p => p.RefKind == RefKind.None) + // Attempt to determine the variant type + .Select(p => MarshalUtils.ConvertManagedTypeToMarshalType(p.Type, typeCache)) + // Discard parameter types that couldn't be determined (null entries) + .Where(t => t != null).Cast<MarshalType>().ToImmutableArray(); + + // If any parameter type was incompatible, it was discarded so the length won't match + if (parameters.Length > paramTypes.Length) + return null; // Ignore incompatible method + + return new GodotMethodData(method, paramTypes, parameters + .Select(p => p.Type).ToImmutableArray(), retType, retSymbol); + } + + public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature( + this IEnumerable<IMethodSymbol> methods, + MarshalUtils.TypeCache typeCache + ) + { + foreach (var method in methods) + { + var methodData = HasGodotCompatibleSignature(method, typeCache); + + if (methodData != null) + yield return methodData.Value; + } + } + + public static IEnumerable<GodotPropertyData> WhereIsGodotCompatibleType( + this IEnumerable<IPropertySymbol> properties, + MarshalUtils.TypeCache typeCache + ) + { + foreach (var property in properties) + { + // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. + if (property.IsWriteOnly || property.IsReadOnly) + continue; + + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(property.Type, typeCache); + + if (marshalType == null) + continue; + + yield return new GodotPropertyData(property, marshalType.Value); + } + } + + public static IEnumerable<GodotFieldData> WhereIsGodotCompatibleType( + this IEnumerable<IFieldSymbol> fields, + MarshalUtils.TypeCache typeCache + ) + { + foreach (var field in fields) + { + // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. + if (field.IsReadOnly) + continue; + + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(field.Type, typeCache); + + if (marshalType == null) + continue; + + yield return new GodotFieldData(field, marshalType.Value); + } + } } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj index 11d8e0f72b..f51b5970c3 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> - <LangVersion>8.0</LangVersion> + <LangVersion>9.0</LangVersion> <Nullable>enable</Nullable> </PropertyGroup> <PropertyGroup> @@ -21,21 +21,13 @@ </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" /> - <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.1" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" /> </ItemGroup> <ItemGroup> <!-- Package the generator in the analyzer directory of the nuget package --> <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" /> <!-- Package the props file --> - <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="false" /> + <None Include="Godot.SourceGenerators.props" Pack="true" PackagePath="build" Visible="true" /> </ItemGroup> - - <Target Name="CopyNupkgToSConsOutputDir" AfterTargets="Pack"> - <PropertyGroup> - <GodotSourceRootPath>$(SolutionDir)\..\..\..\..\</GodotSourceRootPath> - <GodotOutputDataDir>$(GodotSourceRootPath)\bin\GodotSharp\</GodotOutputDataDir> - </PropertyGroup> - <Copy SourceFiles="$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg" DestinationFolder="$(GodotOutputDataDir)Tools\nupkgs\" /> - </Target> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props index f9b47ad5b1..7881ed0a8c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props @@ -2,6 +2,7 @@ <ItemGroup> <!-- $(GodotProjectDir) is defined by Godot.NET.Sdk --> <CompilerVisibleProperty Include="GodotProjectDir" /> - <CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" /> + <CompilerVisibleProperty Include="GodotSourceGenerators" /> + <CompilerVisibleProperty Include="IsGodotToolsProject" /> </ItemGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs index 29e41d155a..9ba8bb89b8 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs @@ -3,7 +3,10 @@ namespace Godot.SourceGenerators public static class GodotClasses { public const string Object = "Godot.Object"; - public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute"; public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute"; + public const string ExportAttr = "Godot.ExportAttribute"; + public const string SignalAttr = "Godot.SignalAttribute"; + public const string GodotClassNameAttr = "Godot.GodotClassName"; + public const string SystemFlagsAttr = "System.FlagsAttribute"; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs new file mode 100644 index 0000000000..1a25d684a0 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs @@ -0,0 +1,148 @@ +using System; + +namespace Godot.SourceGenerators +{ + // TODO: May need to think about compatibility here. Could Godot change these values between minor versions? + + internal enum VariantType + { + Nil = 0, + Bool = 1, + Int = 2, + Float = 3, + String = 4, + Vector2 = 5, + Vector2i = 6, + Rect2 = 7, + Rect2i = 8, + Vector3 = 9, + Vector3i = 10, + Transform2d = 11, + Vector4 = 12, + Vector4i = 13, + Plane = 14, + Quaternion = 15, + Aabb = 16, + Basis = 17, + Transform3d = 18, + Projection = 19, + Color = 20, + StringName = 21, + NodePath = 22, + Rid = 23, + Object = 24, + Callable = 25, + Signal = 26, + Dictionary = 27, + Array = 28, + PackedByteArray = 29, + PackedInt32Array = 30, + PackedInt64Array = 31, + PackedFloat32Array = 32, + PackedFloat64Array = 33, + PackedStringArray = 34, + PackedVector2Array = 35, + PackedVector3Array = 36, + PackedColorArray = 37, + Max = 38 + } + + internal enum PropertyHint + { + None = 0, + Range = 1, + Enum = 2, + EnumSuggestion = 3, + ExpEasing = 4, + Link = 5, + Flags = 6, + Layers2dRender = 7, + Layers2dPhysics = 8, + Layers2dNavigation = 9, + Layers3dRender = 10, + Layers3dPhysics = 11, + Layers3dNavigation = 12, + File = 13, + Dir = 14, + GlobalFile = 15, + GlobalDir = 16, + ResourceType = 17, + MultilineText = 18, + Expression = 19, + PlaceholderText = 20, + ColorNoAlpha = 21, + ImageCompressLossy = 22, + ImageCompressLossless = 23, + ObjectId = 24, + TypeString = 25, + NodePathToEditedNode = 26, + MethodOfVariantType = 27, + MethodOfBaseType = 28, + MethodOfInstance = 29, + MethodOfScript = 30, + PropertyOfVariantType = 31, + PropertyOfBaseType = 32, + PropertyOfInstance = 33, + PropertyOfScript = 34, + ObjectTooBig = 35, + NodePathValidTypes = 36, + SaveFile = 37, + GlobalSaveFile = 38, + IntIsObjectid = 39, + IntIsPointer = 41, + ArrayType = 40, + LocaleId = 42, + LocalizableString = 43, + NodeType = 44, + Max = 45 + } + + [Flags] + internal enum PropertyUsageFlags + { + None = 0, + Storage = 2, + Editor = 4, + Checkable = 8, + Checked = 16, + Internationalized = 32, + Group = 64, + Category = 128, + Subgroup = 256, + ClassIsBitfield = 512, + NoInstanceState = 1024, + RestartIfChanged = 2048, + ScriptVariable = 4096, + StoreIfNull = 8192, + AnimateAsTrigger = 16384, + UpdateAllIfModified = 32768, + ScriptDefaultValue = 65536, + ClassIsEnum = 131072, + NilIsVariant = 262144, + Internal = 524288, + DoNotShareOnDuplicate = 1048576, + HighEndGfx = 2097152, + NodePathFromSceneRoot = 4194304, + ResourceNotPersistent = 8388608, + KeyingIncrements = 16777216, + DeferredSetResource = 33554432, + EditorInstantiateObject = 67108864, + EditorBasicSetting = 134217728, + Array = 536870912, + Default = 6, + DefaultIntl = 38, + NoEditor = 2 + } + + public enum MethodFlags + { + Normal = 1, + Editor = 2, + Const = 4, + Virtual = 8, + Vararg = 16, + Static = 32, + ObjectCore = 64, + Default = 1 + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs new file mode 100644 index 0000000000..a3ad8cbabd --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs @@ -0,0 +1,62 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + public struct GodotMethodData + { + public GodotMethodData(IMethodSymbol method, ImmutableArray<MarshalType> paramTypes, + ImmutableArray<ITypeSymbol> paramTypeSymbols, MarshalType? retType, ITypeSymbol? retSymbol) + { + Method = method; + ParamTypes = paramTypes; + ParamTypeSymbols = paramTypeSymbols; + RetType = retType; + RetSymbol = retSymbol; + } + + public IMethodSymbol Method { get; } + public ImmutableArray<MarshalType> ParamTypes { get; } + public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; } + public MarshalType? RetType { get; } + public ITypeSymbol? RetSymbol { get; } + } + + public struct GodotSignalDelegateData + { + public GodotSignalDelegateData(string name, INamedTypeSymbol delegateSymbol, GodotMethodData invokeMethodData) + { + Name = name; + DelegateSymbol = delegateSymbol; + InvokeMethodData = invokeMethodData; + } + + public string Name { get; } + public INamedTypeSymbol DelegateSymbol { get; } + public GodotMethodData InvokeMethodData { get; } + } + + public struct GodotPropertyData + { + public GodotPropertyData(IPropertySymbol propertySymbol, MarshalType type) + { + PropertySymbol = propertySymbol; + Type = type; + } + + public IPropertySymbol PropertySymbol { get; } + public MarshalType Type { get; } + } + + public struct GodotFieldData + { + public GodotFieldData(IFieldSymbol fieldSymbol, MarshalType type) + { + FieldSymbol = fieldSymbol; + Type = type; + } + + public IFieldSymbol FieldSymbol { get; } + public MarshalType Type { get; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs new file mode 100644 index 0000000000..54da6218f3 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotPluginsInitializerGenerator.cs @@ -0,0 +1,60 @@ +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class GodotPluginsInitializerGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.IsGodotToolsProject()) + return; + + string source = + @"using System; +using System.Runtime.InteropServices; +using Godot.Bridge; +using Godot.NativeInterop; + +namespace GodotPlugins.Game +{ + internal static partial class Main + { + [UnmanagedCallersOnly(EntryPoint = ""godotsharp_game_main_init"")] + private static godot_bool InitializeFromGameProject(IntPtr godotDllHandle, IntPtr outManagedCallbacks) + { + try + { + DllImportResolver dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport; + + var coreApiAssembly = typeof(Godot.Object).Assembly; + + NativeLibrary.SetDllImportResolver(coreApiAssembly, dllImportResolver); + + ManagedCallbacks.Create(outManagedCallbacks); + + ScriptManagerBridge.LookupScriptsInAssembly(typeof(GodotPlugins.Game.Main).Assembly); + + return godot_bool.True; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return false.ToGodotBool(); + } + } + } +} +"; + + context.AddSource("GodotPlugins.Game_Generated", + SourceText.From(source, Encoding.UTF8)); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs new file mode 100644 index 0000000000..15f5803bf0 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs @@ -0,0 +1,73 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum MarshalType + { + Boolean, + Char, + SByte, + Int16, + Int32, + Int64, + Byte, + UInt16, + UInt32, + UInt64, + Single, + Double, + String, + + // Godot structs + Vector2, + Vector2i, + Rect2, + Rect2i, + Transform2D, + Vector3, + Vector3i, + Basis, + Quaternion, + Transform3D, + Vector4, + Vector4i, + Projection, + AABB, + Color, + Plane, + Callable, + SignalInfo, + + // Enums + Enum, + + // Arrays + ByteArray, + Int32Array, + Int64Array, + Float32Array, + Float64Array, + StringArray, + Vector2Array, + Vector3Array, + ColorArray, + GodotObjectOrDerivedArray, + SystemArrayOfStringName, + SystemArrayOfNodePath, + SystemArrayOfRID, + + // Variant + Variant, + + // Classes + GodotObjectOrDerived, + StringName, + NodePath, + RID, + GodotDictionary, + GodotArray, + GodotGenericDictionary, + GodotGenericArray, + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs new file mode 100644 index 0000000000..ca84518c0c --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -0,0 +1,678 @@ +using System; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + internal static class MarshalUtils + { + public class TypeCache + { + public INamedTypeSymbol GodotObjectType { get; } + + public TypeCache(GeneratorExecutionContext context) + { + INamedTypeSymbol GetTypeByMetadataNameOrThrow(string fullyQualifiedMetadataName) + { + return context.Compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? + throw new InvalidOperationException("Type not found: " + fullyQualifiedMetadataName); + } + + GodotObjectType = GetTypeByMetadataNameOrThrow("Godot.Object"); + } + } + + public static VariantType? ConvertMarshalTypeToVariantType(MarshalType marshalType) + => marshalType switch + { + MarshalType.Boolean => VariantType.Bool, + MarshalType.Char => VariantType.Int, + MarshalType.SByte => VariantType.Int, + MarshalType.Int16 => VariantType.Int, + MarshalType.Int32 => VariantType.Int, + MarshalType.Int64 => VariantType.Int, + MarshalType.Byte => VariantType.Int, + MarshalType.UInt16 => VariantType.Int, + MarshalType.UInt32 => VariantType.Int, + MarshalType.UInt64 => VariantType.Int, + MarshalType.Single => VariantType.Float, + MarshalType.Double => VariantType.Float, + MarshalType.String => VariantType.String, + MarshalType.Vector2 => VariantType.Vector2, + MarshalType.Vector2i => VariantType.Vector2i, + MarshalType.Rect2 => VariantType.Rect2, + MarshalType.Rect2i => VariantType.Rect2i, + MarshalType.Transform2D => VariantType.Transform2d, + MarshalType.Vector3 => VariantType.Vector3, + MarshalType.Vector3i => VariantType.Vector3i, + MarshalType.Basis => VariantType.Basis, + MarshalType.Quaternion => VariantType.Quaternion, + MarshalType.Transform3D => VariantType.Transform3d, + MarshalType.Vector4 => VariantType.Vector4, + MarshalType.Vector4i => VariantType.Vector4i, + MarshalType.Projection => VariantType.Projection, + MarshalType.AABB => VariantType.Aabb, + MarshalType.Color => VariantType.Color, + MarshalType.Plane => VariantType.Plane, + MarshalType.Callable => VariantType.Callable, + MarshalType.SignalInfo => VariantType.Signal, + MarshalType.Enum => VariantType.Int, + MarshalType.ByteArray => VariantType.PackedByteArray, + MarshalType.Int32Array => VariantType.PackedInt32Array, + MarshalType.Int64Array => VariantType.PackedInt64Array, + MarshalType.Float32Array => VariantType.PackedFloat32Array, + MarshalType.Float64Array => VariantType.PackedFloat64Array, + MarshalType.StringArray => VariantType.PackedStringArray, + MarshalType.Vector2Array => VariantType.PackedVector2Array, + MarshalType.Vector3Array => VariantType.PackedVector3Array, + MarshalType.ColorArray => VariantType.PackedColorArray, + MarshalType.GodotObjectOrDerivedArray => VariantType.Array, + MarshalType.SystemArrayOfStringName => VariantType.Array, + MarshalType.SystemArrayOfNodePath => VariantType.Array, + MarshalType.SystemArrayOfRID => VariantType.Array, + MarshalType.Variant => VariantType.Nil, + MarshalType.GodotObjectOrDerived => VariantType.Object, + MarshalType.StringName => VariantType.StringName, + MarshalType.NodePath => VariantType.NodePath, + MarshalType.RID => VariantType.Rid, + MarshalType.GodotDictionary => VariantType.Dictionary, + MarshalType.GodotArray => VariantType.Array, + MarshalType.GodotGenericDictionary => VariantType.Dictionary, + MarshalType.GodotGenericArray => VariantType.Array, + _ => null + }; + + public static MarshalType? ConvertManagedTypeToMarshalType(ITypeSymbol type, TypeCache typeCache) + { + var specialType = type.SpecialType; + + switch (specialType) + { + case SpecialType.System_Boolean: + return MarshalType.Boolean; + case SpecialType.System_Char: + return MarshalType.Char; + case SpecialType.System_SByte: + return MarshalType.SByte; + case SpecialType.System_Int16: + return MarshalType.Int16; + case SpecialType.System_Int32: + return MarshalType.Int32; + case SpecialType.System_Int64: + return MarshalType.Int64; + case SpecialType.System_Byte: + return MarshalType.Byte; + case SpecialType.System_UInt16: + return MarshalType.UInt16; + case SpecialType.System_UInt32: + return MarshalType.UInt32; + case SpecialType.System_UInt64: + return MarshalType.UInt64; + case SpecialType.System_Single: + return MarshalType.Single; + case SpecialType.System_Double: + return MarshalType.Double; + case SpecialType.System_String: + return MarshalType.String; + default: + { + var typeKind = type.TypeKind; + + if (typeKind == TypeKind.Enum) + return MarshalType.Enum; + + if (typeKind == TypeKind.Struct) + { + if (type.ContainingAssembly.Name == "GodotSharp" && + type.ContainingNamespace.Name == "Godot") + { + return type switch + { + { Name: "Vector2" } => MarshalType.Vector2, + { Name: "Vector2i" } => MarshalType.Vector2i, + { Name: "Rect2" } => MarshalType.Rect2, + { Name: "Rect2i" } => MarshalType.Rect2i, + { Name: "Transform2D" } => MarshalType.Transform2D, + { Name: "Vector3" } => MarshalType.Vector3, + { Name: "Vector3i" } => MarshalType.Vector3i, + { Name: "Basis" } => MarshalType.Basis, + { Name: "Quaternion" } => MarshalType.Quaternion, + { Name: "Transform3D" } => MarshalType.Transform3D, + { Name: "Vector4" } => MarshalType.Vector4, + { Name: "Vector4i" } => MarshalType.Vector4i, + { Name: "Projection" } => MarshalType.Projection, + { Name: "AABB" } => MarshalType.AABB, + { Name: "Color" } => MarshalType.Color, + { Name: "Plane" } => MarshalType.Plane, + { Name: "RID" } => MarshalType.RID, + { Name: "Callable" } => MarshalType.Callable, + { Name: "SignalInfo" } => MarshalType.SignalInfo, + { Name: "Variant" } => MarshalType.Variant, + _ => null + }; + } + } + else if (typeKind == TypeKind.Array) + { + var arrayType = (IArrayTypeSymbol)type; + var elementType = arrayType.ElementType; + + switch (elementType.SpecialType) + { + case SpecialType.System_Byte: + return MarshalType.ByteArray; + case SpecialType.System_Int32: + return MarshalType.Int32Array; + case SpecialType.System_Int64: + return MarshalType.Int64Array; + case SpecialType.System_Single: + return MarshalType.Float32Array; + case SpecialType.System_Double: + return MarshalType.Float64Array; + case SpecialType.System_String: + return MarshalType.StringArray; + } + + if (elementType.SimpleDerivesFrom(typeCache.GodotObjectType)) + return MarshalType.GodotObjectOrDerivedArray; + + if (elementType.ContainingAssembly.Name == "GodotSharp" && + elementType.ContainingNamespace.Name == "Godot") + { + switch (elementType) + { + case { Name: "Vector2" }: + return MarshalType.Vector2Array; + case { Name: "Vector3" }: + return MarshalType.Vector3Array; + case { Name: "Color" }: + return MarshalType.ColorArray; + case { Name: "StringName" }: + return MarshalType.SystemArrayOfStringName; + case { Name: "NodePath" }: + return MarshalType.SystemArrayOfNodePath; + case { Name: "RID" }: + return MarshalType.SystemArrayOfRID; + } + } + + return null; + } + else + { + if (type.SimpleDerivesFrom(typeCache.GodotObjectType)) + return MarshalType.GodotObjectOrDerived; + + if (type.ContainingAssembly.Name == "GodotSharp") + { + switch (type.ContainingNamespace.Name) + { + case "Godot": + return type switch + { + { Name: "StringName" } => MarshalType.StringName, + { Name: "NodePath" } => MarshalType.NodePath, + _ => null + }; + case "Collections" + when type.ContainingNamespace.FullQualifiedName() == "Godot.Collections": + return type switch + { + { Name: "Dictionary" } => + type is INamedTypeSymbol { IsGenericType: false } ? + MarshalType.GodotDictionary : + MarshalType.GodotGenericDictionary, + { Name: "Array" } => + type is INamedTypeSymbol { IsGenericType: false } ? + MarshalType.GodotArray : + MarshalType.GodotGenericArray, + _ => null + }; + } + } + } + + break; + } + } + + return null; + } + + private static bool SimpleDerivesFrom(this ITypeSymbol? type, ITypeSymbol candidateBaseType) + { + while (type != null) + { + if (SymbolEqualityComparer.Default.Equals(type, candidateBaseType)) + return true; + + type = type.BaseType; + } + + return false; + } + + public static ITypeSymbol? GetArrayElementType(ITypeSymbol typeSymbol) + { + if (typeSymbol.TypeKind == TypeKind.Array) + { + var arrayType = (IArrayTypeSymbol)typeSymbol; + return arrayType.ElementType; + } + + if (typeSymbol is INamedTypeSymbol { IsGenericType: true } genericType) + return genericType.TypeArguments.FirstOrDefault(); + + return null; + } + + private static StringBuilder Append(this StringBuilder source, string a, string b) + => source.Append(a).Append(b); + + private static StringBuilder Append(this StringBuilder source, string a, string b, string c) + => source.Append(a).Append(b).Append(c); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d) + => source.Append(a).Append(b).Append(c).Append(d); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d, string e) + => source.Append(a).Append(b).Append(c).Append(d).Append(e); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d, string e, string f) + => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d, string e, string f, string g) + => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g); + + private static StringBuilder Append(this StringBuilder source, string a, string b, + string c, string d, string e, string f, string g, string h) + => source.Append(a).Append(b).Append(c).Append(d).Append(e).Append(f).Append(g).Append(h); + + private const string VariantUtils = "global::Godot.NativeInterop.VariantUtils"; + + public static StringBuilder AppendNativeVariantToManagedExpr(this StringBuilder source, + string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType) + { + return marshalType switch + { + MarshalType.Boolean => + source.Append(VariantUtils, ".ConvertToBool(", inputExpr, ")"), + MarshalType.Char => + source.Append("(char)", VariantUtils, ".ConvertToUInt16(", inputExpr, ")"), + MarshalType.SByte => + source.Append(VariantUtils, ".ConvertToInt8(", inputExpr, ")"), + MarshalType.Int16 => + source.Append(VariantUtils, ".ConvertToInt16(", inputExpr, ")"), + MarshalType.Int32 => + source.Append(VariantUtils, ".ConvertToInt32(", inputExpr, ")"), + MarshalType.Int64 => + source.Append(VariantUtils, ".ConvertToInt64(", inputExpr, ")"), + MarshalType.Byte => + source.Append(VariantUtils, ".ConvertToUInt8(", inputExpr, ")"), + MarshalType.UInt16 => + source.Append(VariantUtils, ".ConvertToUInt16(", inputExpr, ")"), + MarshalType.UInt32 => + source.Append(VariantUtils, ".ConvertToUInt32(", inputExpr, ")"), + MarshalType.UInt64 => + source.Append(VariantUtils, ".ConvertToUInt64(", inputExpr, ")"), + MarshalType.Single => + source.Append(VariantUtils, ".ConvertToFloat32(", inputExpr, ")"), + MarshalType.Double => + source.Append(VariantUtils, ".ConvertToFloat64(", inputExpr, ")"), + MarshalType.String => + source.Append(VariantUtils, ".ConvertToStringObject(", inputExpr, ")"), + MarshalType.Vector2 => + source.Append(VariantUtils, ".ConvertToVector2(", inputExpr, ")"), + MarshalType.Vector2i => + source.Append(VariantUtils, ".ConvertToVector2i(", inputExpr, ")"), + MarshalType.Rect2 => + source.Append(VariantUtils, ".ConvertToRect2(", inputExpr, ")"), + MarshalType.Rect2i => + source.Append(VariantUtils, ".ConvertToRect2i(", inputExpr, ")"), + MarshalType.Transform2D => + source.Append(VariantUtils, ".ConvertToTransform2D(", inputExpr, ")"), + MarshalType.Vector3 => + source.Append(VariantUtils, ".ConvertToVector3(", inputExpr, ")"), + MarshalType.Vector3i => + source.Append(VariantUtils, ".ConvertToVector3i(", inputExpr, ")"), + MarshalType.Basis => + source.Append(VariantUtils, ".ConvertToBasis(", inputExpr, ")"), + MarshalType.Quaternion => + source.Append(VariantUtils, ".ConvertToQuaternion(", inputExpr, ")"), + MarshalType.Transform3D => + source.Append(VariantUtils, ".ConvertToTransform3D(", inputExpr, ")"), + MarshalType.Vector4 => + source.Append(VariantUtils, ".ConvertToVector4(", inputExpr, ")"), + MarshalType.Vector4i => + source.Append(VariantUtils, ".ConvertToVector4i(", inputExpr, ")"), + MarshalType.Projection => + source.Append(VariantUtils, ".ConvertToProjection(", inputExpr, ")"), + MarshalType.AABB => + source.Append(VariantUtils, ".ConvertToAABB(", inputExpr, ")"), + MarshalType.Color => + source.Append(VariantUtils, ".ConvertToColor(", inputExpr, ")"), + MarshalType.Plane => + source.Append(VariantUtils, ".ConvertToPlane(", inputExpr, ")"), + MarshalType.Callable => + source.Append(VariantUtils, ".ConvertToCallableManaged(", inputExpr, ")"), + MarshalType.SignalInfo => + source.Append(VariantUtils, ".ConvertToSignalInfo(", inputExpr, ")"), + MarshalType.Enum => + source.Append("(", typeSymbol.FullQualifiedName(), + ")", VariantUtils, ".ConvertToInt32(", inputExpr, ")"), + MarshalType.ByteArray => + source.Append(VariantUtils, ".ConvertAsPackedByteArrayToSystemArray(", inputExpr, ")"), + MarshalType.Int32Array => + source.Append(VariantUtils, ".ConvertAsPackedInt32ArrayToSystemArray(", inputExpr, ")"), + MarshalType.Int64Array => + source.Append(VariantUtils, ".ConvertAsPackedInt64ArrayToSystemArray(", inputExpr, ")"), + MarshalType.Float32Array => + source.Append(VariantUtils, ".ConvertAsPackedFloat32ArrayToSystemArray(", inputExpr, ")"), + MarshalType.Float64Array => + source.Append(VariantUtils, ".ConvertAsPackedFloat64ArrayToSystemArray(", inputExpr, ")"), + MarshalType.StringArray => + source.Append(VariantUtils, ".ConvertAsPackedStringArrayToSystemArray(", inputExpr, ")"), + MarshalType.Vector2Array => + source.Append(VariantUtils, ".ConvertAsPackedVector2ArrayToSystemArray(", inputExpr, ")"), + MarshalType.Vector3Array => + source.Append(VariantUtils, ".ConvertAsPackedVector3ArrayToSystemArray(", inputExpr, ")"), + MarshalType.ColorArray => + source.Append(VariantUtils, ".ConvertAsPackedColorArrayToSystemArray(", inputExpr, ")"), + MarshalType.GodotObjectOrDerivedArray => + source.Append(VariantUtils, ".ConvertToSystemArrayOfGodotObject<", + ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">(", inputExpr, ")"), + MarshalType.SystemArrayOfStringName => + source.Append(VariantUtils, ".ConvertToSystemArrayOfStringName(", inputExpr, ")"), + MarshalType.SystemArrayOfNodePath => + source.Append(VariantUtils, ".ConvertToSystemArrayOfNodePath(", inputExpr, ")"), + MarshalType.SystemArrayOfRID => + source.Append(VariantUtils, ".ConvertToSystemArrayOfRID(", inputExpr, ")"), + MarshalType.Variant => + source.Append("global::Godot.Variant.CreateCopyingBorrowed(", inputExpr, ")"), + MarshalType.GodotObjectOrDerived => + source.Append("(", typeSymbol.FullQualifiedName(), + ")", VariantUtils, ".ConvertToGodotObject(", inputExpr, ")"), + MarshalType.StringName => + source.Append(VariantUtils, ".ConvertToStringNameObject(", inputExpr, ")"), + MarshalType.NodePath => + source.Append(VariantUtils, ".ConvertToNodePathObject(", inputExpr, ")"), + MarshalType.RID => + source.Append(VariantUtils, ".ConvertToRID(", inputExpr, ")"), + MarshalType.GodotDictionary => + source.Append(VariantUtils, ".ConvertToDictionaryObject(", inputExpr, ")"), + MarshalType.GodotArray => + source.Append(VariantUtils, ".ConvertToArrayObject(", inputExpr, ")"), + MarshalType.GodotGenericDictionary => + source.Append(VariantUtils, ".ConvertToDictionaryObject<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">(", inputExpr, ")"), + MarshalType.GodotGenericArray => + source.Append(VariantUtils, ".ConvertToArrayObject<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">(", inputExpr, ")"), + _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, + "Received unexpected marshal type") + }; + } + + public static StringBuilder AppendManagedToNativeVariantExpr( + this StringBuilder source, string inputExpr, MarshalType marshalType) + { + return marshalType switch + { + MarshalType.Boolean => + source.Append(VariantUtils, ".CreateFromBool(", inputExpr, ")"), + MarshalType.Char => + source.Append(VariantUtils, ".CreateFromInt((ushort)", inputExpr, ")"), + MarshalType.SByte => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Int16 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Int32 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Int64 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Byte => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.UInt16 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.UInt32 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.UInt64 => + source.Append(VariantUtils, ".CreateFromInt(", inputExpr, ")"), + MarshalType.Single => + source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"), + MarshalType.Double => + source.Append(VariantUtils, ".CreateFromFloat(", inputExpr, ")"), + MarshalType.String => + source.Append(VariantUtils, ".CreateFromString(", inputExpr, ")"), + MarshalType.Vector2 => + source.Append(VariantUtils, ".CreateFromVector2(", inputExpr, ")"), + MarshalType.Vector2i => + source.Append(VariantUtils, ".CreateFromVector2i(", inputExpr, ")"), + MarshalType.Rect2 => + source.Append(VariantUtils, ".CreateFromRect2(", inputExpr, ")"), + MarshalType.Rect2i => + source.Append(VariantUtils, ".CreateFromRect2i(", inputExpr, ")"), + MarshalType.Transform2D => + source.Append(VariantUtils, ".CreateFromTransform2D(", inputExpr, ")"), + MarshalType.Vector3 => + source.Append(VariantUtils, ".CreateFromVector3(", inputExpr, ")"), + MarshalType.Vector3i => + source.Append(VariantUtils, ".CreateFromVector3i(", inputExpr, ")"), + MarshalType.Basis => + source.Append(VariantUtils, ".CreateFromBasis(", inputExpr, ")"), + MarshalType.Quaternion => + source.Append(VariantUtils, ".CreateFromQuaternion(", inputExpr, ")"), + MarshalType.Transform3D => + source.Append(VariantUtils, ".CreateFromTransform3D(", inputExpr, ")"), + MarshalType.Vector4 => + source.Append(VariantUtils, ".CreateFromVector4(", inputExpr, ")"), + MarshalType.Vector4i => + source.Append(VariantUtils, ".CreateFromVector4i(", inputExpr, ")"), + MarshalType.Projection => + source.Append(VariantUtils, ".CreateFromProjection(", inputExpr, ")"), + MarshalType.AABB => + source.Append(VariantUtils, ".CreateFromAABB(", inputExpr, ")"), + MarshalType.Color => + source.Append(VariantUtils, ".CreateFromColor(", inputExpr, ")"), + MarshalType.Plane => + source.Append(VariantUtils, ".CreateFromPlane(", inputExpr, ")"), + MarshalType.Callable => + source.Append(VariantUtils, ".CreateFromCallable(", inputExpr, ")"), + MarshalType.SignalInfo => + source.Append(VariantUtils, ".CreateFromSignalInfo(", inputExpr, ")"), + MarshalType.Enum => + source.Append(VariantUtils, ".CreateFromInt((int)", inputExpr, ")"), + MarshalType.ByteArray => + source.Append(VariantUtils, ".CreateFromPackedByteArray(", inputExpr, ")"), + MarshalType.Int32Array => + source.Append(VariantUtils, ".CreateFromPackedInt32Array(", inputExpr, ")"), + MarshalType.Int64Array => + source.Append(VariantUtils, ".CreateFromPackedInt64Array(", inputExpr, ")"), + MarshalType.Float32Array => + source.Append(VariantUtils, ".CreateFromPackedFloat32Array(", inputExpr, ")"), + MarshalType.Float64Array => + source.Append(VariantUtils, ".CreateFromPackedFloat64Array(", inputExpr, ")"), + MarshalType.StringArray => + source.Append(VariantUtils, ".CreateFromPackedStringArray(", inputExpr, ")"), + MarshalType.Vector2Array => + source.Append(VariantUtils, ".CreateFromPackedVector2Array(", inputExpr, ")"), + MarshalType.Vector3Array => + source.Append(VariantUtils, ".CreateFromPackedVector3Array(", inputExpr, ")"), + MarshalType.ColorArray => + source.Append(VariantUtils, ".CreateFromPackedColorArray(", inputExpr, ")"), + MarshalType.GodotObjectOrDerivedArray => + source.Append(VariantUtils, ".CreateFromSystemArrayOfGodotObject(", inputExpr, ")"), + MarshalType.SystemArrayOfStringName => + source.Append(VariantUtils, ".CreateFromSystemArrayOfStringName(", inputExpr, ")"), + MarshalType.SystemArrayOfNodePath => + source.Append(VariantUtils, ".CreateFromSystemArrayOfNodePath(", inputExpr, ")"), + MarshalType.SystemArrayOfRID => + source.Append(VariantUtils, ".CreateFromSystemArrayOfRID(", inputExpr, ")"), + MarshalType.Variant => + source.Append(inputExpr, ".CopyNativeVariant()"), + MarshalType.GodotObjectOrDerived => + source.Append(VariantUtils, ".CreateFromGodotObject(", inputExpr, ")"), + MarshalType.StringName => + source.Append(VariantUtils, ".CreateFromStringName(", inputExpr, ")"), + MarshalType.NodePath => + source.Append(VariantUtils, ".CreateFromNodePath(", inputExpr, ")"), + MarshalType.RID => + source.Append(VariantUtils, ".CreateFromRID(", inputExpr, ")"), + MarshalType.GodotDictionary => + source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"), + MarshalType.GodotArray => + source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"), + MarshalType.GodotGenericDictionary => + source.Append(VariantUtils, ".CreateFromDictionary(", inputExpr, ")"), + MarshalType.GodotGenericArray => + source.Append(VariantUtils, ".CreateFromArray(", inputExpr, ")"), + _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, + "Received unexpected marshal type") + }; + } + + public static StringBuilder AppendVariantToManagedExpr(this StringBuilder source, + string inputExpr, ITypeSymbol typeSymbol, MarshalType marshalType) + { + return marshalType switch + { + MarshalType.Boolean => source.Append(inputExpr, ".AsBool()"), + MarshalType.Char => source.Append(inputExpr, ".AsChar()"), + MarshalType.SByte => source.Append(inputExpr, ".AsSByte()"), + MarshalType.Int16 => source.Append(inputExpr, ".AsInt16()"), + MarshalType.Int32 => source.Append(inputExpr, ".AsInt32()"), + MarshalType.Int64 => source.Append(inputExpr, ".AsInt64()"), + MarshalType.Byte => source.Append(inputExpr, ".AsByte()"), + MarshalType.UInt16 => source.Append(inputExpr, ".AsUInt16()"), + MarshalType.UInt32 => source.Append(inputExpr, ".AsUInt32()"), + MarshalType.UInt64 => source.Append(inputExpr, ".AsUInt64()"), + MarshalType.Single => source.Append(inputExpr, ".AsSingle()"), + MarshalType.Double => source.Append(inputExpr, ".AsDouble()"), + MarshalType.String => source.Append(inputExpr, ".AsString()"), + MarshalType.Vector2 => source.Append(inputExpr, ".AsVector2()"), + MarshalType.Vector2i => source.Append(inputExpr, ".AsVector2i()"), + MarshalType.Rect2 => source.Append(inputExpr, ".AsRect2()"), + MarshalType.Rect2i => source.Append(inputExpr, ".AsRect2i()"), + MarshalType.Transform2D => source.Append(inputExpr, ".AsTransform2D()"), + MarshalType.Vector3 => source.Append(inputExpr, ".AsVector3()"), + MarshalType.Vector3i => source.Append(inputExpr, ".AsVector3i()"), + MarshalType.Basis => source.Append(inputExpr, ".AsBasis()"), + MarshalType.Quaternion => source.Append(inputExpr, ".AsQuaternion()"), + MarshalType.Transform3D => source.Append(inputExpr, ".AsTransform3D()"), + MarshalType.Vector4 => source.Append(inputExpr, ".AsVector4()"), + MarshalType.Vector4i => source.Append(inputExpr, ".AsVector4i()"), + MarshalType.Projection => source.Append(inputExpr, ".AsProjection()"), + MarshalType.AABB => source.Append(inputExpr, ".AsAABB()"), + MarshalType.Color => source.Append(inputExpr, ".AsColor()"), + MarshalType.Plane => source.Append(inputExpr, ".AsPlane()"), + MarshalType.Callable => source.Append(inputExpr, ".AsCallable()"), + MarshalType.SignalInfo => source.Append(inputExpr, ".AsSignalInfo()"), + MarshalType.Enum => + source.Append("(", typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsInt64()"), + MarshalType.ByteArray => source.Append(inputExpr, ".AsByteArray()"), + MarshalType.Int32Array => source.Append(inputExpr, ".AsInt32Array()"), + MarshalType.Int64Array => source.Append(inputExpr, ".AsInt64Array()"), + MarshalType.Float32Array => source.Append(inputExpr, ".AsFloat32Array()"), + MarshalType.Float64Array => source.Append(inputExpr, ".AsFloat64Array()"), + MarshalType.StringArray => source.Append(inputExpr, ".AsStringArray()"), + MarshalType.Vector2Array => source.Append(inputExpr, ".AsVector2Array()"), + MarshalType.Vector3Array => source.Append(inputExpr, ".AsVector3Array()"), + MarshalType.ColorArray => source.Append(inputExpr, ".AsColorArray()"), + MarshalType.GodotObjectOrDerivedArray => source.Append(inputExpr, ".AsGodotObjectArray<", + ((IArrayTypeSymbol)typeSymbol).ElementType.FullQualifiedName(), ">()"), + MarshalType.SystemArrayOfStringName => source.Append(inputExpr, ".AsSystemArrayOfStringName()"), + MarshalType.SystemArrayOfNodePath => source.Append(inputExpr, ".AsSystemArrayOfNodePath()"), + MarshalType.SystemArrayOfRID => source.Append(inputExpr, ".AsSystemArrayOfRID()"), + MarshalType.Variant => source.Append(inputExpr), + MarshalType.GodotObjectOrDerived => source.Append("(", + typeSymbol.FullQualifiedName(), ")", inputExpr, ".AsGodotObject()"), + MarshalType.StringName => source.Append(inputExpr, ".AsStringName()"), + MarshalType.NodePath => source.Append(inputExpr, ".AsNodePath()"), + MarshalType.RID => source.Append(inputExpr, ".AsRID()"), + MarshalType.GodotDictionary => source.Append(inputExpr, ".AsGodotDictionary()"), + MarshalType.GodotArray => source.Append(inputExpr, ".AsGodotArray()"), + MarshalType.GodotGenericDictionary => source.Append(inputExpr, ".AsGodotDictionary<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ", ", + ((INamedTypeSymbol)typeSymbol).TypeArguments[1].FullQualifiedName(), ">()"), + MarshalType.GodotGenericArray => source.Append(inputExpr, ".AsGodotArray<", + ((INamedTypeSymbol)typeSymbol).TypeArguments[0].FullQualifiedName(), ">()"), + _ => throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, + "Received unexpected marshal type") + }; + } + + public static StringBuilder AppendManagedToVariantExpr(this StringBuilder source, + string inputExpr, MarshalType marshalType) + { + switch (marshalType) + { + case MarshalType.Boolean: + case MarshalType.Char: + case MarshalType.SByte: + case MarshalType.Int16: + case MarshalType.Int32: + case MarshalType.Int64: + case MarshalType.Byte: + case MarshalType.UInt16: + case MarshalType.UInt32: + case MarshalType.UInt64: + case MarshalType.Single: + case MarshalType.Double: + case MarshalType.String: + case MarshalType.Vector2: + case MarshalType.Vector2i: + case MarshalType.Rect2: + case MarshalType.Rect2i: + case MarshalType.Transform2D: + case MarshalType.Vector3: + case MarshalType.Vector3i: + case MarshalType.Basis: + case MarshalType.Quaternion: + case MarshalType.Transform3D: + case MarshalType.Vector4: + case MarshalType.Vector4i: + case MarshalType.Projection: + case MarshalType.AABB: + case MarshalType.Color: + case MarshalType.Plane: + case MarshalType.Callable: + case MarshalType.SignalInfo: + case MarshalType.ByteArray: + case MarshalType.Int32Array: + case MarshalType.Int64Array: + case MarshalType.Float32Array: + case MarshalType.Float64Array: + case MarshalType.StringArray: + case MarshalType.Vector2Array: + case MarshalType.Vector3Array: + case MarshalType.ColorArray: + case MarshalType.GodotObjectOrDerivedArray: + case MarshalType.SystemArrayOfStringName: + case MarshalType.SystemArrayOfNodePath: + case MarshalType.SystemArrayOfRID: + case MarshalType.GodotObjectOrDerived: + case MarshalType.StringName: + case MarshalType.NodePath: + case MarshalType.RID: + case MarshalType.GodotDictionary: + case MarshalType.GodotArray: + case MarshalType.GodotGenericDictionary: + case MarshalType.GodotGenericArray: + return source.Append("Variant.CreateFrom(", inputExpr, ")"); + case MarshalType.Enum: + return source.Append("Variant.CreateFrom((long)", inputExpr, ")"); + case MarshalType.Variant: + return source.Append(inputExpr); + default: + throw new ArgumentOutOfRangeException(nameof(marshalType), marshalType, + "Received unexpected marshal type"); + } + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs new file mode 100644 index 0000000000..81c6f2b7d5 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MethodInfo.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; + +namespace Godot.SourceGenerators +{ + internal struct MethodInfo + { + public MethodInfo(string name, PropertyInfo returnVal, MethodFlags flags, + List<PropertyInfo>? arguments, + List<string?>? defaultArguments) + { + Name = name; + ReturnVal = returnVal; + Flags = flags; + Arguments = arguments; + DefaultArguments = defaultArguments; + } + + public string Name { get; } + public PropertyInfo ReturnVal { get; } + public MethodFlags Flags { get; } + public List<PropertyInfo>? Arguments { get; } + public List<string?>? DefaultArguments { get; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs new file mode 100644 index 0000000000..b345f5f84d --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/PropertyInfo.cs @@ -0,0 +1,23 @@ +namespace Godot.SourceGenerators +{ + internal struct PropertyInfo + { + public PropertyInfo(VariantType type, string name, PropertyHint hint, + string? hintString, PropertyUsageFlags usage, bool exported) + { + Type = type; + Name = name; + Hint = hint; + HintString = hintString; + Usage = usage; + Exported = exported; + } + + public VariantType Type { get; } + public string Name { get; } + public PropertyHint Hint { get; } + public string? HintString { get; } + public PropertyUsageFlags Usage { get; } + public bool Exported { get; } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs new file mode 100644 index 0000000000..1fdc04a262 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMethodsGenerator.cs @@ -0,0 +1,408 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptMethodsGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + private class MethodOverloadEqualityComparer : IEqualityComparer<GodotMethodData> + { + public bool Equals(GodotMethodData x, GodotMethodData y) + => x.ParamTypes.Length == y.ParamTypes.Length && x.Method.Name == y.Method.Name; + + public int GetHashCode(GodotMethodData obj) + { + unchecked + { + return (obj.ParamTypes.Length.GetHashCode() * 397) ^ obj.Method.Name.GetHashCode(); + } + } + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptMethods_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + var members = symbol.GetMembers(); + + var methodSymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared) + .Cast<IMethodSymbol>() + .Where(m => m.MethodKind == MethodKind.Ordinary); + + var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache) + .Distinct(new MethodOverloadEqualityComparer()) + .ToArray(); + + source.Append(" private partial class GodotInternal {\n"); + + // Generate cached StringNames for methods and properties, for fast lookup + + var distinctMethodNames = godotClassMethods + .Select(m => m.Method.Name) + .Distinct() + .ToArray(); + + foreach (string methodName in distinctMethodNames) + { + source.Append(" public static readonly StringName MethodName_"); + source.Append(methodName); + source.Append(" = \""); + source.Append(methodName); + source.Append("\";\n"); + } + + source.Append(" }\n"); // class GodotInternal + + // Generate GetGodotMethodList + + if (godotClassMethods.Length > 0) + { + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + + source.Append(" internal new static ") + .Append(listType) + .Append(" GetGodotMethodList()\n {\n"); + + source.Append(" var methods = new ") + .Append(listType) + .Append("(") + .Append(godotClassMethods.Length) + .Append(");\n"); + + foreach (var method in godotClassMethods) + { + var methodInfo = DetermineMethodInfo(method); + AppendMethodInfo(source, methodInfo); + } + + source.Append(" return methods;\n"); + source.Append(" }\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + + // Generate InvokeGodotClassMethod + + if (godotClassMethods.Length > 0) + { + source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, "); + source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n"); + + foreach (var method in godotClassMethods) + { + GenerateMethodInvoker(method, source); + } + + source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n"); + + source.Append(" }\n"); + } + + // Generate HasGodotClassMethod + + if (distinctMethodNames.Length > 0) + { + source.Append(" protected override bool HasGodotClassMethod(in godot_string_name method)\n {\n"); + + bool isFirstEntry = true; + foreach (string methodName in distinctMethodNames) + { + GenerateHasMethodEntry(methodName, source, isFirstEntry); + isFirstEntry = false; + } + + source.Append(" return base.HasGodotClassMethod(method);\n"); + + source.Append(" }\n"); + } + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) + { + source.Append(" methods.Add(new(name: GodotInternal.MethodName_") + .Append(methodInfo.Name) + .Append(", returnVal: "); + + AppendPropertyInfo(source, methodInfo.ReturnVal); + + source.Append(", flags: (Godot.MethodFlags)") + .Append((int)methodInfo.Flags) + .Append(", arguments: "); + + if (methodInfo.Arguments is { Count: > 0 }) + { + source.Append("new() { "); + + foreach (var param in methodInfo.Arguments) + { + AppendPropertyInfo(source, param); + + // C# allows colon after the last element + source.Append(", "); + } + + source.Append(" }"); + } + else + { + source.Append("null"); + } + + source.Append(", defaultArguments: null));\n"); + } + + private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) + { + source.Append("new(type: (Godot.Variant.Type)") + .Append((int)propertyInfo.Type) + .Append(", name: \"") + .Append(propertyInfo.Name) + .Append("\", hint: (Godot.PropertyHint)") + .Append((int)propertyInfo.Hint) + .Append(", hintString: \"") + .Append(propertyInfo.HintString) + .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append((int)propertyInfo.Usage) + .Append(", exported: ") + .Append(propertyInfo.Exported ? "true" : "false") + .Append(")"); + } + + private static MethodInfo DetermineMethodInfo(GodotMethodData method) + { + PropertyInfo returnVal; + + if (method.RetType != null) + { + returnVal = DeterminePropertyInfo(method.RetType.Value, name: string.Empty); + } + else + { + returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None, + hintString: null, PropertyUsageFlags.Default, exported: false); + } + + int paramCount = method.ParamTypes.Length; + + List<PropertyInfo>? arguments; + + if (paramCount > 0) + { + arguments = new(capacity: paramCount); + + for (int i = 0; i < paramCount; i++) + { + arguments.Add(DeterminePropertyInfo(method.ParamTypes[i], + name: method.Method.Parameters[i].Name)); + } + } + else + { + arguments = null; + } + + return new MethodInfo(method.Method.Name, returnVal, MethodFlags.Default, arguments, + defaultArguments: null); + } + + private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name) + { + var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value; + + var propUsage = PropertyUsageFlags.Default; + + if (memberVariantType == VariantType.Nil) + propUsage |= PropertyUsageFlags.NilIsVariant; + + return new PropertyInfo(memberVariantType, name, + PropertyHint.None, string.Empty, propUsage, exported: false); + } + + private static void GenerateHasMethodEntry( + string methodName, + StringBuilder source, + bool isFirstEntry + ) + { + source.Append(" "); + if (!isFirstEntry) + source.Append("else "); + source.Append("if (method == GodotInternal.MethodName_"); + source.Append(methodName); + source.Append(") {\n return true;\n }\n"); + } + + private static void GenerateMethodInvoker( + GodotMethodData method, + StringBuilder source + ) + { + string methodName = method.Method.Name; + + source.Append(" if (method == GodotInternal.MethodName_"); + source.Append(methodName); + source.Append(" && argCount == "); + source.Append(method.ParamTypes.Length); + source.Append(") {\n"); + + if (method.RetType != null) + source.Append(" var callRet = "); + else + source.Append(" "); + + source.Append(methodName); + source.Append("("); + + for (int i = 0; i < method.ParamTypes.Length; i++) + { + if (i != 0) + source.Append(", "); + + source.AppendNativeVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"), + method.ParamTypeSymbols[i], method.ParamTypes[i]); + } + + source.Append(");\n"); + + if (method.RetType != null) + { + source.Append(" ret = "); + + source.AppendManagedToNativeVariantExpr("callRet", method.RetType.Value); + source.Append(";\n"); + + source.Append(" return true;\n"); + } + else + { + source.Append(" ret = default;\n"); + source.Append(" return true;\n"); + } + + source.Append(" }\n"); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index fa65595290..e8a9e28d0c 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -14,13 +14,13 @@ namespace Godot.SourceGenerators { public void Execute(GeneratorExecutionContext context) { - if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle) - && toggle == "disabled") - { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + if (context.IsGodotToolsProject()) return; - } - // NOTE: IsNullOrEmpty doesn't work well with nullable checks + // NOTE: NotNullWhen diagnostics don't work on projects targeting .NET Standard 2.0 // ReSharper disable once ReplaceWithStringIsNullOrEmpty if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir) || godotProjectDir!.Length == 0) @@ -28,17 +28,18 @@ namespace Godot.SourceGenerators throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty."); } - var godotClasses = context.Compilation.SyntaxTrees + Dictionary<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses = context + .Compilation.SyntaxTrees .SelectMany(tree => tree.GetRoot().DescendantNodes() .OfType<ClassDeclarationSyntax>() // Ignore inner classes - .Where(cds => !(cds.Parent is ClassDeclarationSyntax)) + .Where(cds => !cds.IsNested()) .SelectGodotScriptClasses(context.Compilation) // Report and skip non-partial classes .Where(x => { - if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute()) + if (x.cds.IsPartial()) return true; Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); return false; @@ -89,21 +90,14 @@ namespace Godot.SourceGenerators attributes.Append(@""")]"); } - string className = symbol.Name; - INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? namespaceSymbol.FullQualifiedName() : string.Empty; bool hasNamespace = classNs.Length != 0; - var uniqueName = new StringBuilder(); - if (hasNamespace) - uniqueName.Append($"{classNs}."); - uniqueName.Append(className); - if (symbol.IsGenericType) - uniqueName.Append($"Of{string.Join(string.Empty, symbol.TypeParameters)}"); - uniqueName.Append("_ScriptPath_Generated"); + var uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptPath_Generated"; var source = new StringBuilder(); @@ -123,10 +117,8 @@ namespace Godot.SourceGenerators } source.Append(attributes); - source.Append("\n partial class "); - source.Append(className); - if (symbol.IsGenericType) - source.Append($"<{string.Join(", ", symbol.TypeParameters)}>"); + source.Append("\npartial class "); + source.Append(symbol.NameWithTypeParameters()); source.Append("\n{\n}\n"); if (hasNamespace) @@ -134,7 +126,7 @@ namespace Godot.SourceGenerators source.Append("\n}\n"); } - context.AddSource(uniqueName.ToString(), SourceText.From(source.ToString(), Encoding.UTF8)); + context.AddSource(uniqueHint.ToString(), SourceText.From(source.ToString(), Encoding.UTF8)); } private static void AddScriptTypesAssemblyAttr(GeneratorExecutionContext context, diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs new file mode 100644 index 0000000000..12a369fd72 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs @@ -0,0 +1,615 @@ +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptPropertiesGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptProperties_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + var members = symbol.GetMembers(); + + var propertySymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property) + .Cast<IPropertySymbol>(); + + var fieldSymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared) + .Cast<IFieldSymbol>(); + + var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + + source.Append(" private partial class GodotInternal {\n"); + + // Generate cached StringNames for methods and properties, for fast lookup + + foreach (var property in godotClassProperties) + { + string propertyName = property.PropertySymbol.Name; + source.Append(" public static readonly StringName PropName_"); + source.Append(propertyName); + source.Append(" = \""); + source.Append(propertyName); + source.Append("\";\n"); + } + + foreach (var field in godotClassFields) + { + string fieldName = field.FieldSymbol.Name; + source.Append(" public static readonly StringName PropName_"); + source.Append(fieldName); + source.Append(" = \""); + source.Append(fieldName); + source.Append("\";\n"); + } + + source.Append(" }\n"); // class GodotInternal + + if (godotClassProperties.Length > 0 || godotClassFields.Length > 0) + { + bool isFirstEntry; + + // Generate SetGodotClassPropertyValue + + bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) && + godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly); + + if (!allPropertiesAreReadOnly) + { + source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, "); + source.Append("in godot_variant value)\n {\n"); + + isFirstEntry = true; + foreach (var property in godotClassProperties) + { + if (property.PropertySymbol.IsReadOnly) + continue; + + GeneratePropertySetter(property.PropertySymbol.Name, + property.PropertySymbol.Type, property.Type, source, isFirstEntry); + isFirstEntry = false; + } + + foreach (var field in godotClassFields) + { + if (field.FieldSymbol.IsReadOnly) + continue; + + GeneratePropertySetter(field.FieldSymbol.Name, + field.FieldSymbol.Type, field.Type, source, isFirstEntry); + isFirstEntry = false; + } + + source.Append(" return base.SetGodotClassPropertyValue(name, value);\n"); + + source.Append(" }\n"); + } + + // Generate GetGodotClassPropertyValue + + source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, "); + source.Append("out godot_variant value)\n {\n"); + + isFirstEntry = true; + foreach (var property in godotClassProperties) + { + GeneratePropertyGetter(property.PropertySymbol.Name, + property.Type, source, isFirstEntry); + isFirstEntry = false; + } + + foreach (var field in godotClassFields) + { + GeneratePropertyGetter(field.FieldSymbol.Name, + field.Type, source, isFirstEntry); + isFirstEntry = false; + } + + source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n"); + + source.Append(" }\n"); + + // Generate GetGodotPropertyList + + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + string dictionaryType = "System.Collections.Generic.List<global::Godot.Bridge.PropertyInfo>"; + + source.Append(" internal new static ") + .Append(dictionaryType) + .Append(" GetGodotPropertyList()\n {\n"); + + source.Append(" var properties = new ") + .Append(dictionaryType) + .Append("();\n"); + + foreach (var property in godotClassProperties) + { + var propertyInfo = DeterminePropertyInfo(context, typeCache, + property.PropertySymbol, property.Type); + + if (propertyInfo == null) + continue; + + AppendPropertyInfo(source, propertyInfo.Value); + } + + foreach (var field in godotClassFields) + { + var propertyInfo = DeterminePropertyInfo(context, typeCache, + field.FieldSymbol, field.Type); + + if (propertyInfo == null) + continue; + + AppendPropertyInfo(source, propertyInfo.Value); + } + + source.Append(" return properties;\n"); + source.Append(" }\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static void GeneratePropertySetter( + string propertyMemberName, + ITypeSymbol propertyTypeSymbol, + MarshalType propertyMarshalType, + StringBuilder source, + bool isFirstEntry + ) + { + source.Append(" "); + + if (!isFirstEntry) + source.Append("else "); + + source.Append("if (name == GodotInternal.PropName_") + .Append(propertyMemberName) + .Append(") {\n") + .Append(" ") + .Append(propertyMemberName) + .Append(" = ") + .AppendNativeVariantToManagedExpr("value", propertyTypeSymbol, propertyMarshalType) + .Append(";\n") + .Append(" return true;\n") + .Append(" }\n"); + } + + private static void GeneratePropertyGetter( + string propertyMemberName, + MarshalType propertyMarshalType, + StringBuilder source, + bool isFirstEntry + ) + { + source.Append(" "); + + if (!isFirstEntry) + source.Append("else "); + + source.Append("if (name == GodotInternal.PropName_") + .Append(propertyMemberName) + .Append(") {\n") + .Append(" value = ") + .AppendManagedToNativeVariantExpr(propertyMemberName, propertyMarshalType) + .Append(";\n") + .Append(" return true;\n") + .Append(" }\n"); + } + + private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) + { + source.Append(" properties.Add(new(type: (Godot.Variant.Type)") + .Append((int)propertyInfo.Type) + .Append(", name: GodotInternal.PropName_") + .Append(propertyInfo.Name) + .Append(", hint: (Godot.PropertyHint)") + .Append((int)propertyInfo.Hint) + .Append(", hintString: \"") + .Append(propertyInfo.HintString) + .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append((int)propertyInfo.Usage) + .Append(", exported: ") + .Append(propertyInfo.Exported ? "true" : "false") + .Append("));\n"); + } + + private static PropertyInfo? DeterminePropertyInfo( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + ISymbol memberSymbol, + MarshalType marshalType + ) + { + var exportAttr = memberSymbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.IsGodotExportAttribute() ?? false); + + var propertySymbol = memberSymbol as IPropertySymbol; + var fieldSymbol = memberSymbol as IFieldSymbol; + + if (exportAttr != null && propertySymbol != null) + { + if (propertySymbol.GetMethod == null) + { + // This should never happen, as we filtered WriteOnly properties, but just in case. + Common.ReportExportedMemberIsWriteOnly(context, propertySymbol); + return null; + } + + if (propertySymbol.SetMethod == null) + { + // This should never happen, as we filtered ReadOnly properties, but just in case. + Common.ReportExportedMemberIsReadOnly(context, propertySymbol); + return null; + } + } + + var memberType = propertySymbol?.Type ?? fieldSymbol!.Type; + + var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value; + string memberName = memberSymbol.Name; + + if (exportAttr == null) + { + return new PropertyInfo(memberVariantType, memberName, PropertyHint.None, + hintString: null, PropertyUsageFlags.ScriptVariable, exported: false); + } + + if (!TryGetMemberExportHint(typeCache, memberType, exportAttr, memberVariantType, + isTypeArgument: false, out var hint, out var hintString)) + { + var constructorArguments = exportAttr.ConstructorArguments; + + if (constructorArguments.Length > 0) + { + var hintValue = exportAttr.ConstructorArguments[0].Value; + + hint = hintValue switch + { + null => PropertyHint.None, + int intValue => (PropertyHint)intValue, + _ => (PropertyHint)(long)hintValue + }; + + hintString = constructorArguments.Length > 1 ? + exportAttr.ConstructorArguments[1].Value?.ToString() : + null; + } + else + { + hint = PropertyHint.None; + } + } + + var propUsage = PropertyUsageFlags.Default | PropertyUsageFlags.ScriptVariable; + + if (memberVariantType == VariantType.Nil) + propUsage |= PropertyUsageFlags.NilIsVariant; + + return new PropertyInfo(memberVariantType, memberName, + hint, hintString, propUsage, exported: true); + } + + private static bool TryGetMemberExportHint( + MarshalUtils.TypeCache typeCache, + ITypeSymbol type, AttributeData exportAttr, + VariantType variantType, bool isTypeArgument, + out PropertyHint hint, out string? hintString + ) + { + hint = PropertyHint.None; + hintString = null; + + if (variantType == VariantType.Nil) + return true; // Variant, no export hint + + if (variantType == VariantType.Int && + type.IsValueType && type.TypeKind == TypeKind.Enum) + { + bool hasFlagsAttr = type.GetAttributes() + .Any(a => a.AttributeClass?.IsSystemFlagsAttribute() ?? false); + + hint = hasFlagsAttr ? PropertyHint.Flags : PropertyHint.Enum; + + var members = type.GetMembers(); + + var enumFields = members + .Where(s => s.Kind == SymbolKind.Field && s.IsStatic && + s.DeclaredAccessibility == Accessibility.Public && + !s.IsImplicitlyDeclared) + .Cast<IFieldSymbol>().ToArray(); + + var hintStringBuilder = new StringBuilder(); + var nameOnlyHintStringBuilder = new StringBuilder(); + + // True: enum Foo { Bar, Baz, Qux } + // True: enum Foo { Bar = 0, Baz = 1, Qux = 2 } + // False: enum Foo { Bar = 0, Baz = 7, Qux = 5 } + bool usesDefaultValues = true; + + for (int i = 0; i < enumFields.Length; i++) + { + var enumField = enumFields[i]; + + if (i > 0) + { + hintStringBuilder.Append(","); + nameOnlyHintStringBuilder.Append(","); + } + + string enumFieldName = enumField.Name; + hintStringBuilder.Append(enumFieldName); + nameOnlyHintStringBuilder.Append(enumFieldName); + + long val = enumField.ConstantValue switch + { + sbyte v => v, + short v => v, + int v => v, + long v => v, + byte v => v, + ushort v => v, + uint v => v, + ulong v => (long)v, + _ => 0 + }; + + uint expectedVal = (uint)(hint == PropertyHint.Flags ? 1 << i : i); + if (val != expectedVal) + usesDefaultValues = false; + + hintStringBuilder.Append(":"); + hintStringBuilder.Append(val); + } + + hintString = !usesDefaultValues ? + hintStringBuilder.ToString() : + // If we use the format NAME:VAL, that's what the editor displays. + // That's annoying if the user is not using custom values for the enum constants. + // This may not be needed in the future if the editor is changed to not display values. + nameOnlyHintStringBuilder.ToString(); + + return true; + } + + if (variantType == VariantType.Object && type is INamedTypeSymbol memberNamedType) + { + if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Resource")) + { + string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!; + + hint = PropertyHint.ResourceType; + hintString = nativeTypeName; + + return true; + } + + if (memberNamedType.InheritsFrom("GodotSharp", "Godot.Node")) + { + string nativeTypeName = memberNamedType.GetGodotScriptNativeClassName()!; + + hint = PropertyHint.NodeType; + hintString = nativeTypeName; + + return true; + } + } + + static bool GetStringArrayEnumHint(VariantType elementVariantType, + AttributeData exportAttr, out string? hintString) + { + var constructorArguments = exportAttr.ConstructorArguments; + + if (constructorArguments.Length > 0) + { + var presetHintValue = exportAttr.ConstructorArguments[0].Value; + + PropertyHint presetHint = presetHintValue switch + { + null => PropertyHint.None, + int intValue => (PropertyHint)intValue, + _ => (PropertyHint)(long)presetHintValue + }; + + if (presetHint == PropertyHint.Enum) + { + string? presetHintString = constructorArguments.Length > 1 ? + exportAttr.ConstructorArguments[1].Value?.ToString() : + null; + + hintString = (int)elementVariantType + "/" + (int)PropertyHint.Enum + ":"; + + if (presetHintString != null) + hintString += presetHintString; + + return true; + } + } + + hintString = null; + return false; + } + + if (!isTypeArgument && variantType == VariantType.Array) + { + var elementType = MarshalUtils.GetArrayElementType(type); + + if (elementType == null) + return false; // Non-generic Array, so there's no hint to add + + var elementMarshalType = MarshalUtils.ConvertManagedTypeToMarshalType(elementType, typeCache)!.Value; + var elementVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(elementMarshalType)!.Value; + + bool isPresetHint = false; + + if (elementVariantType == VariantType.String) + isPresetHint = GetStringArrayEnumHint(elementVariantType, exportAttr, out hintString); + + if (!isPresetHint) + { + bool hintRes = TryGetMemberExportHint(typeCache, elementType, + exportAttr, elementVariantType, isTypeArgument: true, + out var elementHint, out var elementHintString); + + // Format: type/hint:hint_string + if (hintRes) + { + hintString = (int)elementVariantType + "/" + (int)elementHint + ":"; + + if (elementHintString != null) + hintString += elementHintString; + } + else + { + hintString = (int)elementVariantType + "/" + (int)PropertyHint.None + ":"; + } + } + + hint = PropertyHint.TypeString; + + return hintString != null; + } + + if (!isTypeArgument && variantType == VariantType.PackedStringArray) + { + if (GetStringArrayEnumHint(VariantType.String, exportAttr, out hintString)) + { + hint = PropertyHint.TypeString; + return true; + } + } + + if (!isTypeArgument && variantType == VariantType.Dictionary) + { + // TODO: Dictionaries are not supported in the inspector + return false; + } + + return false; + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs new file mode 100644 index 0000000000..3b8ba21107 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs @@ -0,0 +1,293 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptPropertyDefValGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptPropertyDefVal_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + var exportedMembers = new List<ExportedPropertyMetadata>(); + + var members = symbol.GetMembers(); + + var exportedProperties = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property) + .Cast<IPropertySymbol>() + .Where(s => s.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotExportAttribute() ?? false)) + .ToArray(); + + var exportedFields = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared) + .Cast<IFieldSymbol>() + .Where(s => s.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotExportAttribute() ?? false)) + .ToArray(); + + foreach (var property in exportedProperties) + { + if (property.IsStatic) + { + Common.ReportExportedMemberIsStatic(context, property); + continue; + } + + // TODO: We should still restore read-only properties after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. + if (property.IsWriteOnly) + { + Common.ReportExportedMemberIsWriteOnly(context, property); + continue; + } + + if (property.IsReadOnly) + { + Common.ReportExportedMemberIsReadOnly(context, property); + continue; + } + + + var propertyType = property.Type; + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(propertyType, typeCache); + + if (marshalType == null) + { + Common.ReportExportedMemberTypeNotSupported(context, property); + continue; + } + + // TODO: Detect default value from simple property getters (currently we only detect from initializers) + + EqualsValueClauseSyntax? initializer = property.DeclaringSyntaxReferences + .Select(r => r.GetSyntax() as PropertyDeclarationSyntax) + .Select(s => s?.Initializer ?? null) + .FirstOrDefault(); + + string? value = initializer?.Value.ToString(); + + exportedMembers.Add(new ExportedPropertyMetadata( + property.Name, marshalType.Value, propertyType, value)); + } + + foreach (var field in exportedFields) + { + if (field.IsStatic) + { + Common.ReportExportedMemberIsStatic(context, field); + continue; + } + + // TODO: We should still restore read-only fields after reloading assembly. Two possible ways: reflection or turn RestoreGodotObjectData into a constructor overload. + // Ignore properties without a getter or without a setter. Godot properties must be both readable and writable. + if (field.IsReadOnly) + { + Common.ReportExportedMemberIsReadOnly(context, field); + continue; + } + + var fieldType = field.Type; + var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(fieldType, typeCache); + + if (marshalType == null) + { + Common.ReportExportedMemberTypeNotSupported(context, field); + continue; + } + + EqualsValueClauseSyntax? initializer = field.DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType<VariableDeclaratorSyntax>() + .Select(s => s.Initializer) + .FirstOrDefault(i => i != null); + + string? value = initializer?.Value.ToString(); + + exportedMembers.Add(new ExportedPropertyMetadata( + field.Name, marshalType.Value, fieldType, value)); + } + + // Generate GetGodotExportedProperties + + if (exportedMembers.Count > 0) + { + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + string dictionaryType = "System.Collections.Generic.Dictionary<StringName, object>"; + + source.Append("#if TOOLS\n"); + source.Append(" internal new static "); + source.Append(dictionaryType); + source.Append(" GetGodotPropertyDefaultValues()\n {\n"); + + source.Append(" var values = new "); + source.Append(dictionaryType); + source.Append("("); + source.Append(exportedMembers.Count); + source.Append(");\n"); + + foreach (var exportedMember in exportedMembers) + { + string defaultValueLocalName = string.Concat("__", exportedMember.Name, "_default_value"); + + source.Append(" "); + source.Append(exportedMember.TypeSymbol.FullQualifiedName()); + source.Append(" "); + source.Append(defaultValueLocalName); + source.Append(" = "); + source.Append(exportedMember.Value ?? "default"); + source.Append(";\n"); + source.Append(" values.Add(GodotInternal.PropName_"); + source.Append(exportedMember.Name); + source.Append(", "); + source.Append(defaultValueLocalName); + source.Append(");\n"); + } + + source.Append(" return values;\n"); + source.Append(" }\n"); + source.Append("#endif\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private struct ExportedPropertyMetadata + { + public ExportedPropertyMetadata(string name, MarshalType type, ITypeSymbol typeSymbol, string? value) + { + Name = name; + Type = type; + TypeSymbol = typeSymbol; + Value = value; + } + + public string Name { get; } + public MarshalType Type { get; } + public ITypeSymbol TypeSymbol { get; } + public string? Value { get; } + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs new file mode 100644 index 0000000000..ec04a319e2 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + // Placeholder. Once we switch to native extensions this will act as the registrar for all + // user Godot classes in the assembly. Think of it as something similar to `register_types`. + public class ScriptRegistrarGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + throw new System.NotImplementedException(); + } + + public void Execute(GeneratorExecutionContext context) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs new file mode 100644 index 0000000000..1b87c6e760 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs @@ -0,0 +1,283 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptSerializationGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptSerialization_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + var members = symbol.GetMembers(); + + var propertySymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property) + .Cast<IPropertySymbol>(); + + var fieldSymbols = members + .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared) + .Cast<IFieldSymbol>(); + + var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray(); + + var signalDelegateSymbols = members + .Where(s => s.Kind == SymbolKind.NamedType) + .Cast<INamedTypeSymbol>() + .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate) + .Where(s => s.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false)); + + List<GodotSignalDelegateData> godotSignalDelegates = new(); + + foreach (var signalDelegateSymbol in signalDelegateSymbols) + { + if (!signalDelegateSymbol.Name.EndsWith(ScriptSignalsGenerator.SignalDelegateSuffix)) + continue; + + string signalName = signalDelegateSymbol.Name; + signalName = signalName.Substring(0, + signalName.Length - ScriptSignalsGenerator.SignalDelegateSuffix.Length); + + var invokeMethodData = signalDelegateSymbol + .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache); + + if (invokeMethodData == null) + continue; + + godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value)); + } + + source.Append( + " protected override void SaveGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n"); + source.Append(" base.SaveGodotObjectData(info);\n"); + + // Save properties + + foreach (var property in godotClassProperties) + { + string propertyName = property.PropertySymbol.Name; + + source.Append(" info.AddProperty(GodotInternal.PropName_") + .Append(propertyName) + .Append(", ") + .AppendManagedToVariantExpr(string.Concat("this.", propertyName), property.Type) + .Append(");\n"); + } + + // Save fields + + foreach (var field in godotClassFields) + { + string fieldName = field.FieldSymbol.Name; + + source.Append(" info.AddProperty(GodotInternal.PropName_") + .Append(fieldName) + .Append(", ") + .AppendManagedToVariantExpr(string.Concat("this.", fieldName), field.Type) + .Append(");\n"); + } + + // Save signal events + + foreach (var signalDelegate in godotSignalDelegates) + { + string signalName = signalDelegate.Name; + + source.Append(" info.AddSignalEventDelegate(GodotInternal.SignalName_") + .Append(signalName) + .Append(", this.backing_") + .Append(signalName) + .Append(");\n"); + } + + source.Append(" }\n"); + + source.Append( + " protected override void RestoreGodotObjectData(global::Godot.Bridge.GodotSerializationInfo info)\n {\n"); + source.Append(" base.RestoreGodotObjectData(info);\n"); + + // Restore properties + + foreach (var property in godotClassProperties) + { + string propertyName = property.PropertySymbol.Name; + + source.Append(" if (info.TryGetProperty(GodotInternal.PropName_") + .Append(propertyName) + .Append(", out var _value_") + .Append(propertyName) + .Append("))\n") + .Append(" this.") + .Append(propertyName) + .Append(" = ") + .AppendVariantToManagedExpr(string.Concat("_value_", propertyName), + property.PropertySymbol.Type, property.Type) + .Append(";\n"); + } + + // Restore fields + + foreach (var field in godotClassFields) + { + string fieldName = field.FieldSymbol.Name; + + source.Append(" if (info.TryGetProperty(GodotInternal.PropName_") + .Append(fieldName) + .Append(", out var _value_") + .Append(fieldName) + .Append("))\n") + .Append(" this.") + .Append(fieldName) + .Append(" = ") + .AppendVariantToManagedExpr(string.Concat("_value_", fieldName), + field.FieldSymbol.Type, field.Type) + .Append(";\n"); + } + + // Restore signal events + + foreach (var signalDelegate in godotSignalDelegates) + { + string signalName = signalDelegate.Name; + string signalDelegateQualifiedName = signalDelegate.DelegateSymbol.FullQualifiedName(); + + source.Append(" if (info.TryGetSignalEventDelegate<") + .Append(signalDelegateQualifiedName) + .Append(">(GodotInternal.SignalName_") + .Append(signalName) + .Append(", out var _value_") + .Append(signalName) + .Append("))\n") + .Append(" this.backing_") + .Append(signalName) + .Append(" = _value_") + .Append(signalName) + .Append(";\n"); + } + + source.Append(" }\n"); + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs new file mode 100644 index 0000000000..536ddb02f8 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSignalsGenerator.cs @@ -0,0 +1,411 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +// TODO: +// Determine a proper way to emit the signal. +// 'Emit(nameof(TheEvent))' creates a StringName everytime and has the overhead of string marshaling. +// I haven't decided on the best option yet. Some possibilities: +// - Expose the generated StringName fields to the user, for use with 'Emit(...)'. +// - Generate a 'EmitSignalName' method for each event signal. + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptSignalsGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + } + + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialGodotScriptOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + if (godotClasses.Length > 0) + { + var typeCache = new MarshalUtils.TypeCache(context); + + foreach (var godotClass in godotClasses) + { + VisitGodotScriptClass(context, typeCache, godotClass); + } + } + } + + internal static string SignalDelegateSuffix = "EventHandler"; + + private static void VisitGodotScriptClass( + GeneratorExecutionContext context, + MarshalUtils.TypeCache typeCache, + INamedTypeSymbol symbol + ) + { + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + bool isInnerClass = symbol.ContainingType != null; + + string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint() + + "_ScriptSignals_Generated"; + + var source = new StringBuilder(); + + source.Append("using Godot;\n"); + source.Append("using Godot.NativeInterop;\n"); + source.Append("\n"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append(" {\n\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("partial class "); + source.Append(symbol.NameWithTypeParameters()); + source.Append("\n{\n"); + + // TODO: + // The delegate name already needs to end with 'Signal' to avoid collision with the event name. + // Requiring SignalAttribute is redundant. Should we remove it to make declaration shorter? + + var members = symbol.GetMembers(); + + var signalDelegateSymbols = members + .Where(s => s.Kind == SymbolKind.NamedType) + .Cast<INamedTypeSymbol>() + .Where(namedTypeSymbol => namedTypeSymbol.TypeKind == TypeKind.Delegate) + .Where(s => s.GetAttributes() + .Any(a => a.AttributeClass?.IsGodotSignalAttribute() ?? false)); + + List<GodotSignalDelegateData> godotSignalDelegates = new(); + + foreach (var signalDelegateSymbol in signalDelegateSymbols) + { + if (!signalDelegateSymbol.Name.EndsWith(SignalDelegateSuffix)) + { + Common.ReportSignalDelegateMissingSuffix(context, signalDelegateSymbol); + continue; + } + + string signalName = signalDelegateSymbol.Name; + signalName = signalName.Substring(0, signalName.Length - SignalDelegateSuffix.Length); + + var invokeMethodData = signalDelegateSymbol + .DelegateInvokeMethod?.HasGodotCompatibleSignature(typeCache); + + if (invokeMethodData == null) + { + // TODO: Better error for incompatible signature. We should indicate incompatible argument types, as we do with exported properties. + Common.ReportSignalDelegateSignatureNotSupported(context, signalDelegateSymbol); + continue; + } + + godotSignalDelegates.Add(new(signalName, signalDelegateSymbol, invokeMethodData.Value)); + } + + source.Append(" private partial class GodotInternal {\n"); + + // Generate cached StringNames for methods and properties, for fast lookup + + foreach (var signalDelegate in godotSignalDelegates) + { + string signalName = signalDelegate.Name; + source.Append(" public static readonly StringName SignalName_"); + source.Append(signalName); + source.Append(" = \""); + source.Append(signalName); + source.Append("\";\n"); + } + + source.Append(" }\n"); // class GodotInternal + + // Generate GetGodotSignalList + + if (godotSignalDelegates.Count > 0) + { + source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n"); + + const string listType = "System.Collections.Generic.List<global::Godot.Bridge.MethodInfo>"; + + source.Append(" internal new static ") + .Append(listType) + .Append(" GetGodotSignalList()\n {\n"); + + source.Append(" var signals = new ") + .Append(listType) + .Append("(") + .Append(godotSignalDelegates.Count) + .Append(");\n"); + + foreach (var signalDelegateData in godotSignalDelegates) + { + var methodInfo = DetermineMethodInfo(signalDelegateData); + AppendMethodInfo(source, methodInfo); + } + + source.Append(" return signals;\n"); + source.Append(" }\n"); + + source.Append("#pragma warning restore CS0109\n"); + } + + // Generate signal event + + foreach (var signalDelegate in godotSignalDelegates) + { + string signalName = signalDelegate.Name; + + // TODO: Hide backing event from code-completion and debugger + // The reason we have a backing field is to hide the invoke method from the event, + // as it doesn't emit the signal, only the event delegates. This can confuse users. + // Maybe we should directly connect the delegates, as we do with native signals? + source.Append(" private ") + .Append(signalDelegate.DelegateSymbol.FullQualifiedName()) + .Append(" backing_") + .Append(signalName) + .Append(";\n"); + + source.Append(" public event ") + .Append(signalDelegate.DelegateSymbol.FullQualifiedName()) + .Append(" ") + .Append(signalName) + .Append(" {\n") + .Append(" add => backing_") + .Append(signalName) + .Append(" += value;\n") + .Append(" remove => backing_") + .Append(signalName) + .Append(" -= value;\n") + .Append("}\n"); + } + + // Generate RaiseGodotClassSignalCallbacks + + if (godotSignalDelegates.Count > 0) + { + source.Append( + " protected override void RaiseGodotClassSignalCallbacks(in godot_string_name signal, "); + source.Append("NativeVariantPtrArgs args, int argCount)\n {\n"); + + foreach (var signal in godotSignalDelegates) + { + GenerateSignalEventInvoker(signal, source); + } + + source.Append(" base.RaiseGodotClassSignalCallbacks(signal, args, argCount);\n"); + + source.Append(" }\n"); + } + + source.Append("}\n"); // partial class + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static void AppendMethodInfo(StringBuilder source, MethodInfo methodInfo) + { + source.Append(" signals.Add(new(name: GodotInternal.SignalName_") + .Append(methodInfo.Name) + .Append(", returnVal: "); + + AppendPropertyInfo(source, methodInfo.ReturnVal); + + source.Append(", flags: (Godot.MethodFlags)") + .Append((int)methodInfo.Flags) + .Append(", arguments: "); + + if (methodInfo.Arguments is { Count: > 0 }) + { + source.Append("new() { "); + + foreach (var param in methodInfo.Arguments) + { + AppendPropertyInfo(source, param); + + // C# allows colon after the last element + source.Append(", "); + } + + source.Append(" }"); + } + else + { + source.Append("null"); + } + + source.Append(", defaultArguments: null));\n"); + } + + private static void AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo) + { + source.Append("new(type: (Godot.Variant.Type)") + .Append((int)propertyInfo.Type) + .Append(", name: \"") + .Append(propertyInfo.Name) + .Append("\", hint: (Godot.PropertyHint)") + .Append((int)propertyInfo.Hint) + .Append(", hintString: \"") + .Append(propertyInfo.HintString) + .Append("\", usage: (Godot.PropertyUsageFlags)") + .Append((int)propertyInfo.Usage) + .Append(", exported: ") + .Append(propertyInfo.Exported ? "true" : "false") + .Append(")"); + } + + private static MethodInfo DetermineMethodInfo(GodotSignalDelegateData signalDelegateData) + { + var invokeMethodData = signalDelegateData.InvokeMethodData; + + PropertyInfo returnVal; + + if (invokeMethodData.RetType != null) + { + returnVal = DeterminePropertyInfo(invokeMethodData.RetType.Value, name: string.Empty); + } + else + { + returnVal = new PropertyInfo(VariantType.Nil, string.Empty, PropertyHint.None, + hintString: null, PropertyUsageFlags.Default, exported: false); + } + + int paramCount = invokeMethodData.ParamTypes.Length; + + List<PropertyInfo>? arguments; + + if (paramCount > 0) + { + arguments = new(capacity: paramCount); + + for (int i = 0; i < paramCount; i++) + { + arguments.Add(DeterminePropertyInfo(invokeMethodData.ParamTypes[i], + name: invokeMethodData.Method.Parameters[i].Name)); + } + } + else + { + arguments = null; + } + + return new MethodInfo(signalDelegateData.Name, returnVal, MethodFlags.Default, arguments, + defaultArguments: null); + } + + private static PropertyInfo DeterminePropertyInfo(MarshalType marshalType, string name) + { + var memberVariantType = MarshalUtils.ConvertMarshalTypeToVariantType(marshalType)!.Value; + + var propUsage = PropertyUsageFlags.Default; + + if (memberVariantType == VariantType.Nil) + propUsage |= PropertyUsageFlags.NilIsVariant; + + return new PropertyInfo(memberVariantType, name, + PropertyHint.None, string.Empty, propUsage, exported: false); + } + + private static void GenerateSignalEventInvoker( + GodotSignalDelegateData signal, + StringBuilder source + ) + { + string signalName = signal.Name; + var invokeMethodData = signal.InvokeMethodData; + + source.Append(" if (signal == GodotInternal.SignalName_"); + source.Append(signalName); + source.Append(" && argCount == "); + source.Append(invokeMethodData.ParamTypes.Length); + source.Append(") {\n"); + source.Append(" backing_"); + source.Append(signalName); + source.Append("?.Invoke("); + + for (int i = 0; i < invokeMethodData.ParamTypes.Length; i++) + { + if (i != 0) + source.Append(", "); + + source.AppendNativeVariantToManagedExpr(string.Concat("args[", i.ToString(), "]"), + invokeMethodData.ParamTypeSymbols[i], invokeMethodData.ParamTypes[i]); + } + + source.Append(");\n"); + + source.Append(" return;\n"); + + source.Append(" }\n"); + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs index 2bf1cb7a18..01aa65bfc3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotBuildLogger.cs @@ -7,8 +7,6 @@ namespace GodotTools.BuildLogger { public class GodotBuildLogger : ILogger { - public static readonly string AssemblyPath = Path.GetFullPath(typeof(GodotBuildLogger).Assembly.Location); - public string Parameters { get; set; } public LoggerVerbosity Verbosity { get; set; } diff --git a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj index 0afec970c6..9e36497b06 100644 --- a/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.BuildLogger/GodotTools.BuildLogger.csproj @@ -5,6 +5,6 @@ <LangVersion>7.2</LangVersion> </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Build.Framework" Version="16.5.0" /> + <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" ExcludeAssets="runtime" /> </ItemGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj index d6d8962f90..cfd5c88a58 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Core/GodotTools.Core.csproj @@ -1,7 +1,7 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{639E48BD-44E5-4091-8EDD-22D36DC0768D}</ProjectGuid> - <TargetFramework>netstandard2.0</TargetFramework> - <LangVersion>7.2</LangVersion> + <TargetFramework>net6.0</TargetFramework> + <LangVersion>10</LangVersion> </PropertyGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj index 303ca3a293..d2132115f3 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{B06C2951-C8E3-4F28-80B2-717CF327EB19}</ProjectGuid> <OutputType>Exe</OutputType> diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj index 5b3ed0b1b7..c05096bdcc 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/GodotTools.OpenVisualStudio.csproj @@ -1,4 +1,4 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{EAFFF236-FA96-4A4D-BD23-0E51EF988277}</ProjectGuid> <OutputType>Exe</OutputType> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj index 37123ba2b2..bde14b2b40 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/GodotTools.ProjectEditor.csproj @@ -1,32 +1,16 @@ -<Project Sdk="Microsoft.NET.Sdk"> +<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}</ProjectGuid> - <TargetFramework>net472</TargetFramework> - <LangVersion>7.2</LangVersion> + <TargetFramework>net6.0</TargetFramework> + <LangVersion>10</LangVersion> </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Build" Version="16.5.0" /> + <PackageReference Include="Microsoft.Build" Version="15.1.548" ExcludeAssets="runtime" /> + <PackageReference Include="Microsoft.Build.Locator" Version="1.2.6" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\GodotTools.Core\GodotTools.Core.csproj" /> <ProjectReference Include="..\GodotTools.Shared\GodotTools.Shared.csproj" /> </ItemGroup> - <!-- - The Microsoft.Build.Runtime package is too problematic so we create a MSBuild.exe stub. The workaround described - here doesn't work with Microsoft.NETFramework.ReferenceAssemblies: https://github.com/microsoft/msbuild/issues/3486 - We need a MSBuild.exe file as there's an issue in Microsoft.Build where it executes platform dependent code when - searching for MSBuild.exe before the fallback to not using it. A stub is fine as it should never be executed. - --> - <ItemGroup> - <None Include="MSBuild.exe" CopyToOutputDirectory="Always" /> - </ItemGroup> - <Target Name="CopyMSBuildStubWindows" AfterTargets="Build" Condition=" '$(GodotPlatform)' == 'windows' Or ( '$(GodotPlatform)' == '' And '$(OS)' == 'Windows_NT' ) "> - <PropertyGroup> - <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> - <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir> - </PropertyGroup> - <!-- Need to copy it here as well on Windows --> - <Copy SourceFiles="MSBuild.exe" DestinationFiles="$(GodotOutputDataDir)\Mono\lib\mono\v4.0\MSBuild.exe" /> - </Target> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe deleted file mode 100644 index e69de29bb2..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/MSBuild.exe +++ /dev/null diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs index 7d49d251dd..fb6d2a707b 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Text; using Microsoft.Build.Construction; @@ -21,7 +22,8 @@ namespace GodotTools.ProjectEditor root.Sdk = GodotSdkAttrValue; var mainGroup = root.AddPropertyGroup(); - mainGroup.AddProperty("TargetFramework", "netstandard2.1"); + mainGroup.AddProperty("TargetFramework", "net6.0"); + mainGroup.AddProperty("EnableDynamicLoading", "true"); string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true); @@ -44,7 +46,7 @@ namespace GodotTools.ProjectEditor // Save (without BOM) root.Save(path, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); - return Guid.NewGuid().ToString().ToUpper(); + return Guid.NewGuid().ToString().ToUpperInvariant(); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs index cdac9acb25..7b1d5c228a 100644 --- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs @@ -19,6 +19,16 @@ namespace GodotTools.ProjectEditor public static class ProjectUtils { + public static void MSBuildLocatorRegisterDefaults(out Version version, out string path) + { + var instance = Microsoft.Build.Locator.MSBuildLocator.RegisterDefaults(); + version = instance.Version; + path = instance.MSBuildPath; + } + + public static void MSBuildLocatorRegisterMSBuildPath(string msbuildPath) + => Microsoft.Build.Locator.MSBuildLocator.RegisterMSBuildPath(msbuildPath); + public static MSBuildProject Open(string path) { var root = ProjectRootElement.Open(path); @@ -42,7 +52,8 @@ namespace GodotTools.ProjectEditor var root = project.Root; string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue; - if (!string.IsNullOrEmpty(root.Sdk) && root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(root.Sdk) && + root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase)) return; root.Sdk = godotSdkAttrValue; diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets index aab2d73bdd..4baae77b34 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GenerateGodotNupkgsVersions.targets @@ -8,8 +8,8 @@ </Target> <Target Name="GenerateGodotNupkgsVersionsFile" - DependsOnTargets="PrepareForBuild;_GenerateGodotNupkgsVersionsFile" - BeforeTargets="BeforeCompile;CoreCompile"> + DependsOnTargets="_GenerateGodotNupkgsVersionsFile" + BeforeTargets="PrepareForBuild;CompileDesignTime;BeforeCompile;CoreCompile"> <ItemGroup> <Compile Include="$(GeneratedGodotNupkgsVersionsFile)" /> <FileWrites Include="$(GeneratedGodotNupkgsVersionsFile)" /> diff --git a/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj index 3bc1698c15..d60e6343ea 100644 --- a/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.Shared/GodotTools.Shared.csproj @@ -1,6 +1,8 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>netstandard2.0</TargetFramework> + <TargetFramework>net6.0</TargetFramework> + <!-- Specify compile items manually to avoid including dangling generated items. --> + <EnableDefaultCompileItems>false</EnableDefaultCompileItems> </PropertyGroup> <Import Project="GenerateGodotNupkgsVersions.targets" /> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.sln b/modules/mono/editor/GodotTools/GodotTools.sln index d3107a69db..564775635d 100644 --- a/modules/mono/editor/GodotTools/GodotTools.sln +++ b/modules/mono/editor/GodotTools/GodotTools.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.ProjectEditor", "GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj", "{A8CDAD94-C6D4-4B19-A7E7-76C53CC92984}" @@ -15,6 +15,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.OpenVisualStudio EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotTools.Shared", "GodotTools.Shared\GodotTools.Shared.csproj", "{2758FFAF-8237-4CF2-B569-66BF8B3587BB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators", "..\Godot.NET.Sdk\Godot.SourceGenerators\Godot.SourceGenerators.csproj", "{D8C421B2-8911-41EB-B983-F675C7141EB7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Internal", "..\..\glue\GodotSharp\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj", "{55666071-BEC1-4A52-8A98-9A4A7A947DBF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -49,5 +53,13 @@ Global {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Debug|Any CPU.Build.0 = Debug|Any CPU {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.ActiveCfg = Release|Any CPU {2758FFAF-8237-4CF2-B569-66BF8B3587BB}.Release|Any CPU.Build.0 = Release|Any CPU + {D8C421B2-8911-41EB-B983-F675C7141EB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D8C421B2-8911-41EB-B983-F675C7141EB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D8C421B2-8911-41EB-B983-F675C7141EB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D8C421B2-8911-41EB-B983-F675C7141EB7}.Release|Any CPU.Build.0 = Release|Any CPU + {55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55666071-BEC1-4A52-8A98-9A4A7A947DBF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs index 28bf57dc21..3c5b897719 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs @@ -4,26 +4,36 @@ using Godot.Collections; using GodotTools.Internals; using Path = System.IO.Path; +#nullable enable + namespace GodotTools.Build { [Serializable] - public sealed class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization + public sealed partial class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization { - public string Solution { get; } - public string[] Targets { get; } - public string Configuration { get; } - public bool Restore { get; } + public string Solution { get; private set; } + public string Configuration { get; private set; } + public string? RuntimeIdentifier { get; private set; } + public string? PublishOutputDir { get; private set; } + public bool Restore { get; private set; } + public bool Rebuild { get; private set; } + public bool OnlyClean { get; private set; } + // TODO Use List once we have proper serialization - public Array<string> CustomProperties { get; } = new Array<string>(); + public Godot.Collections.Array CustomProperties { get; private set; } = new(); - public string LogsDirPath => Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); + public string LogsDirPath => + Path.Combine(GodotSharpDirs.BuildLogsDirs, $"{Solution.MD5Text()}_{Configuration}"); - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is BuildInfo other) - return other.Solution == Solution && other.Targets == Targets && - other.Configuration == Configuration && other.Restore == Restore && - other.CustomProperties == CustomProperties && other.LogsDirPath == LogsDirPath; + return other.Solution == Solution && + other.Configuration == Configuration && other.RuntimeIdentifier == RuntimeIdentifier && + other.PublishOutputDir == PublishOutputDir && other.Restore == Restore && + other.Rebuild == Rebuild && other.OnlyClean == OnlyClean && + other.CustomProperties == CustomProperties && + other.LogsDirPath == LogsDirPath; return false; } @@ -34,25 +44,44 @@ namespace GodotTools.Build { int hash = 17; hash = (hash * 29) + Solution.GetHashCode(); - hash = (hash * 29) + Targets.GetHashCode(); hash = (hash * 29) + Configuration.GetHashCode(); + hash = (hash * 29) + (RuntimeIdentifier?.GetHashCode() ?? 0); + hash = (hash * 29) + (PublishOutputDir?.GetHashCode() ?? 0); hash = (hash * 29) + Restore.GetHashCode(); + hash = (hash * 29) + Rebuild.GetHashCode(); + hash = (hash * 29) + OnlyClean.GetHashCode(); hash = (hash * 29) + CustomProperties.GetHashCode(); hash = (hash * 29) + LogsDirPath.GetHashCode(); return hash; } } + // Needed for instantiation from Godot, after reloading assemblies private BuildInfo() { + Solution = string.Empty; + Configuration = string.Empty; + } + + public BuildInfo(string solution, string configuration, bool restore, bool rebuild, bool onlyClean) + { + Solution = solution; + Configuration = configuration; + Restore = restore; + Rebuild = rebuild; + OnlyClean = onlyClean; } - public BuildInfo(string solution, string[] targets, string configuration, bool restore) + public BuildInfo(string solution, string configuration, string runtimeIdentifier, + string publishOutputDir, bool restore, bool rebuild, bool onlyClean) { Solution = solution; - Targets = targets; Configuration = configuration; + RuntimeIdentifier = runtimeIdentifier; + PublishOutputDir = publishOutputDir; Restore = restore; + Rebuild = rebuild; + OnlyClean = onlyClean; } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs index 21bff70b15..43256953f5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs @@ -1,12 +1,10 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading.Tasks; -using GodotTools.Ides.Rider; +using Godot; using GodotTools.Internals; -using JetBrains.Annotations; -using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; -using OS = GodotTools.Utils.OS; namespace GodotTools.Build { @@ -14,13 +12,8 @@ namespace GodotTools.Build { private static BuildInfo _buildInProgress; - public const string PropNameMSBuildMono = "MSBuild (Mono)"; - public const string PropNameMSBuildVs = "MSBuild (VS Build Tools)"; - public const string PropNameMSBuildJetBrains = "MSBuild (JetBrains Rider)"; - public const string PropNameDotnetCli = "dotnet CLI"; - public const string MsBuildIssuesFileName = "msbuild_issues.csv"; - public const string MsBuildLogFileName = "msbuild_log.txt"; + private const string MsBuildLogFileName = "msbuild_log.txt"; public delegate void BuildLaunchFailedEventHandler(BuildInfo buildInfo, string reason); @@ -62,11 +55,11 @@ namespace GodotTools.Build private static void PrintVerbose(string text) { - if (Godot.OS.IsStdoutVerbose()) - Godot.GD.Print(text); + if (OS.IsStdoutVerbose()) + GD.Print(text); } - public static bool Build(BuildInfo buildInfo) + private static bool Build(BuildInfo buildInfo) { if (_buildInProgress != null) throw new InvalidOperationException("A build is already in progress"); @@ -103,7 +96,8 @@ namespace GodotTools.Build } catch (Exception e) { - BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + BuildLaunchFailed?.Invoke(buildInfo, + $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); Console.Error.WriteLine(e); return false; } @@ -148,7 +142,8 @@ namespace GodotTools.Build } catch (Exception e) { - BuildLaunchFailed?.Invoke(buildInfo, $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + BuildLaunchFailed?.Invoke(buildInfo, + $"The build method threw an exception.\n{e.GetType().FullName}: {e.Message}"); Console.Error.WriteLine(e); return false; } @@ -159,18 +154,54 @@ namespace GodotTools.Build } } - public static bool BuildProjectBlocking(string config, [CanBeNull] string[] targets = null, [CanBeNull] string platform = null) + private static bool Publish(BuildInfo buildInfo) { - var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets ?? new[] {"Build"}, config, restore: true); + if (_buildInProgress != null) + throw new InvalidOperationException("A build is already in progress"); - // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it. - if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform)) - buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); + _buildInProgress = buildInfo; - if (Internal.GodotIsRealTDouble()) - buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); + try + { + BuildStarted?.Invoke(buildInfo); + + // Required in order to update the build tasks list + Internal.GodotMainIteration(); + + try + { + RemoveOldIssuesFile(buildInfo); + } + catch (IOException e) + { + BuildLaunchFailed?.Invoke(buildInfo, $"Cannot remove issues file: {GetIssuesFilePath(buildInfo)}"); + Console.Error.WriteLine(e); + } + + try + { + int exitCode = BuildSystem.Publish(buildInfo, StdOutputReceived, StdErrorReceived); + + if (exitCode != 0) + PrintVerbose( + $"dotnet publish exited with code: {exitCode}. Log file: {GetLogFilePath(buildInfo)}"); - return BuildProjectBlocking(buildInfo); + BuildFinished?.Invoke(exitCode == 0 ? BuildResult.Success : BuildResult.Error); + + return exitCode == 0; + } + catch (Exception e) + { + BuildLaunchFailed?.Invoke(buildInfo, + $"The publish method threw an exception.\n{e.GetType().FullName}: {e.Message}"); + Console.Error.WriteLine(e); + return false; + } + } + finally + { + _buildInProgress = null; + } } private static bool BuildProjectBlocking(BuildInfo buildInfo) @@ -178,31 +209,109 @@ namespace GodotTools.Build if (!File.Exists(buildInfo.Solution)) return true; // No solution to build - // Make sure the API assemblies are up to date before building the project. - // We may not have had the chance to update the release API assemblies, and the debug ones - // may have been deleted by the user at some point after they were loaded by the Godot editor. - string apiAssembliesUpdateError = Internal.UpdateApiAssembliesFromPrebuilt(buildInfo.Configuration == "ExportRelease" ? "Release" : "Debug"); + using var pr = new EditorProgress("dotnet_build_project", "Building .NET project...", 1); + + pr.Step("Building project solution", 0); - if (!string.IsNullOrEmpty(apiAssembliesUpdateError)) + if (!Build(buildInfo)) { - ShowBuildErrorDialog("Failed to update the Godot API assemblies"); + ShowBuildErrorDialog("Failed to build project solution"); return false; } - using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1)) + return true; + } + + private static bool CleanProjectBlocking(BuildInfo buildInfo) + { + if (!File.Exists(buildInfo.Solution)) + return true; // No solution to clean + + using var pr = new EditorProgress("dotnet_clean_project", "Cleaning .NET project...", 1); + + pr.Step("Cleaning project solution", 0); + + if (!Build(buildInfo)) { - pr.Step("Building project solution", 0); + ShowBuildErrorDialog("Failed to clean project solution"); + return false; + } - if (!Build(buildInfo)) - { - ShowBuildErrorDialog("Failed to build project solution"); - return false; - } + return true; + } + + private static bool PublishProjectBlocking(BuildInfo buildInfo) + { + using var pr = new EditorProgress("dotnet_publish_project", "Publishing .NET project...", 1); + + pr.Step("Running dotnet publish", 0); + + if (!Publish(buildInfo)) + { + ShowBuildErrorDialog("Failed to publish .NET project"); + return false; } return true; } + private static BuildInfo CreateBuildInfo( + [DisallowNull] string configuration, + [AllowNull] string platform = null, + bool rebuild = false, + bool onlyClean = false + ) + { + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration, + restore: true, rebuild, onlyClean); + + // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it. + if (platform != null || Utils.OS.PlatformNameMap.TryGetValue(OS.GetName(), out platform)) + buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); + + if (Internal.GodotIsRealTDouble()) + buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); + + return buildInfo; + } + + private static BuildInfo CreatePublishBuildInfo( + [DisallowNull] string configuration, + [DisallowNull] string platform, + [DisallowNull] string runtimeIdentifier, + [DisallowNull] string publishOutputDir + ) + { + var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration, + runtimeIdentifier, publishOutputDir, restore: true, rebuild: false, onlyClean: false); + + buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}"); + + if (Internal.GodotIsRealTDouble()) + buildInfo.CustomProperties.Add("GodotRealTIsDouble=true"); + + return buildInfo; + } + + public static bool BuildProjectBlocking( + [DisallowNull] string configuration, + [AllowNull] string platform = null, + bool rebuild = false + ) => BuildProjectBlocking(CreateBuildInfo(configuration, platform, rebuild)); + + public static bool CleanProjectBlocking( + [DisallowNull] string configuration, + [AllowNull] string platform = null + ) => CleanProjectBlocking(CreateBuildInfo(configuration, platform, rebuild: false)); + + public static bool PublishProjectBlocking( + [DisallowNull] string configuration, + [DisallowNull] string platform, + [DisallowNull] string runtimeIdentifier, + string publishOutputDir + ) => PublishProjectBlocking(CreatePublishBuildInfo(configuration, + platform, runtimeIdentifier, publishOutputDir)); + public static bool EditorBuildCallback() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) @@ -215,7 +324,7 @@ namespace GodotTools.Build } catch (Exception e) { - Godot.GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); + GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } if (GodotSharpEditor.Instance.SkipBuildBeforePlaying) @@ -226,47 +335,6 @@ namespace GodotTools.Build public static void Initialize() { - // Build tool settings - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - - BuildTool msbuildDefault; - - if (OS.IsWindows) - { - if (RiderPathManager.IsExternalEditorSetToRider(editorSettings)) - msbuildDefault = BuildTool.JetBrainsMsBuild; - else - msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildVs; - } - else - { - msbuildDefault = !string.IsNullOrEmpty(OS.PathWhich("dotnet")) ? BuildTool.DotnetCli : BuildTool.MsBuildMono; - } - - EditorDef("mono/builds/build_tool", msbuildDefault); - - string hintString; - - if (OS.IsWindows) - { - hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," + - $"{PropNameMSBuildVs}:{(int)BuildTool.MsBuildVs}," + - $"{PropNameMSBuildJetBrains}:{(int)BuildTool.JetBrainsMsBuild}," + - $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}"; - } - else - { - hintString = $"{PropNameMSBuildMono}:{(int)BuildTool.MsBuildMono}," + - $"{PropNameDotnetCli}:{(int)BuildTool.DotnetCli}"; - } - - editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary - { - ["type"] = Godot.Variant.Type.Int, - ["name"] = "mono/builds/build_tool", - ["hint"] = Godot.PropertyHint.Enum, - ["hint_string"] = hintString - }); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs index ebdaca0ce8..96d1fc28bf 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildOutputView.cs @@ -1,17 +1,16 @@ using Godot; using System; -using Godot.Collections; +using System.Diagnostics.CodeAnalysis; using GodotTools.Internals; -using JetBrains.Annotations; using File = GodotTools.Utils.File; using Path = System.IO.Path; namespace GodotTools.Build { - public class BuildOutputView : VBoxContainer, ISerializationListener + public partial class BuildOutputView : VBoxContainer, ISerializationListener { [Serializable] - private class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization + private partial class BuildIssue : RefCounted // TODO Remove RefCounted once we have proper serialization { public bool Warning { get; set; } public string File { get; set; } @@ -22,7 +21,8 @@ namespace GodotTools.Build public string ProjectFile { get; set; } } - [Signal] public event Action BuildStateChanged; + [Signal] + public delegate void BuildStateChangedEventHandler(); public bool HasBuildExited { get; private set; } = false; @@ -58,7 +58,7 @@ namespace GodotTools.Build } // TODO Use List once we have proper serialization. - private readonly Array<BuildIssue> _issues = new Array<BuildIssue>(); + private Godot.Collections.Array<BuildIssue> _issues = new(); private ItemList _issuesList; private PopupMenu _issuesListContextMenu; private TextEdit _buildLog; @@ -123,7 +123,7 @@ namespace GodotTools.Build throw new IndexOutOfRangeException("Item list index out of range"); // Get correct issue idx from issue list - int issueIndex = (int)(long)_issuesList.GetItemMetadata(idx); + int issueIndex = (int)_issuesList.GetItemMetadata(idx); if (issueIndex < 0 || issueIndex >= _issues.Count) throw new IndexOutOfRangeException("Issue index out of range"); @@ -133,7 +133,9 @@ namespace GodotTools.Build if (string.IsNullOrEmpty(issue.ProjectFile) && string.IsNullOrEmpty(issue.File)) return; - string projectDir = issue.ProjectFile.Length > 0 ? issue.ProjectFile.GetBaseDir() : _buildInfo.Solution.GetBaseDir(); + string projectDir = !string.IsNullOrEmpty(issue.ProjectFile) ? + issue.ProjectFile.GetBaseDir() : + _buildInfo.Solution.GetBaseDir(); string file = Path.Combine(projectDir.SimplifyGodotPath(), issue.File.SimplifyGodotPath()); @@ -412,6 +414,16 @@ namespace GodotTools.Build { // In case it didn't update yet. We don't want to have to serialize any pending output. UpdateBuildLogText(); + + // NOTE: + // Currently, GodotTools is loaded in its own load context. This load context is not reloaded, but the script still are. + // Until that changes, we need workarounds like this one because events keep strong references to disposed objects. + BuildManager.BuildLaunchFailed -= BuildLaunchFailed; + BuildManager.BuildStarted -= BuildStarted; + BuildManager.BuildFinished -= BuildFinished; + // StdOutput/Error can be received from different threads, so we need to use CallDeferred + BuildManager.StdOutputReceived -= StdOutputReceived; + BuildManager.StdErrorReceived -= StdErrorReceived; } public void OnAfterDeserialize() diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs index 02e9d98647..655be0ab5e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs @@ -1,61 +1,93 @@ -using GodotTools.Core; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Text; using System.Threading.Tasks; using GodotTools.BuildLogger; -using GodotTools.Internals; using GodotTools.Utils; -using Directory = System.IO.Directory; namespace GodotTools.Build { public static class BuildSystem { - private static string MonoWindowsBinDir + private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, + Action<string> stdErrHandler) { - get - { - string monoWinBinDir = Path.Combine(Internal.MonoWindowsInstallRoot, "bin"); + string dotnetPath = DotNetFinder.FindDotNetExe(); - if (!Directory.Exists(monoWinBinDir)) - throw new FileNotFoundException("Cannot find the Windows Mono install bin directory."); + if (dotnetPath == null) + throw new FileNotFoundException("Cannot find the dotnet executable."); - return monoWinBinDir; - } + var startInfo = new ProcessStartInfo(dotnetPath); + + BuildArguments(buildInfo, startInfo.ArgumentList); + + string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString(); + stdOutHandler?.Invoke(launchMessage); + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine(launchMessage); + + startInfo.RedirectStandardOutput = true; + startInfo.RedirectStandardError = true; + startInfo.UseShellExecute = false; + startInfo.CreateNoWindow = true; + + // Needed when running from Developer Command Prompt for VS + RemovePlatformVariable(startInfo.EnvironmentVariables); + + var process = new Process { StartInfo = startInfo }; + + if (stdOutHandler != null) + process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data); + if (stdErrHandler != null) + process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data); + + process.Start(); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + return process; } - private static Godot.EditorSettings EditorSettings => - GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); + public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + { + using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) + { + process.WaitForExit(); + + return process.ExitCode; + } + } - private static bool UsingMonoMsBuildOnWindows + public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, + Action<string> stdErrHandler) { - get + using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) { - if (OS.IsWindows) - { - return (BuildTool)EditorSettings.GetSetting("mono/builds/build_tool") - == BuildTool.MsBuildMono; - } + await process.WaitForExitAsync(); - return false; + return process.ExitCode; } } - private static Process LaunchBuild(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + private static Process LaunchPublish(BuildInfo buildInfo, Action<string> stdOutHandler, + Action<string> stdErrHandler) { - (string msbuildPath, BuildTool buildTool) = MsBuildFinder.FindMsBuild(); + string dotnetPath = DotNetFinder.FindDotNetExe(); - if (msbuildPath == null) - throw new FileNotFoundException("Cannot find the MSBuild executable."); + if (dotnetPath == null) + throw new FileNotFoundException("Cannot find the dotnet executable."); - string compilerArgs = BuildArguments(buildTool, buildInfo); + var startInfo = new ProcessStartInfo(dotnetPath); - var startInfo = new ProcessStartInfo(msbuildPath, compilerArgs); + BuildPublishArguments(buildInfo, startInfo.ArgumentList); - string launchMessage = $"Running: \"{startInfo.FileName}\" {startInfo.Arguments}"; + string launchMessage = startInfo.GetCommandLineDisplay(new StringBuilder("Running: ")).ToString(); stdOutHandler?.Invoke(launchMessage); if (Godot.OS.IsStdoutVerbose()) Console.WriteLine(launchMessage); @@ -63,27 +95,16 @@ namespace GodotTools.Build startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - - if (UsingMonoMsBuildOnWindows) - { - // These environment variables are required for Mono's MSBuild to find the compilers. - // We use the batch files in Mono's bin directory to make sure the compilers are executed with mono. - string monoWinBinDir = MonoWindowsBinDir; - startInfo.EnvironmentVariables.Add("CscToolExe", Path.Combine(monoWinBinDir, "csc.bat")); - startInfo.EnvironmentVariables.Add("VbcToolExe", Path.Combine(monoWinBinDir, "vbc.bat")); - startInfo.EnvironmentVariables.Add("FscToolExe", Path.Combine(monoWinBinDir, "fsharpc.bat")); - } // Needed when running from Developer Command Prompt for VS RemovePlatformVariable(startInfo.EnvironmentVariables); - var process = new Process {StartInfo = startInfo}; + var process = new Process { StartInfo = startInfo }; if (stdOutHandler != null) - process.OutputDataReceived += (s, e) => stdOutHandler.Invoke(e.Data); + process.OutputDataReceived += (_, e) => stdOutHandler.Invoke(e.Data); if (stdErrHandler != null) - process.ErrorDataReceived += (s, e) => stdErrHandler.Invoke(e.Data); + process.ErrorDataReceived += (_, e) => stdErrHandler.Invoke(e.Data); process.Start(); @@ -93,9 +114,9 @@ namespace GodotTools.Build return process; } - public static int Build(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + public static int Publish(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) { - using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) + using (var process = LaunchPublish(buildInfo, stdOutHandler, stdErrHandler)) { process.WaitForExit(); @@ -103,38 +124,101 @@ namespace GodotTools.Build } } - public static async Task<int> BuildAsync(BuildInfo buildInfo, Action<string> stdOutHandler, Action<string> stdErrHandler) + private static void BuildArguments(BuildInfo buildInfo, Collection<string> arguments) { - using (var process = LaunchBuild(buildInfo, stdOutHandler, stdErrHandler)) + // `dotnet clean` / `dotnet build` commands + arguments.Add(buildInfo.OnlyClean ? "clean" : "build"); + + // Solution + arguments.Add(buildInfo.Solution); + + // `dotnet clean` doesn't recognize these options + if (!buildInfo.OnlyClean) { - await process.WaitForExitAsync(); + // Restore + // `dotnet build` restores by default, unless requested not to + if (!buildInfo.Restore) + arguments.Add("--no-restore"); + + // Incremental or rebuild + if (buildInfo.Rebuild) + arguments.Add("--no-incremental"); + } - return process.ExitCode; + // Configuration + arguments.Add("-c"); + arguments.Add(buildInfo.Configuration); + + // Verbosity + arguments.Add("-v"); + arguments.Add("normal"); + + // Logger + AddLoggerArgument(buildInfo, arguments); + + // Custom properties + foreach (var customProperty in buildInfo.CustomProperties) + { + arguments.Add("-p:" + (string)customProperty); } } - private static string BuildArguments(BuildTool buildTool, BuildInfo buildInfo) + private static void BuildPublishArguments(BuildInfo buildInfo, Collection<string> arguments) { - string arguments = string.Empty; + arguments.Add("publish"); // `dotnet publish` command + + // Solution + arguments.Add(buildInfo.Solution); + + // Restore + // `dotnet publish` restores by default, unless requested not to + if (!buildInfo.Restore) + arguments.Add("--no-restore"); - if (buildTool == BuildTool.DotnetCli) - arguments += "msbuild"; // `dotnet msbuild` command + // Incremental or rebuild + if (buildInfo.Rebuild) + arguments.Add("--no-incremental"); - arguments += $@" ""{buildInfo.Solution}"""; + // Configuration + arguments.Add("-c"); + arguments.Add(buildInfo.Configuration); - if (buildInfo.Restore) - arguments += " /restore"; + // Runtime Identifier + arguments.Add("-r"); + arguments.Add(buildInfo.RuntimeIdentifier!); - arguments += $@" /t:{string.Join(",", buildInfo.Targets)} " + - $@"""/p:{"Configuration=" + buildInfo.Configuration}"" /v:normal " + - $@"""/l:{typeof(GodotBuildLogger).FullName},{GodotBuildLogger.AssemblyPath};{buildInfo.LogsDirPath}"""; + // Self-published + arguments.Add("--self-contained"); + arguments.Add("true"); - foreach (string customProperty in buildInfo.CustomProperties) + // Verbosity + arguments.Add("-v"); + arguments.Add("normal"); + + // Logger + AddLoggerArgument(buildInfo, arguments); + + // Custom properties + foreach (var customProperty in buildInfo.CustomProperties) + { + arguments.Add("-p:" + (string)customProperty); + } + + // Publish output directory + if (buildInfo.PublishOutputDir != null) { - arguments += " /p:" + customProperty; + arguments.Add("-o"); + arguments.Add(buildInfo.PublishOutputDir); } + } + + private static void AddLoggerArgument(BuildInfo buildInfo, Collection<string> arguments) + { + string buildLoggerPath = Path.Combine(Internals.GodotSharpDirs.DataEditorToolsDir, + "GodotTools.BuildLogger.dll"); - return arguments; + arguments.Add( + $"-l:{typeof(GodotBuildLogger).FullName},{buildLoggerPath};{buildInfo.LogsDirPath}"); } private static void RemovePlatformVariable(StringDictionary environmentVariables) @@ -145,7 +229,7 @@ namespace GodotTools.Build foreach (string env in environmentVariables.Keys) { - if (env.ToUpper() == "PLATFORM") + if (env.ToUpperInvariant() == "PLATFORM") platformEnvironmentVariables.Add(env); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs deleted file mode 100644 index 837c8adddb..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildTool.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GodotTools.Build -{ - public enum BuildTool : long - { - MsBuildMono, - MsBuildVs, - JetBrainsMsBuild, - DotnetCli - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs new file mode 100644 index 0000000000..7bce53308c --- /dev/null +++ b/modules/mono/editor/GodotTools/GodotTools/Build/DotNetFinder.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using JetBrains.Annotations; +using OS = GodotTools.Utils.OS; + +namespace GodotTools.Build +{ + public static class DotNetFinder + { + [CanBeNull] + public static string FindDotNetExe() + { + // In the future, this method may do more than just search in PATH. We could look in + // known locations or use Godot's linked nethost to search from the hostfxr location. + + return OS.PathWhich("dotnet"); + } + + public static bool TryFindDotNetSdk( + Version expectedVersion, + [NotNullWhen(true)] out Version version, + [NotNullWhen(true)] out string path + ) + { + version = null; + path = null; + + string dotNetExe = FindDotNetExe(); + + if (string.IsNullOrEmpty(dotNetExe)) + return false; + + using Process process = new Process(); + process.StartInfo = new ProcessStartInfo(dotNetExe, "--list-sdks") + { + UseShellExecute = false, + RedirectStandardOutput = true + }; + + process.StartInfo.EnvironmentVariables["DOTNET_CLI_UI_LANGUAGE"] = "en-US"; + + var lines = new List<string>(); + + process.OutputDataReceived += (_, e) => + { + if (!string.IsNullOrWhiteSpace(e.Data)) + lines.Add(e.Data); + }; + + try + { + process.Start(); + } + catch + { + return false; + } + + process.BeginOutputReadLine(); + process.WaitForExit(); + + Version latestVersionMatch = null; + string matchPath = null; + + foreach (var line in lines) + { + string[] sdkLineParts = line.Trim() + .Split(' ', 2, StringSplitOptions.TrimEntries); + + if (sdkLineParts.Length < 2) + continue; + + if (!Version.TryParse(sdkLineParts[0], out var lineVersion)) + continue; + + // We're looking for the exact same major version + if (lineVersion.Major != expectedVersion.Major) + continue; + + if (latestVersionMatch != null && lineVersion < latestVersionMatch) + continue; + + latestVersionMatch = lineVersion; + matchPath = sdkLineParts[1].TrimStart('[').TrimEnd(']'); + } + + if (latestVersionMatch == null) + return false; + + version = latestVersionMatch; + path = Path.Combine(matchPath!, version.ToString()); + + return true; + } + } +} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 3c020a2589..6dae0a3a0e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -1,13 +1,12 @@ using System; using Godot; using GodotTools.Internals; -using JetBrains.Annotations; using static GodotTools.Internals.Globals; using File = GodotTools.Utils.File; namespace GodotTools.Build { - public class MSBuildPanel : VBoxContainer + public partial class MSBuildPanel : VBoxContainer { public BuildOutputView BuildOutputView { get; private set; } @@ -28,7 +27,6 @@ namespace GodotTools.Build BuildOutputView.UpdateIssuesList(); } - [UsedImplicitly] public void BuildSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) @@ -57,7 +55,6 @@ namespace GodotTools.Build Internal.ReloadAssemblies(softReload: false); } - [UsedImplicitly] private void RebuildSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) @@ -73,7 +70,7 @@ namespace GodotTools.Build GD.PushError("Failed to setup Godot NuGet Offline Packages: " + e.Message); } - if (!BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Rebuild" })) + if (!BuildManager.BuildProjectBlocking("Debug", rebuild: true)) return; // Build failed // Notify running game for hot-reload @@ -86,13 +83,12 @@ namespace GodotTools.Build Internal.ReloadAssemblies(softReload: false); } - [UsedImplicitly] private void CleanSolution() { if (!File.Exists(GodotSharpDirs.ProjectSlnPath)) return; // No solution to build - BuildManager.BuildProjectBlocking("Debug", targets: new[] { "Clean" }); + _ = BuildManager.CleanProjectBlocking("Debug"); } private void ViewLogToggled(bool pressed) => BuildOutputView.LogVisible = pressed; diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs deleted file mode 100644 index a859c6f717..0000000000 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MsBuildFinder.cs +++ /dev/null @@ -1,233 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Godot; -using GodotTools.Ides.Rider; -using GodotTools.Internals; -using Directory = System.IO.Directory; -using Environment = System.Environment; -using File = System.IO.File; -using Path = System.IO.Path; -using OS = GodotTools.Utils.OS; - -namespace GodotTools.Build -{ - public static class MsBuildFinder - { - private static string _msbuildToolsPath = string.Empty; - private static string _msbuildUnixPath = string.Empty; - - public static (string, BuildTool) FindMsBuild() - { - var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool"); - - if (OS.IsWindows) - { - switch (buildTool) - { - case BuildTool.DotnetCli: - { - string dotnetCliPath = OS.PathWhich("dotnet"); - if (!string.IsNullOrEmpty(dotnetCliPath)) - return (dotnetCliPath, BuildTool.DotnetCli); - GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Visual Studio."); - goto case BuildTool.MsBuildVs; - } - case BuildTool.MsBuildVs: - { - if (string.IsNullOrEmpty(_msbuildToolsPath) || !File.Exists(_msbuildToolsPath)) - { - // Try to search it again if it wasn't found last time or if it was removed from its location - _msbuildToolsPath = FindMsBuildToolsPathOnWindows(); - - if (string.IsNullOrEmpty(_msbuildToolsPath)) - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildVs}'."); - } - - if (!_msbuildToolsPath.EndsWith("\\")) - _msbuildToolsPath += "\\"; - - return (Path.Combine(_msbuildToolsPath, "MSBuild.exe"), BuildTool.MsBuildVs); - } - case BuildTool.MsBuildMono: - { - string msbuildPath = Path.Combine(Internal.MonoWindowsInstallRoot, "bin", "msbuild.bat"); - - if (!File.Exists(msbuildPath)) - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildMono}'. Tried with path: {msbuildPath}"); - - return (msbuildPath, BuildTool.MsBuildMono); - } - case BuildTool.JetBrainsMsBuild: - { - string editorPath = (string)editorSettings.GetSetting(RiderPathManager.EditorPathSettingName); - - if (!File.Exists(editorPath)) - throw new FileNotFoundException($"Cannot find Rider executable. Tried with path: {editorPath}"); - - var riderDir = new FileInfo(editorPath).Directory?.Parent; - - string msbuildPath = Path.Combine(riderDir.FullName, @"tools\MSBuild\Current\Bin\MSBuild.exe"); - - if (!File.Exists(msbuildPath)) - throw new FileNotFoundException($"Cannot find executable for '{BuildManager.PropNameMSBuildJetBrains}'. Tried with path: {msbuildPath}"); - - return (msbuildPath, BuildTool.JetBrainsMsBuild); - } - default: - throw new IndexOutOfRangeException("Invalid build tool in editor settings"); - } - } - - if (OS.IsUnixLike) - { - switch (buildTool) - { - case BuildTool.DotnetCli: - { - string dotnetCliPath = FindBuildEngineOnUnix("dotnet"); - if (!string.IsNullOrEmpty(dotnetCliPath)) - return (dotnetCliPath, BuildTool.DotnetCli); - GD.PushError($"Cannot find executable for '{BuildManager.PropNameDotnetCli}'. Fallback to MSBuild from Mono."); - goto case BuildTool.MsBuildMono; - } - case BuildTool.MsBuildMono: - { - if (string.IsNullOrEmpty(_msbuildUnixPath) || !File.Exists(_msbuildUnixPath)) - { - // Try to search it again if it wasn't found last time or if it was removed from its location - _msbuildUnixPath = FindBuildEngineOnUnix("msbuild"); - } - - if (string.IsNullOrEmpty(_msbuildUnixPath)) - throw new FileNotFoundException($"Cannot find binary for '{BuildManager.PropNameMSBuildMono}'"); - - return (_msbuildUnixPath, BuildTool.MsBuildMono); - } - default: - throw new IndexOutOfRangeException("Invalid build tool in editor settings"); - } - } - - throw new PlatformNotSupportedException(); - } - - private static IEnumerable<string> MsBuildHintDirs - { - get - { - var result = new List<string>(); - - if (OS.IsMacOS) - { - result.Add("/Library/Frameworks/Mono.framework/Versions/Current/bin/"); - result.Add("/opt/local/bin/"); - result.Add("/usr/local/var/homebrew/linked/mono/bin/"); - result.Add("/usr/local/bin/"); - result.Add("/usr/local/bin/dotnet/"); - result.Add("/usr/local/share/dotnet/"); - } - - result.Add("/opt/novell/mono/bin/"); - - return result; - } - } - - private static string FindBuildEngineOnUnix(string name) - { - string ret = OS.PathWhich(name); - - if (!string.IsNullOrEmpty(ret)) - return ret; - - string retFallback = OS.PathWhich($"{name}.exe"); - - if (!string.IsNullOrEmpty(retFallback)) - return retFallback; - - foreach (string hintDir in MsBuildHintDirs) - { - string hintPath = Path.Combine(hintDir, name); - - if (File.Exists(hintPath)) - return hintPath; - } - - return string.Empty; - } - - private static string FindMsBuildToolsPathOnWindows() - { - if (!OS.IsWindows) - throw new PlatformNotSupportedException(); - - // Try to find 15.0 with vswhere - - string[] envNames = Internal.GodotIs32Bits() ? - envNames = new[] { "ProgramFiles", "ProgramW6432" } : - envNames = new[] { "ProgramFiles(x86)", "ProgramFiles" }; - - string vsWherePath = null; - foreach (var envName in envNames) - { - vsWherePath = Environment.GetEnvironmentVariable(envName); - if (!string.IsNullOrEmpty(vsWherePath)) - { - vsWherePath += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; - if (File.Exists(vsWherePath)) - break; - } - - vsWherePath = null; - } - - var vsWhereArgs = new[] {"-latest", "-products", "*", "-requires", "Microsoft.Component.MSBuild"}; - - var outputArray = new Godot.Collections.Array<string>(); - int exitCode = Godot.OS.Execute(vsWherePath, vsWhereArgs, - output: (Godot.Collections.Array)outputArray); - - if (exitCode != 0) - return string.Empty; - - if (outputArray.Count == 0) - return string.Empty; - - var lines = outputArray[0].Split('\n'); - - foreach (string line in lines) - { - int sepIdx = line.IndexOf(':'); - - if (sepIdx <= 0) - continue; - - string key = line.Substring(0, sepIdx); // No need to trim - - if (key != "installationPath") - continue; - - string value = line.Substring(sepIdx + 1).StripEdges(); - - if (string.IsNullOrEmpty(value)) - throw new FormatException("installationPath value is empty"); - - if (!value.EndsWith("\\")) - value += "\\"; - - // Since VS2019, the directory is simply named "Current" - string msbuildDir = Path.Combine(value, "MSBuild\\Current\\Bin"); - - if (Directory.Exists(msbuildDir)) - return msbuildDir; - - // Directory name "15.0" is used in VS 2017 - return Path.Combine(value, "MSBuild\\15.0\\Bin"); - } - - return string.Empty; - } - } -} diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs index 63b97e981e..fdb86c8f34 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/NuGetUtils.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; @@ -39,7 +40,8 @@ namespace GodotTools.Build // Since this can be considered pretty much a new NuGet.Config, add the default nuget.org source as well XmlElement nugetOrgSourceEntry = xmlDoc.CreateElement("add"); nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("key")).Value = "nuget.org"; - nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = "https://api.nuget.org/v3/index.json"; + nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("value")).Value = + "https://api.nuget.org/v3/index.json"; nugetOrgSourceEntry.Attributes.Append(xmlDoc.CreateAttribute("protocolVersion")).Value = "3"; rootNode.AppendChild(xmlDoc.CreateElement("packageSources")).AppendChild(nugetOrgSourceEntry); } @@ -181,8 +183,8 @@ namespace GodotTools.Build // - The sha512 of the nupkg is base64 encoded. // - We can get the nuspec from the nupkg which is a Zip file. - string packageIdLower = packageId.ToLower(); - string packageVersionLower = packageVersion.ToLower(); + string packageIdLower = packageId.ToLowerInvariant(); + string packageVersionLower = packageVersion.ToLowerInvariant(); string destDir = Path.Combine(fallbackFolder, packageIdLower, packageVersionLower); string nupkgDestPath = Path.Combine(destDir, $"{packageIdLower}.{packageVersionLower}.nupkg"); @@ -227,9 +229,11 @@ namespace GodotTools.Build var nuspecEntry = archive.GetEntry(packageId + ".nuspec"); if (nuspecEntry == null) - throw new InvalidOperationException($"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file."); + throw new InvalidOperationException( + $"Failed to extract package {packageId}.{packageVersion}. Could not find the nuspec file."); - nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name.ToLower().SimplifyGodotPath())); + nuspecEntry.ExtractToFile(Path.Combine(destDir, nuspecEntry.Name + .ToLowerInvariant().SimplifyGodotPath())); // Extract the other package files diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs index e9718cc82c..87549f61fe 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/AotBuilder.cs @@ -289,7 +289,7 @@ MONO_AOT_MODE_LAST = 1000, // Archive the AOT object files into a static library var arFilePathsForAllArchs = new List<string>(); - string projectAssemblyName = GodotSharpEditor.ProjectAssemblyName; + string projectAssemblyName = GodotSharpDirs.ProjectAssemblyName; foreach (var archPathsPair in objFilePathsForiOSArch) { diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs index cca18a2a1f..ecf363c106 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs @@ -4,11 +4,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; using GodotTools.Build; using GodotTools.Core; using GodotTools.Internals; -using JetBrains.Annotations; using static GodotTools.Internals.Globals; using Directory = GodotTools.Utils.Directory; using File = GodotTools.Utils.File; @@ -17,61 +15,13 @@ using Path = System.IO.Path; namespace GodotTools.Export { - public class ExportPlugin : EditorExportPlugin + public partial class ExportPlugin : EditorExportPlugin { - [Flags] - private enum I18NCodesets : long - { - None = 0, - CJK = 1, - MidEast = 2, - Other = 4, - Rare = 8, - West = 16, - All = CJK | MidEast | Other | Rare | West - } - - private string _maybeLastExportError; - - private void AddI18NAssemblies(Godot.Collections.Dictionary<string, string> assemblies, string bclDir) - { - var codesets = (I18NCodesets)ProjectSettings.GetSetting("mono/export/i18n_codesets"); - - if (codesets == I18NCodesets.None) - return; - - void AddI18NAssembly(string name) => assemblies.Add(name, Path.Combine(bclDir, $"{name}.dll")); - - AddI18NAssembly("I18N"); - - if ((codesets & I18NCodesets.CJK) != 0) - AddI18NAssembly("I18N.CJK"); - if ((codesets & I18NCodesets.MidEast) != 0) - AddI18NAssembly("I18N.MidEast"); - if ((codesets & I18NCodesets.Other) != 0) - AddI18NAssembly("I18N.Other"); - if ((codesets & I18NCodesets.Rare) != 0) - AddI18NAssembly("I18N.Rare"); - if ((codesets & I18NCodesets.West) != 0) - AddI18NAssembly("I18N.West"); - } - public void RegisterExportSettings() { // TODO: These would be better as export preset options, but that doesn't seem to be supported yet GlobalDef("mono/export/include_scripts_content", false); - GlobalDef("mono/export/export_assemblies_inside_pck", true); - - GlobalDef("mono/export/i18n_codesets", I18NCodesets.All); - - ProjectSettings.AddPropertyInfo(new Godot.Collections.Dictionary - { - ["type"] = Variant.Type.Int, - ["name"] = "mono/export/i18n_codesets", - ["hint"] = PropertyHint.Flags, - ["hint_string"] = "CJK,MidEast,Other,Rare,West" - }); GlobalDef("mono/export/aot/enabled", false); GlobalDef("mono/export/aot/full_aot", false); @@ -85,11 +35,7 @@ namespace GodotTools.Export GlobalDef("mono/export/aot/android_toolchain_path", ""); } - private void AddFile(string srcPath, string dstPath, bool remap = false) - { - // Add file to the PCK - AddFile(dstPath.Replace("\\", "/"), File.ReadAllBytes(srcPath), remap); - } + private string _maybeLastExportError; // With this method we can override how a file is exported in the PCK public override void _ExportFile(string path, string type, string[] features) @@ -100,7 +46,9 @@ namespace GodotTools.Export return; if (Path.GetExtension(path) != Internal.CSharpLanguageExtension) - throw new ArgumentException($"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", nameof(path)); + throw new ArgumentException( + $"Resource of type {Internal.CSharpLanguageType} has an invalid file extension: {path}", + nameof(path)); // TODO What if the source file is not part of the game's C# project @@ -152,159 +100,93 @@ namespace GodotTools.Export if (!DeterminePlatformFromFeatures(features, out string platform)) throw new NotSupportedException("Target platform not supported"); + if (!new[] { OS.Platforms.Windows, OS.Platforms.LinuxBSD, OS.Platforms.MacOS, OS.Platforms.Server } + .Contains(platform)) + { + throw new NotImplementedException("Target platform not yet implemented"); + } + string outputDir = new FileInfo(path).Directory?.FullName ?? - throw new FileNotFoundException("Base directory not found"); + throw new FileNotFoundException("Output base directory not found"); string buildConfig = isDebug ? "ExportDebug" : "ExportRelease"; - if (!BuildManager.BuildProjectBlocking(buildConfig, platform: platform)) - throw new Exception("Failed to build project"); + // TODO: This works for now, as we only implemented support for x86 family desktop so far, but it needs to be fixed + string arch = features.Contains("64") ? "x86_64" : "x86"; - // Add dependency assemblies + string ridOS = DetermineRuntimeIdentifierOS(platform); + string ridArch = DetermineRuntimeIdentifierArch(arch); + string runtimeIdentifier = $"{ridOS}-{ridArch}"; - var assemblies = new Godot.Collections.Dictionary<string, string>(); + // Create temporary publish output directory - string projectDllName = GodotSharpEditor.ProjectAssemblyName; - string projectDllSrcDir = Path.Combine(GodotSharpDirs.ResTempAssembliesBaseDir, buildConfig); - string projectDllSrcPath = Path.Combine(projectDllSrcDir, $"{projectDllName}.dll"); + string publishOutputTempDir = Path.Combine(Path.GetTempPath(), "godot-publish-dotnet", + $"{Process.GetCurrentProcess().Id}-{buildConfig}-{runtimeIdentifier}"); - assemblies[projectDllName] = projectDllSrcPath; + if (!Directory.Exists(publishOutputTempDir)) + Directory.CreateDirectory(publishOutputTempDir); - string bclDir = DeterminePlatformBclDir(platform); + // Execute dotnet publish - if (platform == OS.Platforms.Android) + if (!BuildManager.PublishProjectBlocking(buildConfig, platform, + runtimeIdentifier, publishOutputTempDir)) { - string godotAndroidExtProfileDir = GetBclProfileDir("godot_android_ext"); - string monoAndroidAssemblyPath = Path.Combine(godotAndroidExtProfileDir, "Mono.Android.dll"); + throw new Exception("Failed to build project"); + } - if (!File.Exists(monoAndroidAssemblyPath)) - throw new FileNotFoundException("Assembly not found: 'Mono.Android'", monoAndroidAssemblyPath); + string soExt = ridOS switch + { + OS.DotNetOS.Win or OS.DotNetOS.Win10 => "dll", + OS.DotNetOS.OSX or OS.DotNetOS.iOS => "dylib", + _ => "so" + }; - assemblies["Mono.Android"] = monoAndroidAssemblyPath; - } - else if (platform == OS.Platforms.HTML5) + if (!File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.dll")) + // NativeAOT shared library output + && !File.Exists(Path.Combine(publishOutputTempDir, $"{GodotSharpDirs.ProjectAssemblyName}.{soExt}"))) { - // Ideally these would be added automatically since they're referenced by the wasm BCL assemblies. - // However, at least in the case of 'WebAssembly.Net.Http' for some reason the BCL assemblies - // reference a different version even though the assembly is the same, for some weird reason. - - var wasmFrameworkAssemblies = new[] { "WebAssembly.Bindings", "WebAssembly.Net.WebSockets" }; - - foreach (string thisWasmFrameworkAssemblyName in wasmFrameworkAssemblies) - { - string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName + ".dll"); - if (!File.Exists(thisWasmFrameworkAssemblyPath)) - throw new FileNotFoundException($"Assembly not found: '{thisWasmFrameworkAssemblyName}'", thisWasmFrameworkAssemblyPath); - assemblies[thisWasmFrameworkAssemblyName] = thisWasmFrameworkAssemblyPath; - } - - // Assemblies that can have a different name in a newer version. Newer version must come first and it has priority. - (string newName, string oldName)[] wasmFrameworkAssembliesOneOf = new[] - { - ("System.Net.Http.WebAssemblyHttpHandler", "WebAssembly.Net.Http") - }; - - foreach (var thisWasmFrameworkAssemblyName in wasmFrameworkAssembliesOneOf) - { - string thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.newName + ".dll"); - if (File.Exists(thisWasmFrameworkAssemblyPath)) - { - assemblies[thisWasmFrameworkAssemblyName.newName] = thisWasmFrameworkAssemblyPath; - } - else - { - thisWasmFrameworkAssemblyPath = Path.Combine(bclDir, thisWasmFrameworkAssemblyName.oldName + ".dll"); - if (!File.Exists(thisWasmFrameworkAssemblyPath)) - { - throw new FileNotFoundException("Expected one of the following assemblies but none were found: " + - $"'{thisWasmFrameworkAssemblyName.newName}' / '{thisWasmFrameworkAssemblyName.oldName}'", - thisWasmFrameworkAssemblyPath); - } - - assemblies[thisWasmFrameworkAssemblyName.oldName] = thisWasmFrameworkAssemblyPath; - } - } + throw new NotSupportedException( + "Publish succeeded but project assembly not found in the output directory"); } - var initialAssemblies = assemblies.Duplicate(); - internal_GetExportedAssemblyDependencies(initialAssemblies, buildConfig, bclDir, assemblies); - - AddI18NAssemblies(assemblies, bclDir); - - string outputDataDir = null; + // Copy all files from the dotnet publish output directory to + // a data directory next to the Godot output executable. - if (PlatformHasTemplateDir(platform)) - outputDataDir = ExportDataDirectory(features, platform, isDebug, outputDir); + string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject()); - string apiConfig = isDebug ? "Debug" : "Release"; - string resAssembliesDir = Path.Combine(GodotSharpDirs.ResAssembliesBaseDir, apiConfig); + if (Directory.Exists(outputDataDir)) + Directory.Delete(outputDataDir, recursive: true); // Clean first - bool assembliesInsidePck = (bool)ProjectSettings.GetSetting("mono/export/export_assemblies_inside_pck") || outputDataDir == null; + Directory.CreateDirectory(outputDataDir); - if (!assembliesInsidePck) + foreach (string dir in Directory.GetDirectories(publishOutputTempDir, "*", SearchOption.AllDirectories)) { - string outputDataGameAssembliesDir = Path.Combine(outputDataDir, "Assemblies"); - if (!Directory.Exists(outputDataGameAssembliesDir)) - Directory.CreateDirectory(outputDataGameAssembliesDir); + Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(publishOutputTempDir.Length + 1))); } - foreach (var assembly in assemblies) + foreach (string file in Directory.GetFiles(publishOutputTempDir, "*", SearchOption.AllDirectories)) { - void AddToAssembliesDir(string fileSrcPath) - { - if (assembliesInsidePck) - { - string fileDstPath = Path.Combine(resAssembliesDir, fileSrcPath.GetFile()); - AddFile(fileSrcPath, fileDstPath); - } - else - { - Debug.Assert(outputDataDir != null); - string fileDstPath = Path.Combine(outputDataDir, "Assemblies", fileSrcPath.GetFile()); - File.Copy(fileSrcPath, fileDstPath); - } - } - - string assemblySrcPath = assembly.Value; - - string assemblyPathWithoutExtension = Path.ChangeExtension(assemblySrcPath, null); - string pdbSrcPath = assemblyPathWithoutExtension + ".pdb"; - - AddToAssembliesDir(assemblySrcPath); - - if (File.Exists(pdbSrcPath)) - AddToAssembliesDir(pdbSrcPath); + File.Copy(file, Path.Combine(outputDataDir, file.Substring(publishOutputTempDir.Length + 1))); } + } - // AOT compilation - bool aotEnabled = platform == OS.Platforms.iOS || (bool)ProjectSettings.GetSetting("mono/export/aot/enabled"); + private string DetermineRuntimeIdentifierOS(string platform) + => OS.DotNetOSPlatformMap[platform]; - if (aotEnabled) + private string DetermineRuntimeIdentifierArch(string arch) + { + return arch switch { - string aotToolchainPath = null; - - if (platform == OS.Platforms.Android) - aotToolchainPath = (string)ProjectSettings.GetSetting("mono/export/aot/android_toolchain_path"); - - if (aotToolchainPath == string.Empty) - aotToolchainPath = null; // Don't risk it being used as current working dir - - // TODO: LLVM settings are hard-coded and disabled for now - var aotOpts = new AotOptions - { - EnableLLVM = false, - LLVMOnly = false, - LLVMPath = "", - LLVMOutputPath = "", - FullAot = platform == OS.Platforms.iOS || (bool)(ProjectSettings.GetSetting("mono/export/aot/full_aot") ?? false), - UseInterpreter = (bool)ProjectSettings.GetSetting("mono/export/aot/use_interpreter"), - ExtraAotOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_aot_options") ?? Array.Empty<string>(), - ExtraOptimizerOptions = (string[])ProjectSettings.GetSetting("mono/export/aot/extra_optimizer_options") ?? Array.Empty<string>(), - ToolchainPath = aotToolchainPath - }; - - AotBuilder.CompileAssemblies(this, aotOpts, features, platform, isDebug, bclDir, outputDir, outputDataDir, assemblies); - } + "x86" => "x86", + "x86_32" => "x86", + "x64" => "x64", + "x86_64" => "x64", + "armeabi-v7a" => "arm", + "arm64-v8a" => "arm64", + "armv7" => "arm", + "arm64" => "arm64", + _ => throw new ArgumentOutOfRangeException(nameof(arch), arch, "Unexpected architecture") + }; } public override void _ExportEnd() @@ -316,8 +198,10 @@ namespace GodotTools.Export if (Directory.Exists(aotTempDir)) Directory.Delete(aotTempDir, recursive: true); - // TODO: Just a workaround until the export plugins can be made to abort with errors - if (!string.IsNullOrEmpty(_maybeLastExportError)) // Check empty as well, because it's set to empty after hot-reloading + // TODO: The following is just a workaround until the export plugins can be made to abort with errors + + // We check for empty as well, because it's set to empty after hot-reloading + if (!string.IsNullOrEmpty(_maybeLastExportError)) { string lastExportError = _maybeLastExportError; _maybeLastExportError = null; @@ -326,69 +210,11 @@ namespace GodotTools.Export } } - [NotNull] - private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir) - { - string target = isDebug ? "release_debug" : "release"; - - // NOTE: Bits is ok for now as all platforms with a data directory only have one or two architectures. - // However, this may change in the future if we add arm linux or windows desktop templates. - string bits = features.Contains("64") ? "64" : "32"; - - string TemplateDirName() => $"data.mono.{platform}.{bits}.{target}"; - - string templateDirPath = Path.Combine(Internal.FullExportTemplatesDir, TemplateDirName()); - bool validTemplatePathFound = true; - - if (!Directory.Exists(templateDirPath)) - { - validTemplatePathFound = false; - - if (isDebug) - { - target = "debug"; // Support both 'release_debug' and 'debug' for the template data directory name - templateDirPath = Path.Combine(Internal.FullExportTemplatesDir, TemplateDirName()); - validTemplatePathFound = true; - - if (!Directory.Exists(templateDirPath)) - validTemplatePathFound = false; - } - } - - if (!validTemplatePathFound) - throw new FileNotFoundException("Data template directory not found", templateDirPath); - - string outputDataDir = Path.Combine(outputDir, DetermineDataDirNameForProject()); - - if (Directory.Exists(outputDataDir)) - Directory.Delete(outputDataDir, recursive: true); // Clean first - - Directory.CreateDirectory(outputDataDir); - - foreach (string dir in Directory.GetDirectories(templateDirPath, "*", SearchOption.AllDirectories)) - { - Directory.CreateDirectory(Path.Combine(outputDataDir, dir.Substring(templateDirPath.Length + 1))); - } - - foreach (string file in Directory.GetFiles(templateDirPath, "*", SearchOption.AllDirectories)) - { - File.Copy(file, Path.Combine(outputDataDir, file.Substring(templateDirPath.Length + 1))); - } - - return outputDataDir; - } - - private static bool PlatformHasTemplateDir(string platform) - { - // macOS export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest. - return !new[] { OS.Platforms.MacOS, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform); - } - private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform) { foreach (var feature in features) { - if (OS.PlatformNameMap.TryGetValue(feature, out platform)) + if (OS.PlatformFeatureMap.TryGetValue(feature, out platform)) return true; } @@ -396,87 +222,11 @@ namespace GodotTools.Export return false; } - private static string GetBclProfileDir(string profile) - { - string templatesDir = Internal.FullExportTemplatesDir; - return Path.Combine(templatesDir, "bcl", profile); - } - - private static string DeterminePlatformBclDir(string platform) - { - string templatesDir = Internal.FullExportTemplatesDir; - string platformBclDir = Path.Combine(templatesDir, "bcl", platform); - - if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) - { - string profile = DeterminePlatformBclProfile(platform); - platformBclDir = Path.Combine(templatesDir, "bcl", profile); - - if (!File.Exists(Path.Combine(platformBclDir, "mscorlib.dll"))) - { - if (PlatformRequiresCustomBcl(platform)) - throw new FileNotFoundException($"Missing BCL (Base Class Library) for platform: {platform}"); - - platformBclDir = typeof(object).Assembly.Location.GetBaseDir(); // Use the one we're running on - } - } - - return platformBclDir; - } - - /// <summary> - /// Determines whether the BCL bundled with the Godot editor can be used for the target platform, - /// or if it requires a custom BCL that must be distributed with the export templates. - /// </summary> - private static bool PlatformRequiresCustomBcl(string platform) - { - if (new[] { OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform)) - return true; - - // The 'net_4_x' BCL is not compatible between Windows and the other platforms. - // We use the names 'net_4_x_win' and 'net_4_x' to differentiate between the two. - - bool isWinOrUwp = new[] - { - OS.Platforms.Windows, - OS.Platforms.UWP - }.Contains(platform); - - return OS.IsWindows ? !isWinOrUwp : isWinOrUwp; - } - - private static string DeterminePlatformBclProfile(string platform) - { - switch (platform) - { - case OS.Platforms.Windows: - case OS.Platforms.UWP: - return "net_4_x_win"; - case OS.Platforms.MacOS: - case OS.Platforms.LinuxBSD: - case OS.Platforms.Server: - case OS.Platforms.Haiku: - return "net_4_x"; - case OS.Platforms.Android: - return "monodroid"; - case OS.Platforms.iOS: - return "monotouch"; - case OS.Platforms.HTML5: - return "wasm"; - default: - throw new NotSupportedException($"Platform not supported: {platform}"); - } - } - private static string DetermineDataDirNameForProject() { string appName = (string)ProjectSettings.GetSetting("application/config/name"); string appNameSafe = appName.ToSafeDirName(); return $"data_{appNameSafe}"; } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_GetExportedAssemblyDependencies(Godot.Collections.Dictionary<string, string> initialAssemblies, - string buildConfig, string customBclDir, Godot.Collections.Dictionary<string, string> dependencyAssemblies); } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs index b39c3d1c0d..0aca60dad4 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs +++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs @@ -13,13 +13,14 @@ using GodotTools.Internals; using GodotTools.ProjectEditor; using JetBrains.Annotations; using static GodotTools.Internals.Globals; +using Environment = System.Environment; using File = GodotTools.Utils.File; using OS = GodotTools.Utils.OS; using Path = System.IO.Path; namespace GodotTools { - public class GodotSharpEditor : EditorPlugin, ISerializationListener + public partial class GodotSharpEditor : EditorPlugin, ISerializationListener { private EditorSettings _editorSettings; @@ -39,28 +40,27 @@ namespace GodotTools public bool SkipBuildBeforePlaying { get; set; } = false; - public static string ProjectAssemblyName + [UsedImplicitly] + private bool CreateProjectSolutionIfNeeded() { - get + if (!File.Exists(GodotSharpDirs.ProjectSlnPath) || !File.Exists(GodotSharpDirs.ProjectCsProjPath)) { - string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); - projectAssemblyName = projectAssemblyName.ToSafeDirName(); - if (string.IsNullOrEmpty(projectAssemblyName)) - projectAssemblyName = "UnnamedProject"; - return projectAssemblyName; + return CreateProjectSolution(); } + + return true; } private bool CreateProjectSolution() { - using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 3)) + using (var pr = new EditorProgress("create_csharp_solution", "Generating solution...".TTR(), 2)) { pr.Step("Generating C# project...".TTR()); string resourceDir = ProjectSettings.GlobalizePath("res://"); string path = resourceDir; - string name = ProjectAssemblyName; + string name = GodotSharpDirs.ProjectAssemblyName; string guid = CsProjOperations.GenerateGameProject(path, name); @@ -75,7 +75,7 @@ namespace GodotTools { Guid = guid, PathRelativeToSolution = name + ".csproj", - Configs = new List<string> {"Debug", "ExportDebug", "ExportRelease"} + Configs = new List<string> { "Debug", "ExportDebug", "ExportRelease" } }; solution.AddNewProject(name, projectInfo); @@ -90,24 +90,6 @@ namespace GodotTools return false; } - pr.Step("Updating Godot API assemblies...".TTR()); - - string debugApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Debug"); - - if (!string.IsNullOrEmpty(debugApiAssembliesError)) - { - ShowErrorDialog("Failed to update the Godot API assemblies: " + debugApiAssembliesError); - return false; - } - - string releaseApiAssembliesError = Internal.UpdateApiAssembliesFromPrebuilt("Release"); - - if (!string.IsNullOrEmpty(releaseApiAssembliesError)) - { - ShowErrorDialog("Failed to update the Godot API assemblies: " + releaseApiAssembliesError); - return false; - } - pr.Step("Done".TTR()); // Here, after all calls to progress_task_step @@ -141,7 +123,8 @@ namespace GodotTools try { string fallbackFolder = NuGetUtils.GodotFallbackFolderPath; - NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, fallbackFolder); + NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, + fallbackFolder); NuGetUtils.AddBundledPackagesToFallbackFolder(fallbackFolder); } catch (Exception e) @@ -167,13 +150,6 @@ namespace GodotTools Instance.MSBuildPanel.BuildSolution(); } - public override void _Ready() - { - base._Ready(); - - MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged; - } - private enum MenuOptions { CreateSln, @@ -197,7 +173,7 @@ namespace GodotTools [UsedImplicitly] public Error OpenInExternalEditor(Script script, int line, int col) { - var editorId = (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor"); + var editorId = (ExternalEditorId)(int)_editorSettings.GetSetting("mono/editor/external_editor"); switch (editorId) { @@ -219,13 +195,15 @@ namespace GodotTools try { if (Godot.OS.IsStdoutVerbose()) - Console.WriteLine($"Running: \"{command}\" {string.Join(" ", args.Select(a => $"\"{a}\""))}"); + Console.WriteLine( + $"Running: \"{command}\" {string.Join(" ", args.Select(a => $"\"{a}\""))}"); OS.RunProcess(command, args); } catch (Exception e) { - GD.PushError($"Error when trying to run code editor: VisualStudio. Exception message: '{e.Message}'"); + GD.PushError( + $"Error when trying to run code editor: VisualStudio. Exception message: '{e.Message}'"); } break; @@ -347,7 +325,8 @@ namespace GodotTools [UsedImplicitly] public bool OverridesExternalEditor() { - return (ExternalEditorId)_editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None; + return (ExternalEditorId)(int)_editorSettings.GetSetting("mono/editor/external_editor") != + ExternalEditorId.None; } public override bool _Build() @@ -368,7 +347,7 @@ namespace GodotTools // NOTE: The order in which changes are made to the project is important // Migrate to MSBuild project Sdks style if using the old style - ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, ProjectAssemblyName); + ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, GodotSharpDirs.ProjectAssemblyName); ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject); @@ -400,18 +379,49 @@ namespace GodotTools throw new InvalidOperationException(); Instance = this; + var dotNetSdkSearchVersion = Environment.Version; + + // First we try to find the .NET Sdk ourselves to make sure we get the + // correct version first (`RegisterDefaults` always picks the latest). + if (DotNetFinder.TryFindDotNetSdk(dotNetSdkSearchVersion, out var sdkVersion, out string sdkPath)) + { + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}"); + + ProjectUtils.MSBuildLocatorRegisterMSBuildPath(sdkPath); + } + else + { + try + { + ProjectUtils.MSBuildLocatorRegisterDefaults(out sdkVersion, out sdkPath); + if (Godot.OS.IsStdoutVerbose()) + Console.WriteLine($"Found .NET Sdk version '{sdkVersion}': {sdkPath}"); + } + catch (InvalidOperationException e) + { + if (Godot.OS.IsStdoutVerbose()) + GD.PrintErr(e.ToString()); + GD.PushError($".NET Sdk not found. The required version is '{dotNetSdkSearchVersion}'."); + } + } + var editorInterface = GetEditorInterface(); var editorBaseControl = editorInterface.GetBaseControl(); _editorSettings = editorInterface.GetEditorSettings(); + GodotSharpDirs.RegisterProjectSettings(); + _errorDialog = new AcceptDialog(); editorBaseControl.AddChild(_errorDialog); MSBuildPanel = new MSBuildPanel(); + MSBuildPanel.Ready += () => + MSBuildPanel.BuildOutputView.BuildStateChanged += BuildStateChanged; _bottomPanelBtn = AddControlToBottomPanel(MSBuildPanel, "MSBuild".TTR()); - AddChild(new HotReloadAssemblyWatcher {Name = "HotReloadAssemblyWatcher"}); + AddChild(new HotReloadAssemblyWatcher { Name = "HotReloadAssemblyWatcher" }); _menuPopup = new PopupMenu(); _menuPopup.Hide(); @@ -472,9 +482,9 @@ namespace GodotTools _editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { - ["type"] = Variant.Type.Int, + ["type"] = (int)Variant.Type.Int, ["name"] = "mono/editor/external_editor", - ["hint"] = PropertyHint.Enum, + ["hint"] = (int)PropertyHint.Enum, ["hint_string"] = settingsHintStr }); @@ -487,7 +497,8 @@ namespace GodotTools try { // At startup we make sure NuGet.Config files have our Godot NuGet fallback folder included - NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, NuGetUtils.GodotFallbackFolderPath); + NuGetUtils.AddFallbackFolderToUserNuGetConfigs(NuGetUtils.GodotFallbackFolderName, + NuGetUtils.GodotFallbackFolderPath); } catch (Exception e) { @@ -503,20 +514,23 @@ namespace GodotTools protected override void Dispose(bool disposing) { - base.Dispose(disposing); - - if (_exportPluginWeak != null) + if (disposing) { - // We need to dispose our export plugin before the editor destroys EditorSettings. - // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid - // will be freed after EditorSettings already was, and its device polling thread - // will try to access the EditorSettings singleton, resulting in null dereferencing. - (_exportPluginWeak.GetRef() as ExportPlugin)?.Dispose(); + if (IsInstanceValid(_exportPluginWeak)) + { + // We need to dispose our export plugin before the editor destroys EditorSettings. + // Otherwise, if the GC disposes it at a later time, EditorExportPlatformAndroid + // will be freed after EditorSettings already was, and its device polling thread + // will try to access the EditorSettings singleton, resulting in null dereferencing. + (_exportPluginWeak.GetRef().AsGodotObject() as ExportPlugin)?.Dispose(); + + _exportPluginWeak.Dispose(); + } - _exportPluginWeak.Dispose(); + GodotIdeManager?.Dispose(); } - GodotIdeManager?.Dispose(); + base.Dispose(disposing); } public void OnBeforeSerialize() @@ -533,8 +547,10 @@ namespace GodotTools public static GodotSharpEditor Instance { get; private set; } [UsedImplicitly] - private GodotSharpEditor() + private static IntPtr InternalCreateInstance(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) { + Internal.Initialize(unmanagedCallbacks, unmanagedCallbacksSize); + return new GodotSharpEditor().NativeInstance; } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index f1d45463c5..30525ba04a 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -1,14 +1,24 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <ProjectGuid>{27B00618-A6F2-4828-B922-05CAEB08C286}</ProjectGuid> - <TargetFramework>net472</TargetFramework> - <LangVersion>7.2</LangVersion> + <TargetFramework>net6.0</TargetFramework> + <EnableDynamicLoading>true</EnableDynamicLoading> + <LangVersion>10</LangVersion> <!-- The Godot editor uses the Debug Godot API assemblies --> <GodotApiConfiguration>Debug</GodotApiConfiguration> <GodotSourceRootPath>$(SolutionDir)/../../../../</GodotSourceRootPath> <GodotOutputDataDir>$(GodotSourceRootPath)/bin/GodotSharp</GodotOutputDataDir> <GodotApiAssembliesDir>$(GodotOutputDataDir)/Api/$(GodotApiConfiguration)</GodotApiAssembliesDir> + <ProduceReferenceAssembly>false</ProduceReferenceAssembly> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup> + <!-- Needed for our source generators to work despite this not being a Godot game project --> + <PropertyGroup> + <IsGodotToolsProject>true</IsGodotToolsProject> + </PropertyGroup> + <ItemGroup> + <CompilerVisibleProperty Include="IsGodotToolsProject" /> + </ItemGroup> <PropertyGroup Condition=" Exists('$(GodotApiAssembliesDir)/GodotSharp.dll') "> <!-- The project is part of the Godot source tree --> <!-- Use the Godot source tree output folder instead of '$(ProjectDir)/bin' --> @@ -20,6 +30,8 @@ <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> + <!-- For RiderPathLocator --> + <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <Reference Include="GodotSharp"> <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath> <Private>False</Private> @@ -30,6 +42,10 @@ </Reference> </ItemGroup> <ItemGroup> + <ProjectReference Include="..\..\Godot.NET.Sdk\Godot.SourceGenerators\Godot.SourceGenerators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + <ProjectReference Include="..\..\..\glue\GodotSharp\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + </ItemGroup> + <ItemGroup> <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj" /> <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" /> <ProjectReference Include="..\GodotTools.ProjectEditor\GodotTools.ProjectEditor.csproj" /> diff --git a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs index dd05f28af0..260d13a714 100644 --- a/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs +++ b/modules/mono/editor/GodotTools/GodotTools/HotReloadAssemblyWatcher.cs @@ -1,10 +1,11 @@ using Godot; using GodotTools.Internals; +using JetBrains.Annotations; using static GodotTools.Internals.Globals; namespace GodotTools { - public class HotReloadAssemblyWatcher : Node + public partial class HotReloadAssemblyWatcher : Node { private Timer _watchTimer; @@ -25,6 +26,7 @@ namespace GodotTools Internal.ReloadAssemblies(softReload: false); } + [UsedImplicitly] public void RestartTimer() { _watchTimer.Stop(); @@ -38,7 +40,7 @@ namespace GodotTools _watchTimer = new Timer { OneShot = false, - WaitTime = (float)EditorDef("mono/assembly_watch_interval_sec", 0.5) + WaitTime = 0.5f }; _watchTimer.Timeout += TimerTimeout; AddChild(_watchTimer); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs index 23339fe50b..95b60aded1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs @@ -8,7 +8,7 @@ using GodotTools.Internals; namespace GodotTools.Ides { - public sealed class GodotIdeManager : Node, ISerializationListener + public sealed partial class GodotIdeManager : Node, ISerializationListener { private MessagingServer _messagingServer; @@ -76,7 +76,7 @@ namespace GodotTools.Ides public async Task<EditorPick?> LaunchIdeAsync(int millisecondsTimeout = 10000) { - var editorId = (ExternalEditorId)GodotSharpEditor.Instance.GetEditorInterface() + var editorId = (ExternalEditorId)(int)GodotSharpEditor.Instance.GetEditorInterface() .GetEditorSettings().GetSetting("mono/editor/external_editor"); string editorIdentity = GetExternalEditorIdentity(editorId); diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs index 71055f0125..4caab035de 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; +using System.Runtime.Versioning; using Godot; -using JetBrains.Annotations; using Microsoft.Win32; using Newtonsoft.Json; using Directory = System.IO.Directory; @@ -113,6 +114,7 @@ namespace GodotTools.Ides.Rider return installInfos.ToArray(); } + [SupportedOSPlatform("windows")] private static RiderInfo[] CollectRiderInfosWindows() { var installInfos = new List<RiderInfo>(); @@ -217,6 +219,7 @@ namespace GodotTools.Ides.Rider throw new Exception("Unknown OS."); } + [SupportedOSPlatform("windows")] private static void CollectPathsFromRegistry(string registryKey, List<string> installPaths) { using (var key = Registry.CurrentUser.OpenSubKey(registryKey)) @@ -229,6 +232,7 @@ namespace GodotTools.Ides.Rider } } + [SupportedOSPlatform("windows")] private static void CollectPathsFromRegistry(List<string> installPaths, RegistryKey key) { if (key == null) return; @@ -324,7 +328,7 @@ namespace GodotTools.Ides.Rider { public string install_location; - [CanBeNull] + [return: MaybeNull] public static string GetInstallLocationFromJson(string json) { try @@ -378,7 +382,7 @@ namespace GodotTools.Ides.Rider public string version; public string versionSuffix; - [CanBeNull] + [return: MaybeNull] internal static ProductInfo GetProductInfo(string json) { try @@ -402,7 +406,7 @@ namespace GodotTools.Ides.Rider // ReSharper disable once InconsistentNaming public ActiveApplication active_application; - [CanBeNull] + [return: MaybeNull] public static string GetLatestBuildFromJson(string json) { try diff --git a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs index 3440eb701c..60602a5847 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs @@ -22,7 +22,7 @@ namespace GodotTools.Ides.Rider public static void Initialize() { var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings(); - var editor = (ExternalEditorId)editorSettings.GetSetting("mono/editor/external_editor"); + var editor = (ExternalEditorId)(int)editorSettings.GetSetting("mono/editor/external_editor"); if (editor == ExternalEditorId.Rider) { if (!editorSettings.HasSetting(EditorPathSettingName)) @@ -30,9 +30,9 @@ namespace GodotTools.Ides.Rider Globals.EditorDef(EditorPathSettingName, "Optional"); editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary { - ["type"] = Variant.Type.String, + ["type"] = (int)Variant.Type.String, ["name"] = EditorPathSettingName, - ["hint"] = PropertyHint.File, + ["hint"] = (int)PropertyHint.File, ["hint_string"] = "" }); } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs index 70ba7c733a..8f39ad063e 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/EditorProgress.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.CompilerServices; using Godot; +using Godot.NativeInterop; namespace GodotTools.Internals { @@ -8,19 +9,12 @@ namespace GodotTools.Internals { public string Task { get; } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_Create(string task, string label, int amount, bool canCancel); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_Dispose(string task); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_Step(string task, string state, int step, bool forceRefresh); - public EditorProgress(string task, string label, int amount, bool canCancel = false) { Task = task; - internal_Create(task, label, amount, canCancel); + using godot_string taskIn = Marshaling.ConvertStringToNative(task); + using godot_string labelIn = Marshaling.ConvertStringToNative(label); + Internal.godot_icall_EditorProgress_Create(taskIn, labelIn, amount, canCancel); } ~EditorProgress() @@ -33,18 +27,23 @@ namespace GodotTools.Internals public void Dispose() { - internal_Dispose(Task); + using godot_string taskIn = Marshaling.ConvertStringToNative(Task); + Internal.godot_icall_EditorProgress_Dispose(taskIn); GC.SuppressFinalize(this); } public void Step(string state, int step = -1, bool forceRefresh = true) { - internal_Step(Task, state, step, forceRefresh); + using godot_string taskIn = Marshaling.ConvertStringToNative(Task); + using godot_string stateIn = Marshaling.ConvertStringToNative(state); + Internal.godot_icall_EditorProgress_Step(taskIn, stateIn, step, forceRefresh); } public bool TryStep(string state, int step = -1, bool forceRefresh = true) { - return internal_Step(Task, state, step, forceRefresh); + using godot_string taskIn = Marshaling.ConvertStringToNative(Task); + using godot_string stateIn = Marshaling.ConvertStringToNative(state); + return Internal.godot_icall_EditorProgress_Step(taskIn, stateIn, step, forceRefresh); } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs index 5c5ced8c29..acb7cc3ab0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Globals.cs @@ -1,3 +1,4 @@ +using Godot.NativeInterop; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -5,35 +6,41 @@ namespace GodotTools.Internals { public static class Globals { - public static float EditorScale => internal_EditorScale(); - - public static object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) => - internal_GlobalDef(setting, defaultValue, restartIfChanged); - - public static object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) => - internal_EditorDef(setting, defaultValue, restartIfChanged); - - public static object EditorShortcut(string setting) => - internal_EditorShortcut(setting); + public static float EditorScale => Internal.godot_icall_Globals_EditorScale(); + + public static unsafe object GlobalDef(string setting, object defaultValue, bool restartIfChanged = false) + { + using godot_string settingIn = Marshaling.ConvertStringToNative(setting); + using godot_variant defaultValueIn = Marshaling.ConvertManagedObjectToVariant(defaultValue); + Internal.godot_icall_Globals_GlobalDef(settingIn, defaultValueIn, restartIfChanged, out godot_variant result); + using (result) + return Marshaling.ConvertVariantToManagedObject(result); + } + + public static unsafe object EditorDef(string setting, object defaultValue, bool restartIfChanged = false) + { + using godot_string settingIn = Marshaling.ConvertStringToNative(setting); + using godot_variant defaultValueIn = Marshaling.ConvertManagedObjectToVariant(defaultValue); + Internal.godot_icall_Globals_EditorDef(settingIn, defaultValueIn, restartIfChanged, out godot_variant result); + using (result) + return Marshaling.ConvertVariantToManagedObject(result); + } + + public static object EditorShortcut(string setting) + { + using godot_string settingIn = Marshaling.ConvertStringToNative(setting); + Internal.godot_icall_Globals_EditorShortcut(settingIn, out godot_variant result); + using (result) + return Marshaling.ConvertVariantToManagedObject(result); + } [SuppressMessage("ReSharper", "InconsistentNaming")] - public static string TTR(this string text) => internal_TTR(text); - - // Internal Calls - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern float internal_EditorScale(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern object internal_GlobalDef(string setting, object defaultValue, bool restartIfChanged); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern object internal_EditorDef(string setting, object defaultValue, bool restartIfChanged); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern object internal_EditorShortcut(string setting); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_TTR(string text); + public static string TTR(this string text) + { + using godot_string textIn = Marshaling.ConvertStringToNative(text); + Internal.godot_icall_Globals_TTR(textIn, out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs index 5e70c399b2..14285cc0f1 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/GodotSharpDirs.cs @@ -1,103 +1,125 @@ -using System.Runtime.CompilerServices; +using System.IO; +using Godot; +using Godot.NativeInterop; +using GodotTools.Core; +using static GodotTools.Internals.Globals; namespace GodotTools.Internals { public static class GodotSharpDirs { - public static string ResDataDir => internal_ResDataDir(); - public static string ResMetadataDir => internal_ResMetadataDir(); - public static string ResAssembliesBaseDir => internal_ResAssembliesBaseDir(); - public static string ResAssembliesDir => internal_ResAssembliesDir(); - public static string ResConfigDir => internal_ResConfigDir(); - public static string ResTempDir => internal_ResTempDir(); - public static string ResTempAssembliesBaseDir => internal_ResTempAssembliesBaseDir(); - public static string ResTempAssembliesDir => internal_ResTempAssembliesDir(); - - public static string MonoUserDir => internal_MonoUserDir(); - public static string MonoLogsDir => internal_MonoLogsDir(); - - #region Tools-only - public static string MonoSolutionsDir => internal_MonoSolutionsDir(); - public static string BuildLogsDirs => internal_BuildLogsDirs(); - - public static string ProjectSlnPath => internal_ProjectSlnPath(); - public static string ProjectCsProjPath => internal_ProjectCsProjPath(); - - public static string DataEditorToolsDir => internal_DataEditorToolsDir(); - public static string DataEditorPrebuiltApiDir => internal_DataEditorPrebuiltApiDir(); - #endregion - - public static string DataMonoEtcDir => internal_DataMonoEtcDir(); - public static string DataMonoLibDir => internal_DataMonoLibDir(); - - #region Windows-only - public static string DataMonoBinDir => internal_DataMonoBinDir(); - #endregion - - - #region Internal - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResDataDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResMetadataDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResAssembliesBaseDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResAssembliesDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResConfigDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResTempDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResTempAssembliesBaseDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ResTempAssembliesDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_MonoUserDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_MonoLogsDir(); - - #region Tools-only - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_MonoSolutionsDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_BuildLogsDirs(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ProjectSlnPath(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_ProjectCsProjPath(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataEditorToolsDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataEditorPrebuiltApiDir(); - #endregion - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataMonoEtcDir(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataMonoLibDir(); - - #region Windows-only - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_DataMonoBinDir(); - #endregion - - #endregion + public static string ResMetadataDir + { + get + { + Internal.godot_icall_GodotSharpDirs_ResMetadataDir(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } + + public static string MonoUserDir + { + get + { + Internal.godot_icall_GodotSharpDirs_MonoUserDir(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } + + public static string BuildLogsDirs + { + get + { + Internal.godot_icall_GodotSharpDirs_BuildLogsDirs(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } + + public static string DataEditorToolsDir + { + get + { + Internal.godot_icall_GodotSharpDirs_DataEditorToolsDir(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } + + public static void RegisterProjectSettings() + { + GlobalDef("dotnet/project/assembly_name", ""); + GlobalDef("dotnet/project/solution_directory", ""); + GlobalDef("dotnet/project/c#_project_directory", ""); + } + + private static void DetermineProjectLocation() + { + static string DetermineProjectName() + { + string projectAssemblyName = (string)ProjectSettings.GetSetting("application/config/name"); + projectAssemblyName = projectAssemblyName.ToSafeDirName(); + if (string.IsNullOrEmpty(projectAssemblyName)) + projectAssemblyName = "UnnamedProject"; + return projectAssemblyName; + } + + _projectAssemblyName = (string)ProjectSettings.GetSetting("dotnet/project/assembly_name"); + if (string.IsNullOrEmpty(_projectAssemblyName)) + { + _projectAssemblyName = DetermineProjectName(); + ProjectSettings.SetSetting("dotnet/project/assembly_name", _projectAssemblyName); + } + + string slnParentDir = (string)ProjectSettings.GetSetting("dotnet/project/solution_directory"); + if (string.IsNullOrEmpty(slnParentDir)) + slnParentDir = "res://"; + + string csprojParentDir = (string)ProjectSettings.GetSetting("dotnet/project/c#_project_directory"); + if (string.IsNullOrEmpty(csprojParentDir)) + csprojParentDir = "res://"; + + _projectSlnPath = Path.Combine(ProjectSettings.GlobalizePath(slnParentDir), + string.Concat(_projectAssemblyName, ".sln")); + + _projectCsProjPath = Path.Combine(ProjectSettings.GlobalizePath(csprojParentDir), + string.Concat(_projectAssemblyName, ".csproj")); + } + + private static string _projectAssemblyName; + private static string _projectSlnPath; + private static string _projectCsProjPath; + + public static string ProjectAssemblyName + { + get + { + if (_projectAssemblyName == null) + DetermineProjectLocation(); + return _projectAssemblyName; + } + } + + public static string ProjectSlnPath + { + get + { + if (_projectSlnPath == null) + DetermineProjectLocation(); + return _projectSlnPath; + } + } + + public static string ProjectCsProjPath + { + get + { + if (_projectCsProjPath == null) + DetermineProjectLocation(); + return _projectCsProjPath; + } + } } } diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs index 12c90178c9..e3fe1622d0 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Internals/Internal.cs @@ -1,114 +1,161 @@ +using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Godot; +using Godot.NativeInterop; +using Godot.SourceGenerators.Internal; using GodotTools.IdeMessaging.Requests; namespace GodotTools.Internals { - public static class Internal + [SuppressMessage("ReSharper", "InconsistentNaming")] + [GenerateUnmanagedCallbacks(typeof(InternalUnmanagedCallbacks))] + internal static partial class Internal { public const string CSharpLanguageType = "CSharpScript"; public const string CSharpLanguageExtension = ".cs"; - public static string UpdateApiAssembliesFromPrebuilt(string config) => - internal_UpdateApiAssembliesFromPrebuilt(config); + public static string FullExportTemplatesDir + { + get + { + godot_icall_Internal_FullExportTemplatesDir(out godot_string dest); + using (dest) + return Marshaling.ConvertStringToManaged(dest); + } + } - public static string FullExportTemplatesDir => - internal_FullExportTemplatesDir(); + public static string SimplifyGodotPath(this string path) => Godot.StringExtensions.SimplifyPath(path); - public static string SimplifyGodotPath(this string path) => internal_SimplifyGodotPath(path); + public static bool IsMacOSAppBundleInstalled(string bundleId) + { + using godot_string bundleIdIn = Marshaling.ConvertStringToNative(bundleId); + return godot_icall_Internal_IsMacOSAppBundleInstalled(bundleIdIn); + } - public static bool IsMacOSAppBundleInstalled(string bundleId) => internal_IsMacOSAppBundleInstalled(bundleId); + public static bool GodotIs32Bits() => godot_icall_Internal_GodotIs32Bits(); - public static bool GodotIs32Bits() => internal_GodotIs32Bits(); + public static bool GodotIsRealTDouble() => godot_icall_Internal_GodotIsRealTDouble(); - public static bool GodotIsRealTDouble() => internal_GodotIsRealTDouble(); + public static void GodotMainIteration() => godot_icall_Internal_GodotMainIteration(); - public static void GodotMainIteration() => internal_GodotMainIteration(); + public static bool IsAssembliesReloadingNeeded() => godot_icall_Internal_IsAssembliesReloadingNeeded(); - public static ulong GetCoreApiHash() => internal_GetCoreApiHash(); + public static void ReloadAssemblies(bool softReload) => godot_icall_Internal_ReloadAssemblies(softReload); - public static ulong GetEditorApiHash() => internal_GetEditorApiHash(); + public static void EditorDebuggerNodeReloadScripts() => godot_icall_Internal_EditorDebuggerNodeReloadScripts(); - public static bool IsAssembliesReloadingNeeded() => internal_IsAssembliesReloadingNeeded(); + public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) => + godot_icall_Internal_ScriptEditorEdit(resource.NativeInstance, line, col, grabFocus); - public static void ReloadAssemblies(bool softReload) => internal_ReloadAssemblies(softReload); + public static void EditorNodeShowScriptScreen() => godot_icall_Internal_EditorNodeShowScriptScreen(); - public static void EditorDebuggerNodeReloadScripts() => internal_EditorDebuggerNodeReloadScripts(); + public static void EditorRunPlay() => godot_icall_Internal_EditorRunPlay(); - public static bool ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus = true) => - internal_ScriptEditorEdit(resource, line, col, grabFocus); + public static void EditorRunStop() => godot_icall_Internal_EditorRunStop(); - public static void EditorNodeShowScriptScreen() => internal_EditorNodeShowScriptScreen(); + public static void ScriptEditorDebugger_ReloadScripts() => + godot_icall_Internal_ScriptEditorDebugger_ReloadScripts(); - public static string MonoWindowsInstallRoot => internal_MonoWindowsInstallRoot(); + public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind, + string scriptFile) + { + using godot_string scriptFileIn = Marshaling.ConvertStringToNative(scriptFile); + godot_icall_Internal_CodeCompletionRequest((int)kind, scriptFileIn, out godot_packed_string_array res); + using (res) + return Marshaling.ConvertNativePackedStringArrayToSystemArray(res); + } - public static void EditorRunPlay() => internal_EditorRunPlay(); + #region Internal - public static void EditorRunStop() => internal_EditorRunStop(); + private static bool initialized = false; - public static void ScriptEditorDebugger_ReloadScripts() => internal_ScriptEditorDebugger_ReloadScripts(); + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Global + internal static unsafe void Initialize(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) + { + if (initialized) + throw new InvalidOperationException("Already initialized"); + initialized = true; - public static string[] CodeCompletionRequest(CodeCompletionRequest.CompletionKind kind, string scriptFile) => - internal_CodeCompletionRequest((int)kind, scriptFile); + if (unmanagedCallbacksSize != sizeof(InternalUnmanagedCallbacks)) + throw new ArgumentException("Unmanaged callbacks size mismatch"); - #region Internal + _unmanagedCallbacks = Unsafe.AsRef<InternalUnmanagedCallbacks>((void*)unmanagedCallbacks); + } + + private partial struct InternalUnmanagedCallbacks + { + } + + /* + * IMPORTANT: + * The order of the methods defined in NativeFuncs must match the order + * in the array defined at the bottom of 'editor/editor_internal_calls.cpp'. + */ + + public static partial void godot_icall_GodotSharpDirs_ResMetadataDir(out godot_string r_dest); + + public static partial void godot_icall_GodotSharpDirs_MonoUserDir(out godot_string r_dest); + + public static partial void godot_icall_GodotSharpDirs_BuildLogsDirs(out godot_string r_dest); + + public static partial void godot_icall_GodotSharpDirs_DataEditorToolsDir(out godot_string r_dest); + + public static partial void godot_icall_EditorProgress_Create(in godot_string task, in godot_string label, + int amount, bool canCancel); + + public static partial void godot_icall_EditorProgress_Dispose(in godot_string task); + + public static partial bool godot_icall_EditorProgress_Step(in godot_string task, in godot_string state, + int step, + bool forceRefresh); + + private static partial void godot_icall_Internal_FullExportTemplatesDir(out godot_string dest); + + private static partial bool godot_icall_Internal_IsMacOSAppBundleInstalled(in godot_string bundleId); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_UpdateApiAssembliesFromPrebuilt(string config); + private static partial bool godot_icall_Internal_GodotIs32Bits(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_FullExportTemplatesDir(); + private static partial bool godot_icall_Internal_GodotIsRealTDouble(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_SimplifyGodotPath(this string path); + private static partial void godot_icall_Internal_GodotMainIteration(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_IsMacOSAppBundleInstalled(string bundleId); + private static partial bool godot_icall_Internal_IsAssembliesReloadingNeeded(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_GodotIs32Bits(); + private static partial void godot_icall_Internal_ReloadAssemblies(bool softReload); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_GodotIsRealTDouble(); + private static partial void godot_icall_Internal_EditorDebuggerNodeReloadScripts(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_GodotMainIteration(); + private static partial bool godot_icall_Internal_ScriptEditorEdit(IntPtr resource, int line, int col, + bool grabFocus); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern ulong internal_GetCoreApiHash(); + private static partial void godot_icall_Internal_EditorNodeShowScriptScreen(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern ulong internal_GetEditorApiHash(); + private static partial void godot_icall_Internal_EditorRunPlay(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_IsAssembliesReloadingNeeded(); + private static partial void godot_icall_Internal_EditorRunStop(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_ReloadAssemblies(bool softReload); + private static partial void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_EditorDebuggerNodeReloadScripts(); + private static partial void godot_icall_Internal_CodeCompletionRequest(int kind, in godot_string scriptFile, + out godot_packed_string_array res); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool internal_ScriptEditorEdit(Resource resource, int line, int col, bool grabFocus); + public static partial float godot_icall_Globals_EditorScale(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_EditorNodeShowScriptScreen(); + public static partial void godot_icall_Globals_GlobalDef(in godot_string setting, in godot_variant defaultValue, + bool restartIfChanged, out godot_variant result); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string internal_MonoWindowsInstallRoot(); + public static partial void godot_icall_Globals_EditorDef(in godot_string setting, in godot_variant defaultValue, + bool restartIfChanged, out godot_variant result); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_EditorRunPlay(); + public static partial void + godot_icall_Globals_EditorShortcut(in godot_string setting, out godot_variant result); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_EditorRunStop(); + public static partial void godot_icall_Globals_TTR(in godot_string text, out godot_string dest); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void internal_ScriptEditorDebugger_ReloadScripts(); + public static partial void godot_icall_Utils_OS_GetPlatformName(out godot_string dest); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string[] internal_CodeCompletionRequest(int kind, string scriptFile); + public static partial bool godot_icall_Utils_OS_UnixFileHasExecutableAccess(in godot_string filePath); #endregion } diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs index 05499339b1..89bda704bb 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/FsPathUtils.cs @@ -1,14 +1,14 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using Godot; using GodotTools.Core; -using JetBrains.Annotations; namespace GodotTools.Utils { public static class FsPathUtils { - private static readonly string _resourcePath = ProjectSettings.GlobalizePath("res://"); + private static readonly string ResourcePath = ProjectSettings.GlobalizePath("res://"); private static bool PathStartsWithAlreadyNorm(this string childPath, string parentPath) { @@ -30,11 +30,11 @@ namespace GodotTools.Utils return childPathNorm.PathStartsWithAlreadyNorm(parentPathNorm); } - [CanBeNull] + [return: MaybeNull] public static string LocalizePathWithCaseChecked(string path) { string pathNorm = path.NormalizePath() + Path.DirectorySeparatorChar; - string resourcePathNorm = _resourcePath.NormalizePath() + Path.DirectorySeparatorChar; + string resourcePathNorm = ResourcePath.NormalizePath() + Path.DirectorySeparatorChar; if (!pathNorm.PathStartsWithAlreadyNorm(resourcePathNorm)) return null; diff --git a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs index 5cef6e5c3c..62140d41bc 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs @@ -1,24 +1,21 @@ +using Godot.NativeInterop; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; -using System.Runtime.CompilerServices; -using JetBrains.Annotations; +using System.Runtime.Versioning; +using System.Text; +using GodotTools.Internals; namespace GodotTools.Utils { [SuppressMessage("ReSharper", "InconsistentNaming")] public static class OS { - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string GetPlatformName(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool UnixFileHasExecutableAccess(string filePath); - - public static class Names + private static class Names { public const string Windows = "Windows"; public const string MacOS = "macOS"; @@ -47,6 +44,35 @@ namespace GodotTools.Utils public const string HTML5 = "javascript"; } + public static class DotNetOS + { + public const string Win = "win"; + public const string OSX = "osx"; + public const string Linux = "linux"; + public const string Win10 = "win10"; + public const string Android = "android"; + public const string iOS = "ios"; + public const string Browser = "browser"; + } + + public static readonly Dictionary<string, string> PlatformFeatureMap = new Dictionary<string, string>( + // Export `features` may be in lower case + StringComparer.InvariantCultureIgnoreCase + ) + { + ["Windows"] = Platforms.Windows, + ["macOS"] = Platforms.MacOS, + ["LinuxBSD"] = Platforms.LinuxBSD, + // "X11" for compatibility, temporarily, while we are on an outdated branch + ["X11"] = Platforms.LinuxBSD, + ["Server"] = Platforms.Server, + ["UWP"] = Platforms.UWP, + ["Haiku"] = Platforms.Haiku, + ["Android"] = Platforms.Android, + ["iOS"] = Platforms.iOS, + ["HTML5"] = Platforms.HTML5 + }; + public static readonly Dictionary<string, string> PlatformNameMap = new Dictionary<string, string> { [Names.Windows] = Platforms.Windows, @@ -63,14 +89,40 @@ namespace GodotTools.Utils [Names.HTML5] = Platforms.HTML5 }; + public static readonly Dictionary<string, string> DotNetOSPlatformMap = new Dictionary<string, string> + { + [Platforms.Windows] = DotNetOS.Win, + [Platforms.MacOS] = DotNetOS.OSX, + // TODO: + // Does .NET 6 support BSD variants? If it does, it may need the name `unix` + // instead of `linux` in the runtime identifier. This would be a problem as + // Godot has a single export profile for both, named LinuxBSD. + [Platforms.LinuxBSD] = DotNetOS.Linux, + [Platforms.Server] = DotNetOS.Linux, + [Platforms.UWP] = DotNetOS.Win10, + [Platforms.Android] = DotNetOS.Android, + [Platforms.iOS] = DotNetOS.iOS, + [Platforms.HTML5] = DotNetOS.Browser + }; + private static bool IsOS(string name) { - return name.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase); + Internal.godot_icall_Utils_OS_GetPlatformName(out godot_string dest); + using (dest) + { + string platformName = Marshaling.ConvertStringToManaged(dest); + return name.Equals(platformName, StringComparison.OrdinalIgnoreCase); + } } private static bool IsAnyOS(IEnumerable<string> names) { - return names.Any(p => p.Equals(GetPlatformName(), StringComparison.OrdinalIgnoreCase)); + Internal.godot_icall_Utils_OS_GetPlatformName(out godot_string dest); + using (dest) + { + string platformName = Marshaling.ConvertStringToManaged(dest); + return names.Any(p => p.Equals(platformName, StringComparison.OrdinalIgnoreCase)); + } } private static readonly IEnumerable<string> LinuxBSDPlatforms = @@ -80,30 +132,39 @@ namespace GodotTools.Utils new[] { Names.MacOS, Names.Server, Names.Haiku, Names.Android, Names.iOS } .Concat(LinuxBSDPlatforms).ToArray(); - private static readonly Lazy<bool> _isWindows = new Lazy<bool>(() => IsOS(Names.Windows)); - private static readonly Lazy<bool> _isMacOS = new Lazy<bool>(() => IsOS(Names.MacOS)); - private static readonly Lazy<bool> _isLinuxBSD = new Lazy<bool>(() => IsAnyOS(LinuxBSDPlatforms)); - private static readonly Lazy<bool> _isServer = new Lazy<bool>(() => IsOS(Names.Server)); - private static readonly Lazy<bool> _isUWP = new Lazy<bool>(() => IsOS(Names.UWP)); - private static readonly Lazy<bool> _isHaiku = new Lazy<bool>(() => IsOS(Names.Haiku)); - private static readonly Lazy<bool> _isAndroid = new Lazy<bool>(() => IsOS(Names.Android)); - private static readonly Lazy<bool> _isiOS = new Lazy<bool>(() => IsOS(Names.iOS)); - private static readonly Lazy<bool> _isHTML5 = new Lazy<bool>(() => IsOS(Names.HTML5)); - private static readonly Lazy<bool> _isUnixLike = new Lazy<bool>(() => IsAnyOS(UnixLikePlatforms)); - - public static bool IsWindows => _isWindows.Value || IsUWP; - public static bool IsMacOS => _isMacOS.Value; - public static bool IsLinuxBSD => _isLinuxBSD.Value; - public static bool IsServer => _isServer.Value; - public static bool IsUWP => _isUWP.Value; + private static readonly Lazy<bool> _isWindows = new(() => IsOS(Names.Windows)); + private static readonly Lazy<bool> _isMacOS = new(() => IsOS(Names.MacOS)); + private static readonly Lazy<bool> _isLinuxBSD = new(() => IsAnyOS(LinuxBSDPlatforms)); + private static readonly Lazy<bool> _isServer = new(() => IsOS(Names.Server)); + private static readonly Lazy<bool> _isUWP = new(() => IsOS(Names.UWP)); + private static readonly Lazy<bool> _isHaiku = new(() => IsOS(Names.Haiku)); + private static readonly Lazy<bool> _isAndroid = new(() => IsOS(Names.Android)); + private static readonly Lazy<bool> _isiOS = new(() => IsOS(Names.iOS)); + private static readonly Lazy<bool> _isHTML5 = new(() => IsOS(Names.HTML5)); + private static readonly Lazy<bool> _isUnixLike = new(() => IsAnyOS(UnixLikePlatforms)); + + [SupportedOSPlatformGuard("windows")] public static bool IsWindows => _isWindows.Value || IsUWP; + + [SupportedOSPlatformGuard("osx")] public static bool IsMacOS => _isMacOS.Value; + + [SupportedOSPlatformGuard("linux")] public static bool IsLinuxBSD => _isLinuxBSD.Value; + + [SupportedOSPlatformGuard("linux")] public static bool IsServer => _isServer.Value; + + [SupportedOSPlatformGuard("windows")] public static bool IsUWP => _isUWP.Value; + public static bool IsHaiku => _isHaiku.Value; - public static bool IsAndroid => _isAndroid.Value; - public static bool IsiOS => _isiOS.Value; - public static bool IsHTML5 => _isHTML5.Value; + + [SupportedOSPlatformGuard("android")] public static bool IsAndroid => _isAndroid.Value; + + [SupportedOSPlatformGuard("ios")] public static bool IsiOS => _isiOS.Value; + + [SupportedOSPlatformGuard("browser")] public static bool IsHTML5 => _isHTML5.Value; public static bool IsUnixLike => _isUnixLike.Value; public static char PathSep => IsWindows ? ';' : ':'; + [return: MaybeNull] public static string PathWhich([NotNull] string name) { if (IsWindows) @@ -112,9 +173,11 @@ namespace GodotTools.Utils return PathWhichUnix(name); } + [return: MaybeNull] private static string PathWhichWindows([NotNull] string name) { - string[] windowsExts = Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? Array.Empty<string>(); + string[] windowsExts = + Environment.GetEnvironmentVariable("PATHEXT")?.Split(PathSep) ?? Array.Empty<string>(); string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); char[] invalidPathChars = Path.GetInvalidPathChars(); @@ -133,7 +196,7 @@ namespace GodotTools.Utils string nameExt = Path.GetExtension(name); bool hasPathExt = !string.IsNullOrEmpty(nameExt) && - windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); + windowsExts.Contains(nameExt, StringComparer.OrdinalIgnoreCase); searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list @@ -141,12 +204,13 @@ namespace GodotTools.Utils return searchDirs.Select(dir => Path.Combine(dir, name)).FirstOrDefault(File.Exists); return (from dir in searchDirs - select Path.Combine(dir, name) + select Path.Combine(dir, name) into path - from ext in windowsExts - select path + ext).FirstOrDefault(File.Exists); + from ext in windowsExts + select path + ext).FirstOrDefault(File.Exists); } + [return: MaybeNull] private static string PathWhichUnix([NotNull] string name) { string[] pathDirs = Environment.GetEnvironmentVariable("PATH")?.Split(PathSep); @@ -168,19 +232,16 @@ namespace GodotTools.Utils searchDirs.Add(System.IO.Directory.GetCurrentDirectory()); // last in the list return searchDirs.Select(dir => Path.Combine(dir, name)) - .FirstOrDefault(path => File.Exists(path) && UnixFileHasExecutableAccess(path)); + .FirstOrDefault(path => + { + using godot_string pathIn = Marshaling.ConvertStringToNative(path); + return File.Exists(path) && Internal.godot_icall_Utils_OS_UnixFileHasExecutableAccess(pathIn); + }); } public static void RunProcess(string command, IEnumerable<string> arguments) { - // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead - string CmdLineArgsToString(IEnumerable<string> args) - { - // Not perfect, but as long as we are careful... - return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); - } - - var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)) + var startInfo = new ProcessStartInfo(command) { RedirectStandardOutput = true, RedirectStandardError = true, @@ -188,44 +249,104 @@ namespace GodotTools.Utils CreateNoWindow = true }; - using (Process process = Process.Start(startInfo)) - { - if (process == null) - throw new Exception("No process was started"); + foreach (string arg in arguments) + startInfo.ArgumentList.Add(arg); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - if (IsWindows && process.Id > 0) - User32Dll.AllowSetForegroundWindow(process.Id); // allows application to focus itself - } + using Process process = Process.Start(startInfo); + + if (process == null) + throw new Exception("No process was started"); + + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + if (IsWindows && process.Id > 0) + User32Dll.AllowSetForegroundWindow(process.Id); // Allows application to focus itself } public static int ExecuteCommand(string command, IEnumerable<string> arguments) { - // TODO: Once we move to .NET Standard 2.1 we can use ProcessStartInfo.ArgumentList instead - string CmdLineArgsToString(IEnumerable<string> args) + var startInfo = new ProcessStartInfo(command) { - // Not perfect, but as long as we are careful... - return string.Join(" ", args.Select(arg => arg.Contains(" ") ? $@"""{arg}""" : arg)); - } + // Print the output + RedirectStandardOutput = false, + RedirectStandardError = false, + UseShellExecute = false + }; - var startInfo = new ProcessStartInfo(command, CmdLineArgsToString(arguments)); + foreach (string arg in arguments) + startInfo.ArgumentList.Add(arg); - Console.WriteLine($"Executing: \"{startInfo.FileName}\" {startInfo.Arguments}"); + Console.WriteLine(startInfo.GetCommandLineDisplay(new StringBuilder("Executing: ")).ToString()); - // Print the output - startInfo.RedirectStandardOutput = false; - startInfo.RedirectStandardError = false; + using var process = new Process { StartInfo = startInfo }; + process.Start(); + process.WaitForExit(); + + return process.ExitCode; + } - startInfo.UseShellExecute = false; + private static void AppendProcessFileNameForDisplay(this StringBuilder builder, string fileName) + { + if (builder.Length > 0) + builder.Append(' '); - using (var process = new Process { StartInfo = startInfo }) + if (fileName.Contains(' ')) + { + builder.Append('"'); + builder.Append(fileName); + builder.Append('"'); + } + else { - process.Start(); - process.WaitForExit(); + builder.Append(fileName); + } + } + + private static void AppendProcessArgumentsForDisplay(this StringBuilder builder, + Collection<string> argumentList) + { + // This is intended just for reading. It doesn't need to be a valid command line. + // E.g.: We don't handle escaping of quotes. - return process.ExitCode; + foreach (string argument in argumentList) + { + if (builder.Length > 0) + builder.Append(' '); + + if (argument.Contains(' ')) + { + builder.Append('"'); + builder.Append(argument); + builder.Append('"'); + } + else + { + builder.Append(argument); + } } } + + public static StringBuilder GetCommandLineDisplay( + this ProcessStartInfo startInfo, + StringBuilder optionalBuilder = null + ) + { + var builder = optionalBuilder ?? new StringBuilder(); + + builder.AppendProcessFileNameForDisplay(startInfo.FileName); + + if (startInfo.ArgumentList.Count == 0) + { + builder.Append(' '); + builder.Append(startInfo.Arguments); + } + else + { + builder.AppendProcessArgumentsForDisplay(startInfo.ArgumentList); + } + + return builder; + } } } diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 2e628cb576..73d8f23081 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -41,69 +41,101 @@ #include "core/string/ucaps.h" #include "main/main.h" -#include "../glue/cs_glue_version.gen.h" #include "../godotsharp_defs.h" -#include "../mono_gd/gd_mono_marshal.h" #include "../utils/path_utils.h" #include "../utils/string_utils.h" +StringBuilder &operator<<(StringBuilder &r_sb, const String &p_string) { + r_sb.append(p_string); + return r_sb; +} + +StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { + r_sb.append(p_cstring); + return r_sb; +} + #define CS_INDENT " " // 4 whitespaces #define INDENT1 CS_INDENT #define INDENT2 INDENT1 INDENT1 #define INDENT3 INDENT2 INDENT1 #define INDENT4 INDENT3 INDENT1 -#define INDENT5 INDENT4 INDENT1 -#define MEMBER_BEGIN "\n" INDENT2 +#define MEMBER_BEGIN "\n" INDENT1 #define OPEN_BLOCK "{\n" #define CLOSE_BLOCK "}\n" -#define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK INDENT3 -#define OPEN_BLOCK_L3 INDENT3 OPEN_BLOCK INDENT4 +#define OPEN_BLOCK_L1 INDENT1 OPEN_BLOCK +#define OPEN_BLOCK_L2 INDENT2 OPEN_BLOCK +#define CLOSE_BLOCK_L1 INDENT1 CLOSE_BLOCK #define CLOSE_BLOCK_L2 INDENT2 CLOSE_BLOCK #define CLOSE_BLOCK_L3 INDENT3 CLOSE_BLOCK -#define CS_FIELD_MEMORYOWN "memoryOwn" +#define BINDINGS_GLOBAL_SCOPE_CLASS "GD" +#define BINDINGS_NATIVE_NAME_FIELD "NativeName" + +#define CS_PARAM_MEMORYOWN "memoryOwn" #define CS_PARAM_METHODBIND "method" #define CS_PARAM_INSTANCE "ptr" -#define CS_SMETHOD_GETINSTANCE "GetPtr" +#define CS_STATIC_METHOD_GETINSTANCE "GetPtr" #define CS_METHOD_CALL "Call" +#define CS_PROPERTY_SINGLETON "Singleton" +#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod" +#define CS_METHOD_HAS_GODOT_CLASS_METHOD "HasGodotClassMethod" + +#define CS_STATIC_FIELD_NATIVE_CTOR "NativeCtor" +#define CS_STATIC_FIELD_METHOD_BIND_PREFIX "MethodBind" +#define CS_STATIC_FIELD_METHOD_NAME_PREFIX "MethodName_" +#define CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX "MethodProxyName_" +#define CS_STATIC_FIELD_SIGNAL_NAME_PREFIX "SignalName_" -#define GLUE_HEADER_FILE "glue_header.h" #define ICALL_PREFIX "godot_icall_" -#define SINGLETON_ICALL_SUFFIX "_get_singleton" -#define ICALL_GET_METHODBIND "__ClassDB_get_method" +#define ICALL_CLASSDB_GET_METHOD "ClassDB_get_method" +#define ICALL_CLASSDB_GET_CONSTRUCTOR "ClassDB_get_constructor" #define C_LOCAL_RET "ret" #define C_LOCAL_VARARG_RET "vararg_ret" #define C_LOCAL_PTRCALL_ARGS "call_args" -#define C_MACRO_OBJECT_CONSTRUCT "GODOTSHARP_INSTANCE_OBJECT" - -#define C_NS_MONOUTILS "GDMonoUtils" -#define C_NS_MONOINTERNALS "GDMonoInternals" -#define C_METHOD_TIE_MANAGED_TO_UNMANAGED C_NS_MONOINTERNALS "::tie_managed_to_unmanaged" -#define C_METHOD_UNMANAGED_GET_MANAGED C_NS_MONOUTILS "::unmanaged_get_managed" - -#define C_NS_MONOMARSHAL "GDMonoMarshal" -#define C_METHOD_MANAGED_TO_VARIANT C_NS_MONOMARSHAL "::mono_object_to_variant" -#define C_METHOD_MANAGED_FROM_VARIANT C_NS_MONOMARSHAL "::variant_to_mono_object" -#define C_METHOD_MONOSTR_TO_GODOT C_NS_MONOMARSHAL "::mono_string_to_godot" -#define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL "::mono_string_from_godot" -#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL "::mono_array_to_" #m_type -#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL "::" #m_type "_to_mono_array" -#define C_METHOD_MANAGED_TO_CALLABLE C_NS_MONOMARSHAL "::managed_to_callable" -#define C_METHOD_MANAGED_FROM_CALLABLE C_NS_MONOMARSHAL "::callable_to_managed" -#define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL "::signal_info_to_callable" -#define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL "::callable_to_signal_info" - -#define BINDINGS_GENERATOR_VERSION UINT32_C(13) + +#define C_CLASS_NATIVE_FUNCS "NativeFuncs" +#define C_NS_MONOUTILS "InteropUtils" +#define C_METHOD_UNMANAGED_GET_MANAGED C_NS_MONOUTILS ".UnmanagedGetManaged" +#define C_METHOD_ENGINE_GET_SINGLETON C_NS_MONOUTILS ".EngineGetSingleton" + +#define C_NS_MONOMARSHAL "Marshaling" +#define C_METHOD_MONOSTR_TO_GODOT C_NS_MONOMARSHAL ".ConvertStringToNative" +#define C_METHOD_MONOSTR_FROM_GODOT C_NS_MONOMARSHAL ".ConvertStringToManaged" +#define C_METHOD_MONOARRAY_TO(m_type) C_NS_MONOMARSHAL ".ConvertSystemArrayToNative" #m_type +#define C_METHOD_MONOARRAY_FROM(m_type) C_NS_MONOMARSHAL ".ConvertNative" #m_type "ToSystemArray" +#define C_METHOD_MANAGED_TO_CALLABLE C_NS_MONOMARSHAL ".ConvertCallableToNative" +#define C_METHOD_MANAGED_FROM_CALLABLE C_NS_MONOMARSHAL ".ConvertCallableToManaged" +#define C_METHOD_MANAGED_TO_SIGNAL C_NS_MONOMARSHAL ".ConvertSignalToNative" +#define C_METHOD_MANAGED_FROM_SIGNAL C_NS_MONOMARSHAL ".ConvertSignalToManaged" // Types that will be ignored by the generator and won't be available in C#. const Vector<String> ignored_types = { "PhysicsServer3DExtension" }; -const char *BindingsGenerator::TypeInterface::DEFAULT_VARARG_C_IN("\t%0 %1_in = %1;\n"); +void BindingsGenerator::TypeInterface::postsetup_enum_type(BindingsGenerator::TypeInterface &r_enum_itype) { + // C interface for enums is the same as that of 'uint32_t'. Remember to apply + // any of the changes done here to the 'uint32_t' type interface as well. + + r_enum_itype.cs_type = r_enum_itype.proxy_name; + r_enum_itype.cs_in_expr = "(int)%0"; + r_enum_itype.cs_out = "%5return (%2)%0(%1);"; + + { + // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'. + r_enum_itype.c_in = "%5%0 %1_in = %1;\n"; + r_enum_itype.c_out = "%5return (%0)%1;\n"; + r_enum_itype.c_type = "long"; + r_enum_itype.c_arg_in = "&%s_in"; + } + r_enum_itype.c_type_in = "int"; + r_enum_itype.c_type_out = r_enum_itype.c_type_in; + r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name]; +} static String fix_doc_description(const String &p_bbcode) { // This seems to be the correct way to do this. It's the same EditorHelp does. @@ -359,23 +391,23 @@ String BindingsGenerator::bbcode_to_xml(const String &p_bbcode, const TypeInterf xml_output.append(tag); xml_output.append("</c>"); } else if (tag == "PackedByteArray") { - xml_output.append("<see cref=\"T:byte[]\"/>"); + xml_output.append("<see cref=\"byte\"/>[]"); } else if (tag == "PackedInt32Array") { - xml_output.append("<see cref=\"T:int[]\"/>"); + xml_output.append("<see cref=\"int\"/>[]"); } else if (tag == "PackedInt64Array") { - xml_output.append("<see cref=\"T:long[]\"/>"); + xml_output.append("<see cref=\"long\"/>[]"); } else if (tag == "PackedFloat32Array") { - xml_output.append("<see cref=\"T:float[]\"/>"); + xml_output.append("<see cref=\"float\"/>[]"); } else if (tag == "PackedFloat64Array") { - xml_output.append("<see cref=\"T:double[]\"/>"); + xml_output.append("<see cref=\"double\"/>[]"); } else if (tag == "PackedStringArray") { - xml_output.append("<see cref=\"T:string[]\"/>"); + xml_output.append("<see cref=\"string\"/>[]"); } else if (tag == "PackedVector2Array") { - xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Vector2[]\"/>"); + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector2\"/>[]"); } else if (tag == "PackedVector3Array") { - xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Vector3[]\"/>"); + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Vector3\"/>[]"); } else if (tag == "PackedColorArray") { - xml_output.append("<see cref=\"T:" BINDINGS_NAMESPACE ".Color[]\"/>"); + xml_output.append("<see cref=\"" BINDINGS_NAMESPACE ".Color\"/>[]"); } else { const TypeInterface *target_itype = _get_type_or_null(TypeReference(tag)); @@ -794,49 +826,28 @@ void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumI } } -void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { +Error BindingsGenerator::_populate_method_icalls_table(const TypeInterface &p_itype) { for (const MethodInterface &imethod : p_itype.methods) { if (imethod.is_virtual) { continue; } - const TypeInterface *return_type = _get_type_or_placeholder(imethod.return_type); + const TypeInterface *return_type = _get_type_or_null(imethod.return_type); + ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found - String im_sig = "IntPtr " CS_PARAM_METHODBIND; - String im_unique_sig = imethod.return_type.cname.operator String() + ",IntPtr"; + String im_unique_sig = get_ret_unique_sig(return_type) + ",CallMethodBind"; if (!imethod.is_static) { - im_sig += ", IntPtr " CS_PARAM_INSTANCE; - im_unique_sig += ",IntPtr"; + im_unique_sig += ",CallInstance"; } // Get arguments information - int i = 0; for (const ArgumentInterface &iarg : imethod.arguments) { - const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); - - im_sig += ", "; - im_sig += arg_type->im_type_in; - im_sig += " arg"; - im_sig += itos(i + 1); + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found im_unique_sig += ","; - im_unique_sig += get_unique_sig(*arg_type); - - i++; - } - - String im_type_out = return_type->im_type_out; - - if (return_type->ret_as_byref_arg) { - // Doesn't affect the unique signature - im_type_out = "void"; - - im_sig += ", "; - im_sig += return_type->im_type_out; - im_sig += " argRet"; - - i++; + im_unique_sig += get_arg_unique_sig(*arg_type); } // godot_icall_{argc}_{icallcount} @@ -845,7 +856,15 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { icall_method += "_"; icall_method += itos(method_icalls.size()); - InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, im_type_out, im_sig, im_unique_sig); + InternalCall im_icall = InternalCall(p_itype.api_type, icall_method, im_unique_sig); + + im_icall.is_vararg = imethod.is_vararg; + im_icall.is_static = imethod.is_static; + im_icall.return_type = imethod.return_type; + + for (const List<ArgumentInterface>::Element *F = imethod.arguments.front(); F; F = F->next()) { + im_icall.argument_types.push_back(F->get().type); + } List<InternalCall>::Element *match = method_icalls.find(im_icall); @@ -859,47 +878,49 @@ void BindingsGenerator::_generate_method_icalls(const TypeInterface &p_itype) { method_icalls_map.insert(&imethod, &added->get()); } } + + return OK; } void BindingsGenerator::_generate_array_extensions(StringBuilder &p_output) { + p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); p_output.append("using System;\n\n"); - p_output.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); // The class where we put the extensions doesn't matter, so just use "GD". - p_output.append(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{"); + p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{"); #define ARRAY_IS_EMPTY(m_type) \ - p_output.append("\n" INDENT2 "/// <summary>\n"); \ - p_output.append(INDENT2 "/// Returns true if this " #m_type " array is empty or doesn't exist.\n"); \ - p_output.append(INDENT2 "/// </summary>\n"); \ - p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array check.</param>\n"); \ - p_output.append(INDENT2 "/// <returns>Whether or not the array is empty.</returns>\n"); \ - p_output.append(INDENT2 "public static bool IsEmpty(this " #m_type "[] instance)\n"); \ - p_output.append(INDENT2 OPEN_BLOCK); \ - p_output.append(INDENT3 "return instance == null || instance.Length == 0;\n"); \ - p_output.append(INDENT2 CLOSE_BLOCK); + p_output.append("\n" INDENT1 "/// <summary>\n"); \ + p_output.append(INDENT1 "/// Returns true if this " #m_type " array is empty or doesn't exist.\n"); \ + p_output.append(INDENT1 "/// </summary>\n"); \ + p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array check.</param>\n"); \ + p_output.append(INDENT1 "/// <returns>Whether or not the array is empty.</returns>\n"); \ + p_output.append(INDENT1 "public static bool IsEmpty(this " #m_type "[] instance)\n"); \ + p_output.append(OPEN_BLOCK_L1); \ + p_output.append(INDENT2 "return instance == null || instance.Length == 0;\n"); \ + p_output.append(INDENT1 CLOSE_BLOCK); #define ARRAY_JOIN(m_type) \ - p_output.append("\n" INDENT2 "/// <summary>\n"); \ - p_output.append(INDENT2 "/// Converts this " #m_type " array to a string delimited by the given string.\n"); \ - p_output.append(INDENT2 "/// </summary>\n"); \ - p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ - p_output.append(INDENT2 "/// <param name=\"delimiter\">The delimiter to use between items.</param>\n"); \ - p_output.append(INDENT2 "/// <returns>A single string with all items.</returns>\n"); \ - p_output.append(INDENT2 "public static string Join(this " #m_type "[] instance, string delimiter = \", \")\n"); \ - p_output.append(INDENT2 OPEN_BLOCK); \ - p_output.append(INDENT3 "return String.Join(delimiter, instance);\n"); \ - p_output.append(INDENT2 CLOSE_BLOCK); + p_output.append("\n" INDENT1 "/// <summary>\n"); \ + p_output.append(INDENT1 "/// Converts this " #m_type " array to a string delimited by the given string.\n"); \ + p_output.append(INDENT1 "/// </summary>\n"); \ + p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ + p_output.append(INDENT1 "/// <param name=\"delimiter\">The delimiter to use between items.</param>\n"); \ + p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \ + p_output.append(INDENT1 "public static string Join(this " #m_type "[] instance, string delimiter = \", \")\n"); \ + p_output.append(OPEN_BLOCK_L1); \ + p_output.append(INDENT2 "return String.Join(delimiter, instance);\n"); \ + p_output.append(INDENT1 CLOSE_BLOCK); #define ARRAY_STRINGIFY(m_type) \ - p_output.append("\n" INDENT2 "/// <summary>\n"); \ - p_output.append(INDENT2 "/// Converts this " #m_type " array to a string with brackets.\n"); \ - p_output.append(INDENT2 "/// </summary>\n"); \ - p_output.append(INDENT2 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ - p_output.append(INDENT2 "/// <returns>A single string with all items.</returns>\n"); \ - p_output.append(INDENT2 "public static string Stringify(this " #m_type "[] instance)\n"); \ - p_output.append(INDENT2 OPEN_BLOCK); \ - p_output.append(INDENT3 "return \"[\" + instance.Join() + \"]\";\n"); \ - p_output.append(INDENT2 CLOSE_BLOCK); + p_output.append("\n" INDENT1 "/// <summary>\n"); \ + p_output.append(INDENT1 "/// Converts this " #m_type " array to a string with brackets.\n"); \ + p_output.append(INDENT1 "/// </summary>\n"); \ + p_output.append(INDENT1 "/// <param name=\"instance\">The " #m_type " array to convert.</param>\n"); \ + p_output.append(INDENT1 "/// <returns>A single string with all items.</returns>\n"); \ + p_output.append(INDENT1 "public static string Stringify(this " #m_type "[] instance)\n"); \ + p_output.append(OPEN_BLOCK_L1); \ + p_output.append(INDENT2 "return \"[\" + instance.Join() + \"]\";\n"); \ + p_output.append(INDENT1 CLOSE_BLOCK); #define ARRAY_ALL(m_type) \ ARRAY_IS_EMPTY(m_type) \ @@ -925,18 +946,18 @@ void BindingsGenerator::_generate_array_extensions(StringBuilder &p_output) { #undef ARRAY_JOIN #undef ARRAY_STRINGIFY - p_output.append(INDENT1 CLOSE_BLOCK); // End of GD class. - p_output.append(CLOSE_BLOCK); // End of namespace. + p_output.append(CLOSE_BLOCK); // End of GD class. } void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { // Constants (in partial GD class) + p_output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); + p_output.append("\n#pragma warning disable CS1591 // Disable warning: " "'Missing XML comment for publicly visible type or member'\n"); - p_output.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - p_output.append(INDENT1 "public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n" INDENT1 "{"); + p_output.append("public static partial class " BINDINGS_GLOBAL_SCOPE_CLASS "\n{"); for (const ConstantInterface &iconstant : global_constants) { if (iconstant.const_doc && iconstant.const_doc->description.size()) { @@ -947,12 +968,12 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>"); + p_output.append(INDENT1 "/// </summary>"); } } @@ -967,7 +988,7 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { p_output.append("\n"); } - p_output.append(INDENT1 CLOSE_BLOCK); // end of GD class + p_output.append(CLOSE_BLOCK); // end of GD class // Enums @@ -985,21 +1006,21 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { CRASH_COND(enum_class_name != "Variant"); // Hard-coded... - _log("Declaring global enum '%s' inside static class '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); + _log("Declaring global enum '%s' inside struct '%s'\n", enum_proxy_name.utf8().get_data(), enum_class_name.utf8().get_data()); - p_output.append("\n" INDENT1 "public static partial class "); + p_output.append("\npublic partial struct "); p_output.append(enum_class_name); - p_output.append("\n" INDENT1 OPEN_BLOCK); + p_output.append("\n" OPEN_BLOCK); } if (ienum.is_flags) { - p_output.append("\n" INDENT1 "[System.Flags]"); + p_output.append("\n[System.Flags]"); } - p_output.append("\n" INDENT1 "public enum "); + p_output.append("\npublic enum "); p_output.append(enum_proxy_name); p_output.append(" : long"); - p_output.append("\n" INDENT1 OPEN_BLOCK); + p_output.append("\n" OPEN_BLOCK); const ConstantInterface &last = ienum.constants.back()->get(); for (const ConstantInterface &iconstant : ienum.constants) { @@ -1008,34 +1029,32 @@ void BindingsGenerator::_generate_global_constants(StringBuilder &p_output) { Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - p_output.append(INDENT2 "/// <summary>\n"); + p_output.append(INDENT1 "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>\n"); + p_output.append(INDENT1 "/// </summary>\n"); } } - p_output.append(INDENT2); + p_output.append(INDENT1); p_output.append(iconstant.proxy_name); p_output.append(" = "); p_output.append(itos(iconstant.value)); p_output.append(&iconstant != &last ? ",\n" : "\n"); } - p_output.append(INDENT1 CLOSE_BLOCK); + p_output.append(CLOSE_BLOCK); if (enum_in_static_class) { - p_output.append(INDENT1 CLOSE_BLOCK); + p_output.append(CLOSE_BLOCK); } } - p_output.append(CLOSE_BLOCK); // end of namespace - p_output.append("\n#pragma warning restore CS1591\n"); } @@ -1106,42 +1125,38 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { compile_items.push_back(output_file); } - // Generate sources from compressed files + // Generate native calls StringBuilder cs_icalls_content; + cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n"); cs_icalls_content.append("using System;\n" - "using System.Runtime.CompilerServices;\n" + "using System.Diagnostics.CodeAnalysis;\n" + "using System.Runtime.InteropServices;\n" + "using Godot.NativeInterop;\n" "\n"); - cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS "\n" INDENT1 "{"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n"); + cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); + cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS "\n{"); cs_icalls_content.append(MEMBER_BEGIN "internal static ulong godot_api_hash = "); - cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + ";\n"); - cs_icalls_content.append(MEMBER_BEGIN "internal static uint bindings_version = "); - cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); - cs_icalls_content.append(MEMBER_BEGIN "internal static uint cs_glue_version = "); - cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n"); + cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_CORE)) + ";\n"); -#define ADD_INTERNAL_CALL(m_icall) \ - if (!m_icall.editor_only) { \ - cs_icalls_content.append(MEMBER_BEGIN "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.append(INDENT2 "internal static extern "); \ - cs_icalls_content.append(m_icall.im_type_out + " "); \ - cs_icalls_content.append(m_icall.name + "("); \ - cs_icalls_content.append(m_icall.im_sig + ");\n"); \ - } + cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n"); - for (const InternalCall &internal_call : core_custom_icalls) { - ADD_INTERNAL_CALL(internal_call); - } - for (const InternalCall &internal_call : method_icalls) { - ADD_INTERNAL_CALL(internal_call); + for (const InternalCall &icall : method_icalls) { + if (icall.editor_only) { + continue; + } + Error err = _generate_cs_native_calls(icall, cs_icalls_content); + if (err != OK) { + return err; + } } -#undef ADD_INTERNAL_CALL - - cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + cs_icalls_content.append(CLOSE_BLOCK); String internal_methods_file = path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS ".cs"); @@ -1152,6 +1167,8 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_proj_dir) { compile_items.push_back(internal_methods_file); + // Generate GeneratedIncludes.props + StringBuilder includes_props_content; includes_props_content.append("<Project>\n" " <ItemGroup>\n"); @@ -1215,41 +1232,40 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) { compile_items.push_back(output_file); } + // Generate native calls + StringBuilder cs_icalls_content; + cs_icalls_content.append("namespace " BINDINGS_NAMESPACE ";\n\n"); cs_icalls_content.append("using System;\n" - "using System.Runtime.CompilerServices;\n" + "using System.Diagnostics.CodeAnalysis;\n" + "using System.Runtime.InteropServices;\n" + "using Godot.NativeInterop;\n" "\n"); - cs_icalls_content.append("namespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); - cs_icalls_content.append(INDENT1 "internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" INDENT1 OPEN_BLOCK); - - cs_icalls_content.append(INDENT2 "internal static ulong godot_api_hash = "); - cs_icalls_content.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + ";\n"); - cs_icalls_content.append(INDENT2 "internal static uint bindings_version = "); - cs_icalls_content.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + ";\n"); - cs_icalls_content.append(INDENT2 "internal static uint cs_glue_version = "); - cs_icalls_content.append(String::num_uint64(CS_GLUE_VERSION) + ";\n"); - cs_icalls_content.append("\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"InconsistentNaming\")]\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantUnsafeContext\")]\n"); + cs_icalls_content.append("[SuppressMessage(\"ReSharper\", \"RedundantNameQualifier\")]\n"); + cs_icalls_content.append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); + cs_icalls_content.append("internal static class " BINDINGS_CLASS_NATIVECALLS_EDITOR "\n" OPEN_BLOCK); -#define ADD_INTERNAL_CALL(m_icall) \ - if (m_icall.editor_only) { \ - cs_icalls_content.append(INDENT2 "[MethodImpl(MethodImplOptions.InternalCall)]\n"); \ - cs_icalls_content.append(INDENT2 "internal static extern "); \ - cs_icalls_content.append(m_icall.im_type_out + " "); \ - cs_icalls_content.append(m_icall.name + "("); \ - cs_icalls_content.append(m_icall.im_sig + ");\n"); \ - } + cs_icalls_content.append(INDENT1 "internal static ulong godot_api_hash = "); + cs_icalls_content.append(String::num_uint64(ClassDB::get_api_hash(ClassDB::API_EDITOR)) + ";\n"); - for (const InternalCall &internal_call : editor_custom_icalls) { - ADD_INTERNAL_CALL(internal_call); - } - for (const InternalCall &internal_call : method_icalls) { - ADD_INTERNAL_CALL(internal_call); - } + cs_icalls_content.append(MEMBER_BEGIN "private const int VarArgsSpanThreshold = 10;\n"); -#undef ADD_INTERNAL_CALL + cs_icalls_content.append("\n"); - cs_icalls_content.append(INDENT1 CLOSE_BLOCK CLOSE_BLOCK); + for (const InternalCall &icall : method_icalls) { + if (!icall.editor_only) { + continue; + } + Error err = _generate_cs_native_calls(icall, cs_icalls_content); + if (err != OK) { + return err; + } + } + + cs_icalls_content.append(CLOSE_BLOCK); String internal_methods_file = path::join(base_gen_dir, BINDINGS_CLASS_NATIVECALLS_EDITOR ".cs"); @@ -1260,6 +1276,8 @@ Error BindingsGenerator::generate_cs_editor_project(const String &p_proj_dir) { compile_items.push_back(internal_methods_file); + // Generate GeneratedIncludes.props + StringBuilder includes_props_content; includes_props_content.append("<Project>\n" " <ItemGroup>\n"); @@ -1343,16 +1361,15 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str CRASH_COND(itype.is_singleton); } - List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; - _log("Generating %s.cs...\n", itype.proxy_name.utf8().get_data()); - String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types - StringBuilder output; + output.append("namespace " BINDINGS_NAMESPACE ";\n\n"); + output.append("using System;\n"); // IntPtr output.append("using System.Diagnostics;\n"); // DebuggerBrowsable + output.append("using Godot.NativeInterop;\n"); output.append("\n" "#pragma warning disable CS1591 // Disable warning: " @@ -1360,7 +1377,7 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str "#pragma warning disable CS1573 // Disable warning: " "'Parameter has no matching param tag in the XML comment'\n"); - output.append("\nnamespace " BINDINGS_NAMESPACE "\n" OPEN_BLOCK); + output.append("\n#nullable disable\n"); const DocData::ClassDoc *class_doc = itype.class_doc; @@ -1369,40 +1386,48 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - output.append(INDENT1 "/// <summary>\n"); + output.append("/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - output.append(INDENT1 "/// "); + output.append("/// "); output.append(summary_lines[i]); output.append("\n"); } - output.append(INDENT1 "/// </summary>\n"); + output.append("/// </summary>\n"); } } - output.append(INDENT1 "public "); + // We generate a `GodotClassName` attribute if the engine class name is not the same as the + // generated C# class name. This allows introspection code to find the name associated with + // the class. If the attribute is not present, the C# class name can be used instead. + if (itype.name != itype.proxy_name) { + output << "[GodotClassName(\"" << itype.name << "\")]\n"; + } + + output.append("public "); if (itype.is_singleton) { output.append("static partial class "); } else { - output.append(itype.is_instantiable ? "partial class " : "abstract partial class "); + // Even if the class is not instantiable, we can't declare it abstract because + // the engine can still instantiate them and return them via the scripting API. + // Example: `SceneTreeTimer` returned from `SceneTree.create_timer`. + // See the reverted commit: ef5672d3f94a7321ed779c922088bb72adbb1521 + output.append("partial class "); } output.append(itype.proxy_name); - if (itype.is_singleton) { - output.append("\n"); - } else if (is_derived_type) { + if (is_derived_type && !itype.is_singleton) { if (obj_types.has(itype.base_name)) { output.append(" : "); output.append(obj_types[itype.base_name].proxy_name); - output.append("\n"); } else { ERR_PRINT("Base type '" + itype.base_name.operator String() + "' does not exist, for class '" + itype.name + "'."); return ERR_INVALID_DATA; } } - output.append(INDENT1 "{"); + output.append("\n{"); // Add constants @@ -1415,12 +1440,12 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - output.append(INDENT2 "/// "); + output.append(INDENT1 "/// "); output.append(summary_lines[i]); output.append("\n"); } - output.append(INDENT2 "/// </summary>"); + output.append(INDENT1 "/// </summary>"); } } @@ -1456,26 +1481,26 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str Vector<String> summary_lines = xml_summary.length() ? xml_summary.split("\n") : Vector<String>(); if (summary_lines.size()) { - output.append(INDENT3 "/// <summary>\n"); + output.append(INDENT2 "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - output.append(INDENT3 "/// "); + output.append(INDENT2 "/// "); output.append(summary_lines[i]); output.append("\n"); } - output.append(INDENT3 "/// </summary>\n"); + output.append(INDENT2 "/// </summary>\n"); } } - output.append(INDENT3); + output.append(INDENT2); output.append(iconstant.proxy_name); output.append(" = "); output.append(itos(iconstant.value)); output.append(&iconstant != &last ? ",\n" : "\n"); } - output.append(INDENT2 CLOSE_BLOCK); + output.append(INDENT1 CLOSE_BLOCK); } // Add properties @@ -1491,55 +1516,68 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Add the type name and the singleton pointer as static fields output.append(MEMBER_BEGIN "private static Godot.Object singleton;\n"); - output.append(MEMBER_BEGIN "public static Godot.Object Singleton\n" INDENT2 "{\n" INDENT3 - "get\n" INDENT3 "{\n" INDENT4 "if (singleton == null)\n" INDENT5 - "singleton = Engine.GetSingleton(typeof("); - output.append(itype.proxy_name); - output.append(").Name);\n" INDENT4 "return singleton;\n" INDENT3 "}\n" INDENT2 "}\n"); - output.append(MEMBER_BEGIN "private static StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); + output << MEMBER_BEGIN "public static Godot.Object " CS_PROPERTY_SINGLETON "\n" INDENT1 "{\n" + << INDENT2 "get\n" INDENT2 "{\n" INDENT3 "if (singleton == null)\n" + << INDENT4 "singleton = " C_METHOD_ENGINE_GET_SINGLETON "(typeof(" + << itype.proxy_name + << ").Name);\n" INDENT3 "return singleton;\n" INDENT2 "}\n" INDENT1 "}\n"; + + output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); output.append(itype.name); output.append("\";\n"); + } else { + // IMPORTANT: We also generate the static fields for Godot.Object instead of declaring + // them manually in the `Object.base.cs` partial class declaration, because they're + // required by other static fields in this generated partial class declaration. + // Static fields are initialized in order of declaration, but when they're in different + // partial class declarations then it becomes harder to tell (Rider warns about this). - output.append(INDENT2 "internal static IntPtr " BINDINGS_PTR_FIELD " = "); - output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); - output.append("." ICALL_PREFIX); - output.append(itype.name); - output.append(SINGLETON_ICALL_SUFFIX "();\n"); - } else if (is_derived_type) { - // Add member fields + // Add native name static field - output.append(MEMBER_BEGIN "private static StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); + if (is_derived_type) { + output << MEMBER_BEGIN "private static readonly System.Type CachedType = typeof(" << itype.proxy_name << ");\n"; + } + + output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \""); output.append(itype.name); output.append("\";\n"); - // Add default constructor if (itype.is_instantiable) { - output.append(MEMBER_BEGIN "public "); - output.append(itype.proxy_name); - output.append("() : this("); - output.append(itype.memory_own ? "true" : "false"); - - // The default constructor may also be called by the engine when instancing existing native objects - // The engine will initialize the pointer field of the managed side before calling the constructor - // This is why we only allocate a new native object from the constructor if the pointer field is not set - output.append(")\n" OPEN_BLOCK_L2 "if (" BINDINGS_PTR_FIELD " == IntPtr.Zero)\n" INDENT4 BINDINGS_PTR_FIELD " = "); - output.append(itype.api_type == ClassDB::API_EDITOR ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); - output.append("." + ctor_method); - output.append("(this);\n" INDENT3 "_InitializeGodotScriptInstanceInternals();\n" CLOSE_BLOCK_L2); - } else { - // Hide the constructor + // Add native constructor static field + + output << MEMBER_BEGIN << "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT1 "private static readonly unsafe delegate* unmanaged<IntPtr> " + << CS_STATIC_FIELD_NATIVE_CTOR " = " ICALL_CLASSDB_GET_CONSTRUCTOR + << "(" BINDINGS_NATIVE_NAME_FIELD ");\n"; + } + + if (is_derived_type) { + // Add default constructor + if (itype.is_instantiable) { + output << MEMBER_BEGIN "public " << itype.proxy_name << "() : this(" + << (itype.memory_own ? "true" : "false") << ")\n" OPEN_BLOCK_L1 + << INDENT2 "unsafe\n" INDENT2 OPEN_BLOCK + << INDENT3 "_ConstructAndInitialize(" CS_STATIC_FIELD_NATIVE_CTOR ", " + << BINDINGS_NATIVE_NAME_FIELD ", CachedType, refCounted: " + << (itype.is_ref_counted ? "true" : "false") << ");\n" + << CLOSE_BLOCK_L2 CLOSE_BLOCK_L1; + } else { + // Hide the constructor + output.append(MEMBER_BEGIN "internal "); + output.append(itype.proxy_name); + output.append("() {}\n"); + } + + // Add.. em.. trick constructor. Sort of. output.append(MEMBER_BEGIN "internal "); output.append(itype.proxy_name); - output.append("() {}\n"); + output.append("(bool " CS_PARAM_MEMORYOWN ") : base(" CS_PARAM_MEMORYOWN ") {}\n"); } - - // Add.. em.. trick constructor. Sort of. - output.append(MEMBER_BEGIN "internal "); - output.append(itype.proxy_name); - output.append("(bool " CS_FIELD_MEMORYOWN ") : base(" CS_FIELD_MEMORYOWN ") {}\n"); } + // Methods + int method_bind_count = 0; for (const MethodInterface &imethod : itype.methods) { Error method_err = _generate_cs_method(itype, imethod, method_bind_count, output); @@ -1547,30 +1585,153 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'."); } + // Signals + for (const SignalInterface &isignal : itype.signals_) { Error method_err = _generate_cs_signal(itype, isignal, output); ERR_FAIL_COND_V_MSG(method_err != OK, method_err, "Failed to generate signal '" + isignal.name + "' for class '" + itype.name + "'."); } - if (itype.is_singleton) { - InternalCall singleton_icall = InternalCall(itype.api_type, ICALL_PREFIX + itype.name + SINGLETON_ICALL_SUFFIX, "IntPtr"); + // Script members look-up - if (!find_icall_by_name(singleton_icall.name, custom_icalls)) { - custom_icalls.push_back(singleton_icall); + if (!itype.is_singleton && (is_derived_type || itype.has_virtual_methods)) { + // Generate method names cache fields + + for (const MethodInterface &imethod : itype.methods) { + if (!imethod.is_virtual) { + continue; + } + + output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n" + << INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT1 "private static readonly StringName " + << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name + << " = \"" << imethod.name << "\";\n"; + + output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n" + << INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT1 "private static readonly StringName " + << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name + << " = \"" << imethod.proxy_name << "\";\n"; } - } - if (is_derived_type && itype.is_instantiable) { - InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj"); + // TODO: Only generate HasGodotClassMethod and InvokeGodotClassMethod if there's any method + + // Generate InvokeGodotClassMethod + + output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") + << " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, " + << "NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n" + << INDENT1 "{\n"; + + for (const MethodInterface &imethod : itype.methods) { + if (!imethod.is_virtual) { + continue; + } + + // We also call HasGodotClassMethod to ensure the method is overridden and avoid calling + // the stub implementation. This solution adds some extra overhead to calls, but it's + // much simpler than other solutions. This won't be a problem once we move to function + // pointers of generated wrappers for each method, as lookup will only happen once. + + // We check both native names (snake_case) and proxy names (PascalCase) + output << INDENT2 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name + << " || method == " << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name + << ") && argCount == " << itos(imethod.arguments.size()) + << " && " << CS_METHOD_HAS_GODOT_CLASS_METHOD << "((godot_string_name)" + << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name << ".NativeValue))\n" + << INDENT2 "{\n"; + + if (imethod.return_type.cname != name_cache.type_void) { + output << INDENT3 "var callRet = "; + } else { + output << INDENT3; + } + + output << imethod.proxy_name << "("; + + for (int i = 0; i < imethod.arguments.size(); i++) { + const ArgumentInterface &iarg = imethod.arguments[i]; + + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found + + if (i != 0) { + output << ", "; + } + + if (arg_type->cname == name_cache.type_Array_generic || arg_type->cname == name_cache.type_Dictionary_generic) { + String arg_cs_type = arg_type->cs_type + _get_generic_type_parameters(*arg_type, iarg.type.generic_type_parameters); + + output << "new " << arg_cs_type << "(" << sformat(arg_type->cs_variant_to_managed, "args[" + itos(i) + "]", arg_type->cs_type, arg_type->name) << ")"; + } else { + output << sformat(arg_type->cs_variant_to_managed, + "args[" + itos(i) + "]", arg_type->cs_type, arg_type->name); + } + } + + output << ");\n"; + + if (imethod.return_type.cname != name_cache.type_void) { + const TypeInterface *return_type = _get_type_or_null(imethod.return_type); + ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found + + output << INDENT3 "ret = " + << sformat(return_type->cs_managed_to_variant, "callRet", return_type->cs_type, return_type->name) + << ";\n" + << INDENT3 "return true;\n"; + } else { + output << INDENT3 "ret = default;\n" + << INDENT3 "return true;\n"; + } + + output << INDENT2 "}\n"; + } + + if (is_derived_type) { + output << INDENT2 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, argCount, out ret);\n"; + } else { + output << INDENT2 "ret = default;\n" + << INDENT2 "return false;\n"; + } + + output << INDENT1 "}\n"; + + // Generate HasGodotClassMethod + + output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") + << " bool " CS_METHOD_HAS_GODOT_CLASS_METHOD "(in godot_string_name method)\n" + << INDENT1 "{\n"; + + for (const MethodInterface &imethod : itype.methods) { + if (!imethod.is_virtual) { + continue; + } - if (!find_icall_by_name(ctor_icall.name, custom_icalls)) { - custom_icalls.push_back(ctor_icall); + // We check for native names (snake_case). If we detect one, we call HasGodotClassMethod + // again, but this time with the respective proxy name (PascalCase). It's the job of + // user derived classes to override the method and check for those. Our C# source + // generators take care of generating those override methods. + output << INDENT2 "if (method == " << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name + << ")\n" INDENT2 "{\n" + << INDENT3 "if (" CS_METHOD_HAS_GODOT_CLASS_METHOD "(" + << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name + << ".NativeValue.DangerousSelfRef))\n" INDENT3 "{\n" + << INDENT4 "return true;\n" + << INDENT3 "}\n" INDENT2 "}\n"; } + + if (is_derived_type) { + output << INDENT2 "return base." CS_METHOD_HAS_GODOT_CLASS_METHOD "(method);\n"; + } else { + output << INDENT2 "return false;\n"; + } + + output << INDENT1 "}\n"; } - output.append(INDENT1 CLOSE_BLOCK /* class */ - CLOSE_BLOCK /* namespace */); + output.append(CLOSE_BLOCK /* class */); output.append("\n" "#pragma warning restore CS1591\n" @@ -1649,12 +1810,12 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>"); + p_output.append(INDENT1 "/// </summary>"); } } @@ -1669,15 +1830,15 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte p_output.append(prop_cs_type); p_output.append(" "); p_output.append(p_iprop.proxy_name); - p_output.append("\n" INDENT2 OPEN_BLOCK); + p_output.append("\n" OPEN_BLOCK_L1); if (getter) { - p_output.append(INDENT3 "get\n" + p_output.append(INDENT2 "get\n" // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) "#pragma warning disable CS0618 // Disable warning about obsolete method\n" - OPEN_BLOCK_L3); + OPEN_BLOCK_L2 INDENT3); p_output.append("return "); p_output.append(getter->proxy_name + "("); @@ -1694,19 +1855,19 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte } p_output.append(");\n" - CLOSE_BLOCK_L3 + CLOSE_BLOCK_L2 // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) "#pragma warning restore CS0618\n"); } if (setter) { - p_output.append(INDENT3 "set\n" + p_output.append(INDENT2 "set\n" // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) "#pragma warning disable CS0618 // Disable warning about obsolete method\n" - OPEN_BLOCK_L3); + OPEN_BLOCK_L2 INDENT3); p_output.append(setter->proxy_name + "("); if (p_iprop.index != -1) { @@ -1722,19 +1883,20 @@ Error BindingsGenerator::_generate_cs_property(const BindingsGenerator::TypeInte } p_output.append("value);\n" - CLOSE_BLOCK_L3 + CLOSE_BLOCK_L2 // TODO Remove this once we make accessor methods private/internal (they will no longer be marked as obsolete after that) "#pragma warning restore CS0618\n"); } - p_output.append(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L1); return OK; } Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output) { - const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type); + const TypeInterface *return_type = _get_type_or_null(p_imethod.return_type); + ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found ERR_FAIL_COND_V_MSG(return_type->is_singleton, ERR_BUG, "Method return type is a singleton: '" + p_itype.name + "." + p_imethod.name + "'."); @@ -1745,14 +1907,21 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf "' from the editor API. Core API cannot have dependencies on the editor API."); } - String method_bind_field = "__method_bind_" + itos(p_method_bind_count); + String method_bind_field = CS_STATIC_FIELD_METHOD_BIND_PREFIX + itos(p_method_bind_count); String arguments_sig; - String cs_in_statements; + StringBuilder cs_in_statements; + bool cs_in_expr_is_unsafe = false; String icall_params = method_bind_field; + if (!p_imethod.is_static) { - icall_params += ", " + sformat(p_itype.cs_in, "this"); + if (p_itype.cs_in.size()) { + cs_in_statements << sformat(p_itype.cs_in, p_itype.c_type, "this", + String(), String(), String(), INDENT2); + } + + icall_params += ", " + sformat(p_itype.cs_in_expr, "this"); } StringBuilder default_args_doc; @@ -1760,7 +1929,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Retrieve information from the arguments const ArgumentInterface &first = p_imethod.arguments.front()->get(); for (const ArgumentInterface &iarg : p_imethod.arguments) { - const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, "Argument type is a singleton: '" + iarg.name + "' of method '" + p_itype.name + "." + p_imethod.name + "'."); @@ -1813,27 +1983,23 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf if (iarg.default_argument.size() && iarg.def_param_mode != ArgumentInterface::CONSTANT) { // The default value of an argument must be constant. Otherwise we make it Nullable and do the following: // Type arg_in = arg.HasValue ? arg.Value : <non-const default value>; - String arg_in = iarg.name; - arg_in += "_in"; + String arg_or_defval_local = iarg.name; + arg_or_defval_local += "OrDefVal"; - cs_in_statements += arg_cs_type; - cs_in_statements += " "; - cs_in_statements += arg_in; - cs_in_statements += " = "; - cs_in_statements += iarg.name; + cs_in_statements << INDENT2 << arg_cs_type << " " << arg_or_defval_local << " = " << iarg.name; if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) { - cs_in_statements += ".HasValue ? "; + cs_in_statements << ".HasValue ? "; } else { - cs_in_statements += " != null ? "; + cs_in_statements << " != null ? "; } - cs_in_statements += iarg.name; + cs_in_statements << iarg.name; if (iarg.def_param_mode == ArgumentInterface::NULLABLE_VAL) { - cs_in_statements += ".Value : "; + cs_in_statements << ".Value : "; } else { - cs_in_statements += " : "; + cs_in_statements << " : "; } String cs_type = arg_cs_type; @@ -1843,10 +2009,18 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf String def_arg = sformat(iarg.default_argument, cs_type); - cs_in_statements += def_arg; - cs_in_statements += ";\n" INDENT3; + cs_in_statements << def_arg << ";\n"; + + if (arg_type->cs_in.size()) { + cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, arg_or_defval_local, + String(), String(), String(), INDENT2); + } - icall_params += arg_type->cs_in.is_empty() ? arg_in : sformat(arg_type->cs_in, arg_in); + if (arg_type->cs_in_expr.is_empty()) { + icall_params += arg_or_defval_local; + } else { + icall_params += sformat(arg_type->cs_in_expr, arg_or_defval_local, arg_type->c_type); + } // Apparently the name attribute must not include the @ String param_tag_name = iarg.name.begins_with("@") ? iarg.name.substr(1, iarg.name.length()) : iarg.name; @@ -1855,18 +2029,32 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf default_args_doc.append(MEMBER_BEGIN "/// <param name=\"" + param_tag_name + "\">If the parameter is null, then the default value is <c>" + param_def_arg + "</c>.</param>"); } else { - icall_params += arg_type->cs_in.is_empty() ? iarg.name : sformat(arg_type->cs_in, iarg.name); + if (arg_type->cs_in.size()) { + cs_in_statements << sformat(arg_type->cs_in, arg_type->c_type, iarg.name, + String(), String(), String(), INDENT2); + } + + icall_params += arg_type->cs_in_expr.is_empty() ? iarg.name : sformat(arg_type->cs_in_expr, iarg.name, arg_type->c_type); } + + cs_in_expr_is_unsafe |= arg_type->cs_in_expr_is_unsafe; } // Generate method { if (!p_imethod.is_virtual && !p_imethod.requires_object_call) { - p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static readonly IntPtr "); - p_output.append(method_bind_field); - p_output.append(" = Object." ICALL_GET_METHODBIND "(" BINDINGS_NATIVE_NAME_FIELD ", \""); - p_output.append(p_imethod.name); - p_output.append("\");\n"); + p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT1 "private static readonly IntPtr " << method_bind_field << " = "; + + if (p_itype.is_singleton) { + // Singletons are static classes. They don't derive Godot.Object, + // so we need to specify the type to call the static method. + p_output << "Object."; + } + + p_output << ICALL_CLASSDB_GET_METHOD "(" BINDINGS_NATIVE_NAME_FIELD ", \"" + << p_imethod.name + << "\");\n"; } if (p_imethod.method_doc && p_imethod.method_doc->description.size()) { @@ -1877,12 +2065,12 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>"); + p_output.append(INDENT1 "/// </summary>"); } } @@ -1890,16 +2078,6 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append(default_args_doc.as_string()); } - if (!p_imethod.is_internal) { - // TODO: This alone adds ~0.2 MB of bloat to the core API assembly. It would be - // better to generate a table in the C++ glue instead. That way the strings wouldn't - // add that much extra bloat as they're already used in engine code. Also, it would - // probably be much faster than looking up the attributes when fetching methods. - p_output.append(MEMBER_BEGIN "[GodotMethod(\""); - p_output.append(p_imethod.name); - p_output.append("\")]"); - } - if (p_imethod.is_deprecated) { if (p_imethod.deprecation_message.is_empty()) { WARN_PRINT("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'."); @@ -1919,21 +2097,23 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append("virtual "); } + if (cs_in_expr_is_unsafe) { + p_output.append("unsafe "); + } + String return_cs_type = return_type->cs_type + _get_generic_type_parameters(*return_type, p_imethod.return_type.generic_type_parameters); p_output.append(return_cs_type + " "); p_output.append(p_imethod.proxy_name + "("); - p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L2); + p_output.append(arguments_sig + ")\n" OPEN_BLOCK_L1); if (p_imethod.is_virtual) { // Godot virtual method must be overridden, therefore we return a default value by default. if (return_type->cname == name_cache.type_void) { - p_output.append("return;\n" CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L1); } else { - p_output.append("return default("); - p_output.append(return_cs_type); - p_output.append(");\n" CLOSE_BLOCK_L2); + p_output.append(INDENT2 "return default;\n" CLOSE_BLOCK_L1); } return OK; // Won't increment method bind count @@ -1942,7 +2122,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf if (p_imethod.requires_object_call) { // Fallback to Godot's object.Call(string, params) - p_output.append(CS_METHOD_CALL "(\""); + p_output.append(INDENT2 CS_METHOD_CALL "(\""); p_output.append(p_imethod.name); p_output.append("\""); @@ -1951,7 +2131,7 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append(iarg.name); } - p_output.append(");\n" CLOSE_BLOCK_L2); + p_output.append(");\n" CLOSE_BLOCK_L1); return OK; // Won't increment method bind count } @@ -1965,20 +2145,21 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf im_call += "."; im_call += im_icall->name; - if (p_imethod.arguments.size()) { - p_output.append(cs_in_statements); + if (p_imethod.arguments.size() && cs_in_statements.get_string_length() > 0) { + p_output.append(cs_in_statements.as_string()); } if (return_type->cname == name_cache.type_void) { - p_output.append(im_call + "(" + icall_params + ");\n"); + p_output << INDENT2 << im_call << "(" << icall_params << ");\n"; } else if (return_type->cs_out.is_empty()) { - p_output.append("return " + im_call + "(" + icall_params + ");\n"); + p_output << INDENT2 "return " << im_call << "(" << icall_params << ");\n"; } else { - p_output.append(sformat(return_type->cs_out, im_call, icall_params, return_cs_type, return_type->im_type_out)); + p_output.append(sformat(return_type->cs_out, im_call, icall_params, + return_cs_type, return_type->c_type_out, String(), INDENT2)); p_output.append("\n"); } - p_output.append(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L1); } p_method_bind_count++; @@ -1992,7 +2173,8 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // Retrieve information from the arguments const ArgumentInterface &first = p_isignal.arguments.front()->get(); for (const ArgumentInterface &iarg : p_isignal.arguments) { - const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); + const TypeInterface *arg_type = _get_type_or_null(iarg.type); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Argument type not found ERR_FAIL_COND_V_MSG(arg_type->is_singleton, ERR_BUG, "Argument type is a singleton: '" + iarg.name + "' of signal '" + p_itype.name + "." + p_isignal.name + "'."); @@ -2024,12 +2206,12 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append(MEMBER_BEGIN "/// <summary>\n"); for (int i = 0; i < summary_lines.size(); i++) { - p_output.append(INDENT2 "/// "); + p_output.append(INDENT1 "/// "); p_output.append(summary_lines[i]); p_output.append("\n"); } - p_output.append(INDENT2 "/// </summary>"); + p_output.append(INDENT1 "/// </summary>"); } } @@ -2058,7 +2240,9 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // If so, we could store the pointer we get from `data_unique_pointer()` instead of allocating StringName here. // Cached signal name (StringName) - p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static StringName __signal_name_"); + p_output.append(MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"); + p_output.append(INDENT1 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN + "private static readonly StringName " CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); p_output.append(p_isignal.name); p_output.append(" = \""); p_output.append(p_isignal.name); @@ -2075,413 +2259,241 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf p_output.append(delegate_name); p_output.append(" "); p_output.append(p_isignal.proxy_name); - p_output.append("\n" OPEN_BLOCK_L2); + p_output.append("\n" OPEN_BLOCK_L1 INDENT2); if (p_itype.is_singleton) { - p_output.append("add => Singleton.Connect(__signal_name_"); + p_output.append("add => " CS_PROPERTY_SINGLETON ".Connect(" CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); } else { - p_output.append("add => Connect(__signal_name_"); + p_output.append("add => Connect(" CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); } p_output.append(p_isignal.name); p_output.append(", new Callable(value));\n"); if (p_itype.is_singleton) { - p_output.append(INDENT3 "remove => Singleton.Disconnect(__signal_name_"); + p_output.append(INDENT2 "remove => " CS_PROPERTY_SINGLETON ".Disconnect(" CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); } else { - p_output.append(INDENT3 "remove => Disconnect(__signal_name_"); + p_output.append(INDENT2 "remove => Disconnect(" CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); } p_output.append(p_isignal.name); p_output.append(", new Callable(value));\n"); - p_output.append(CLOSE_BLOCK_L2); + p_output.append(CLOSE_BLOCK_L1); } return OK; } -Error BindingsGenerator::generate_glue(const String &p_output_dir) { - ERR_FAIL_COND_V(!initialized, ERR_UNCONFIGURED); - - bool dir_exists = DirAccess::exists(p_output_dir); - ERR_FAIL_COND_V_MSG(!dir_exists, ERR_FILE_BAD_PATH, "The output directory does not exist."); - - StringBuilder output; - - output.append("/* THIS FILE IS GENERATED DO NOT EDIT */\n"); - output.append("#include \"" GLUE_HEADER_FILE "\"\n"); - output.append("\n#ifdef MONO_GLUE_ENABLED\n"); +Error BindingsGenerator::_generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output) { + bool ret_void = p_icall.return_type.cname == name_cache.type_void; - generated_icall_funcs.clear(); + const TypeInterface *return_type = _get_type_or_null(p_icall.return_type); + ERR_FAIL_NULL_V(return_type, ERR_BUG); // Return type not found - for (const KeyValue<StringName, TypeInterface> &type_elem : obj_types) { - const TypeInterface &itype = type_elem.value; + StringBuilder c_func_sig; + StringBuilder c_in_statements; + StringBuilder c_args_var_content; - bool is_derived_type = itype.base_name != StringName(); + c_func_sig << "IntPtr " CS_PARAM_METHODBIND; - if (!is_derived_type) { - // Some Object assertions - CRASH_COND(itype.cname != name_cache.type_Object); - CRASH_COND(!itype.is_instantiable); - CRASH_COND(itype.api_type != ClassDB::API_CORE); - CRASH_COND(itype.is_singleton); - } - - List<InternalCall> &custom_icalls = itype.api_type == ClassDB::API_EDITOR ? editor_custom_icalls : core_custom_icalls; - - OS::get_singleton()->print("Generating %s...\n", itype.name.utf8().get_data()); - - String ctor_method(ICALL_PREFIX + itype.proxy_name + "_Ctor"); // Used only for derived types - - for (const MethodInterface &imethod : itype.methods) { - Error method_err = _generate_glue_method(itype, imethod, output); - ERR_FAIL_COND_V_MSG(method_err != OK, method_err, - "Failed to generate method '" + imethod.name + "' for class '" + itype.name + "'."); - } - - if (itype.is_singleton) { - String singleton_icall_name = ICALL_PREFIX + itype.name + SINGLETON_ICALL_SUFFIX; - InternalCall singleton_icall = InternalCall(itype.api_type, singleton_icall_name, "IntPtr"); - - if (!find_icall_by_name(singleton_icall.name, custom_icalls)) { - custom_icalls.push_back(singleton_icall); - } - - output.append("Object* "); - output.append(singleton_icall_name); - output.append("() " OPEN_BLOCK "\treturn Engine::get_singleton()->get_singleton_object(\""); - output.append(itype.proxy_name); - output.append("\");\n" CLOSE_BLOCK "\n"); - } - - if (is_derived_type && itype.is_instantiable) { - InternalCall ctor_icall = InternalCall(itype.api_type, ctor_method, "IntPtr", itype.proxy_name + " obj"); - - if (!find_icall_by_name(ctor_icall.name, custom_icalls)) { - custom_icalls.push_back(ctor_icall); - } - - output.append("Object* "); - output.append(ctor_method); - output.append("(MonoObject* obj) " OPEN_BLOCK - "\t" C_MACRO_OBJECT_CONSTRUCT "(instance, \""); - output.append(itype.name); - output.append("\");\n" - "\t" C_METHOD_TIE_MANAGED_TO_UNMANAGED "(obj, instance);\n" - "\treturn instance;\n" CLOSE_BLOCK "\n"); - } - } - - output.append("namespace GodotSharpBindings\n" OPEN_BLOCK "\n"); - - output.append("uint64_t get_core_api_hash() { return "); - output.append(String::num_uint64(GDMono::get_singleton()->get_api_core_hash()) + "U; }\n"); - - output.append("#ifdef TOOLS_ENABLED\n" - "uint64_t get_editor_api_hash() { return "); - output.append(String::num_uint64(GDMono::get_singleton()->get_api_editor_hash()) + "U; }\n"); - output.append("#endif // TOOLS_ENABLED\n"); - - output.append("uint32_t get_bindings_version() { return "); - output.append(String::num_uint64(BINDINGS_GENERATOR_VERSION) + "; }\n"); - - output.append("uint32_t get_cs_glue_version() { return "); - output.append(String::num_uint64(CS_GLUE_VERSION) + "; }\n"); - - output.append("\nvoid register_generated_icalls() " OPEN_BLOCK); - output.append("\tgodot_register_glue_header_icalls();\n"); - -#define ADD_INTERNAL_CALL_REGISTRATION(m_icall) \ - { \ - output.append("\tGDMonoUtils::add_internal_call("); \ - output.append("\"" BINDINGS_NAMESPACE "."); \ - output.append(m_icall.editor_only ? BINDINGS_CLASS_NATIVECALLS_EDITOR : BINDINGS_CLASS_NATIVECALLS); \ - output.append("::"); \ - output.append(m_icall.name); \ - output.append("\", "); \ - output.append(m_icall.name); \ - output.append(");\n"); \ - } - - bool tools_sequence = false; - for (const InternalCall &internal_call : core_custom_icalls) { - if (tools_sequence) { - if (!internal_call.editor_only) { - tools_sequence = false; - output.append("#endif\n"); - } - } else { - if (internal_call.editor_only) { - output.append("#ifdef TOOLS_ENABLED\n"); - tools_sequence = true; - } - } - - ADD_INTERNAL_CALL_REGISTRATION(internal_call); + if (!p_icall.is_static) { + c_func_sig += ", IntPtr " CS_PARAM_INSTANCE; } - if (tools_sequence) { - tools_sequence = false; - output.append("#endif\n"); - } - - output.append("#ifdef TOOLS_ENABLED\n"); - for (const InternalCall &internal_call : editor_custom_icalls) { - ADD_INTERNAL_CALL_REGISTRATION(internal_call); - } - output.append("#endif // TOOLS_ENABLED\n"); - - for (const InternalCall &internal_call : method_icalls) { - if (tools_sequence) { - if (!internal_call.editor_only) { - tools_sequence = false; - output.append("#endif\n"); - } - } else { - if (internal_call.editor_only) { - output.append("#ifdef TOOLS_ENABLED\n"); - tools_sequence = true; - } - } - - ADD_INTERNAL_CALL_REGISTRATION(internal_call); - } - - if (tools_sequence) { - tools_sequence = false; - output.append("#endif\n"); - } - -#undef ADD_INTERNAL_CALL_REGISTRATION - - output.append(CLOSE_BLOCK "\n} // namespace GodotSharpBindings\n"); - - output.append("\n#endif // MONO_GLUE_ENABLED\n"); - - Error save_err = _save_file(path::join(p_output_dir, "mono_glue.gen.cpp"), output); - if (save_err != OK) { - return save_err; - } - - OS::get_singleton()->print("Mono glue generated successfully\n"); - - return OK; -} - -uint32_t BindingsGenerator::get_version() { - return BINDINGS_GENERATOR_VERSION; -} - -Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) { - Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(file.is_null(), ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'."); - - file->store_string(p_content.as_string()); - - return OK; -} - -Error BindingsGenerator::_generate_glue_method(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::MethodInterface &p_imethod, StringBuilder &p_output) { - if (p_imethod.is_virtual) { - return OK; // Ignore - } - - bool ret_void = p_imethod.return_type.cname == name_cache.type_void; - - const TypeInterface *return_type = _get_type_or_placeholder(p_imethod.return_type); - - String argc_str = itos(p_imethod.arguments.size()); - - String c_func_sig = "MethodBind* " CS_PARAM_METHODBIND; - if (!p_imethod.is_static) { - c_func_sig += ", " + p_itype.c_type_in + " " CS_PARAM_INSTANCE; - } - String c_in_statements; - String c_args_var_content; - // Get arguments information int i = 0; - for (const ArgumentInterface &iarg : p_imethod.arguments) { - const TypeInterface *arg_type = _get_type_or_placeholder(iarg.type); + for (const TypeReference &arg_type_ref : p_icall.argument_types) { + const TypeInterface *arg_type = _get_type_or_null(arg_type_ref); + ERR_FAIL_NULL_V(arg_type, ERR_BUG); // Return type not found String c_param_name = "arg" + itos(i + 1); - if (p_imethod.is_vararg) { - if (i < p_imethod.arguments.size() - 1) { - c_in_statements += sformat(arg_type->c_in.size() ? arg_type->c_in : TypeInterface::DEFAULT_VARARG_C_IN, "Variant", c_param_name); - c_in_statements += "\t" C_LOCAL_PTRCALL_ARGS ".set("; - c_in_statements += itos(i); - c_in_statements += sformat(", &%s_in);\n", c_param_name); + if (p_icall.is_vararg) { + if (i < p_icall.get_arguments_count() - 1) { + String c_in_vararg = arg_type->c_in_vararg; + + if (arg_type->is_object_type) { + c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromGodotObjectPtr(%1);\n"; + } + + ERR_FAIL_COND_V_MSG(c_in_vararg.is_empty(), ERR_BUG, + "VarArg support not implemented for parameter type: " + arg_type->name); + + c_in_statements + << sformat(c_in_vararg, return_type->c_type, c_param_name, + String(), String(), String(), INDENT3) + << INDENT3 C_LOCAL_PTRCALL_ARGS "[" << itos(i) + << "] = new IntPtr(&" << c_param_name << "_in);\n"; } } else { if (i > 0) { - c_args_var_content += ", "; + c_args_var_content << ", "; } if (arg_type->c_in.size()) { - c_in_statements += sformat(arg_type->c_in, arg_type->c_type, c_param_name); + c_in_statements << sformat(arg_type->c_in, arg_type->c_type, c_param_name, + String(), String(), String(), INDENT2); } - c_args_var_content += sformat(arg_type->c_arg_in, c_param_name); + c_args_var_content << sformat(arg_type->c_arg_in, c_param_name); } - c_func_sig += ", "; - c_func_sig += arg_type->c_type_in; - c_func_sig += " "; - c_func_sig += c_param_name; + c_func_sig << ", " << arg_type->c_type_in << " " << c_param_name; i++; } - if (return_type->ret_as_byref_arg) { - c_func_sig += ", "; - c_func_sig += return_type->c_type_in; - c_func_sig += " "; - c_func_sig += "arg_ret"; + String icall_method = p_icall.name; - i++; - } - - HashMap<const MethodInterface *, const InternalCall *>::ConstIterator match = method_icalls_map.find(&p_imethod); - ERR_FAIL_NULL_V(match, ERR_BUG); + // Generate icall function - const InternalCall *im_icall = match->value; - String icall_method = im_icall->name; + r_output << MEMBER_BEGIN "internal static unsafe " << (ret_void ? "void" : return_type->c_type_out) << " " + << icall_method << "(" << c_func_sig.as_string() << ") " OPEN_BLOCK; - if (!generated_icall_funcs.find(im_icall)) { - generated_icall_funcs.push_back(im_icall); + if (!ret_void && (!p_icall.is_vararg || return_type->cname != name_cache.type_Variant)) { + String ptrcall_return_type; + String initialization; - if (im_icall->editor_only) { - p_output.append("#ifdef TOOLS_ENABLED\n"); + if (return_type->is_object_type) { + ptrcall_return_type = return_type->is_ref_counted ? "godot_ref" : return_type->c_type; + initialization = " = default"; + } else { + ptrcall_return_type = return_type->c_type; } - // Generate icall function + r_output << INDENT2; - p_output.append((ret_void || return_type->ret_as_byref_arg) ? "void " : return_type->c_type_out + " "); - p_output.append(icall_method); - p_output.append("("); - p_output.append(c_func_sig); - p_output.append(") " OPEN_BLOCK); + if (return_type->is_ref_counted || return_type->c_type_is_disposable_struct) { + r_output << "using "; - if (!ret_void) { - String ptrcall_return_type; - String initialization; - - if (p_imethod.is_vararg && return_type->cname != name_cache.type_Variant) { - // VarArg methods always return Variant, but there are some cases in which MethodInfo provides - // a specific return type. We trust this information is valid. We need a temporary local to keep - // the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr, - // it could be deleted too early. This is the case with GDScript.new() which returns OBJECT. - // Alternatively, we could just return Variant, but that would result in a worse API. - p_output.append("\tVariant " C_LOCAL_VARARG_RET ";\n"); + if (initialization.is_empty()) { + initialization = " = default"; } + } else if (return_type->c_ret_needs_default_initialization) { + initialization = " = default"; + } - if (return_type->is_object_type) { - ptrcall_return_type = return_type->is_ref_counted ? "Ref<RefCounted>" : return_type->c_type; - initialization = return_type->is_ref_counted ? "" : " = nullptr"; - } else { - ptrcall_return_type = return_type->c_type; - } + r_output << ptrcall_return_type << " " C_LOCAL_RET << initialization << ";\n"; + } - p_output.append("\t" + ptrcall_return_type); - p_output.append(" " C_LOCAL_RET); - p_output.append(initialization + ";\n"); + if (!p_icall.is_static) { + r_output << INDENT2 "if (" CS_PARAM_INSTANCE " == IntPtr.Zero)\n" + << INDENT3 "throw new ArgumentNullException(nameof(" CS_PARAM_INSTANCE "));\n"; + } - String fail_ret = return_type->c_type_out.ends_with("*") && !return_type->ret_as_byref_arg ? "nullptr" : return_type->c_type_out + "()"; + String argc_str = itos(p_icall.get_arguments_count()); - if (!p_imethod.is_static) { - if (return_type->ret_as_byref_arg) { - p_output.append("\tif (" CS_PARAM_INSTANCE " == nullptr) { *arg_ret = "); - p_output.append(fail_ret); - p_output.append("; ERR_FAIL_MSG(\"Parameter ' " CS_PARAM_INSTANCE " ' is null.\"); }\n"); - } else { - p_output.append("\tERR_FAIL_NULL_V(" CS_PARAM_INSTANCE ", "); - p_output.append(fail_ret); - p_output.append(");\n"); - } - } - } else { - if (!p_imethod.is_static) { - p_output.append("\tERR_FAIL_NULL(" CS_PARAM_INSTANCE ");\n"); - } - } - - if (p_imethod.arguments.size()) { - if (p_imethod.is_vararg) { - String vararg_arg = "arg" + argc_str; - String real_argc_str = itos(p_imethod.arguments.size() - 1); // Arguments count without vararg - - p_output.append("\tint vararg_length = mono_array_length("); - p_output.append(vararg_arg); - p_output.append(");\n\tint total_length = "); - p_output.append(real_argc_str); - p_output.append(" + vararg_length;\n" - "\tArgumentsVector<Variant> varargs(vararg_length);\n" - "\tArgumentsVector<const Variant *> " C_LOCAL_PTRCALL_ARGS "(total_length);\n"); - p_output.append(c_in_statements); - p_output.append("\tfor (int i = 0; i < vararg_length; i++) " OPEN_BLOCK - "\t\tMonoObject* elem = mono_array_get("); - p_output.append(vararg_arg); - p_output.append(", MonoObject*, i);\n" - "\t\tvarargs.set(i, GDMonoMarshal::mono_object_to_variant(elem));\n" - "\t\t" C_LOCAL_PTRCALL_ARGS ".set("); - p_output.append(real_argc_str); - p_output.append(" + i, &varargs.get(i));\n\t" CLOSE_BLOCK); - } else { - p_output.append(c_in_statements); - p_output.append("\tconst void* " C_LOCAL_PTRCALL_ARGS "["); - p_output.append(argc_str + "] = { "); - p_output.append(c_args_var_content + " };\n"); - } - } + auto generate_call_and_return_stmts = [&](const char *base_indent) { + if (p_icall.is_vararg) { + // MethodBind Call + r_output << base_indent; - if (p_imethod.is_vararg) { - p_output.append("\tCallable::CallError vcall_error;\n\t"); + // VarArg methods always return Variant, but there are some cases in which MethodInfo provides + // a specific return type. We trust this information is valid. We need a temporary local to keep + // the Variant alive until the method returns. Otherwise, if the returned Variant holds a RefPtr, + // it could be deleted too early. This is the case with GDScript.new() which returns OBJECT. + // Alternatively, we could just return Variant, but that would result in a worse API. if (!ret_void) { - // See the comment on the C_LOCAL_VARARG_RET declaration if (return_type->cname != name_cache.type_Variant) { - p_output.append(C_LOCAL_VARARG_RET " = "); + r_output << "using godot_variant " << C_LOCAL_VARARG_RET " = "; } else { - p_output.append(C_LOCAL_RET " = "); + r_output << "using godot_variant " << C_LOCAL_RET " = "; } } - p_output.append(CS_PARAM_METHODBIND "->call("); - p_output.append(p_imethod.is_static ? "nullptr" : CS_PARAM_INSTANCE); - p_output.append(", "); - p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ".ptr()" : "nullptr"); - p_output.append(", total_length, vcall_error);\n"); + r_output << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_call(" + << CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE) + << ", " << (p_icall.get_arguments_count() ? "(godot_variant**)" C_LOCAL_PTRCALL_ARGS : "null") + << ", total_length, out _);\n"; if (!ret_void) { - // See the comment on the C_LOCAL_VARARG_RET declaration if (return_type->cname != name_cache.type_Variant) { - p_output.append("\t" C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n"); + if (return_type->cname == name_cache.enum_Error) { + r_output << base_indent << C_LOCAL_RET " = VariantUtils.ConvertToInt64(" C_LOCAL_VARARG_RET ");\n"; + } else { + // TODO: Use something similar to c_in_vararg (see usage above, with error if not implemented) + CRASH_NOW_MSG("Custom VarArg return type not implemented: " + return_type->name); + r_output << base_indent << C_LOCAL_RET " = " C_LOCAL_VARARG_RET ";\n"; + } } } } else { - p_output.append("\t" CS_PARAM_METHODBIND "->ptrcall("); - p_output.append(p_imethod.is_static ? "nullptr" : CS_PARAM_INSTANCE); - p_output.append(", "); - p_output.append(p_imethod.arguments.size() ? C_LOCAL_PTRCALL_ARGS ", " : "nullptr, "); - p_output.append(!ret_void ? "&" C_LOCAL_RET ");\n" : "nullptr);\n"); + // MethodBind PtrCall + r_output << base_indent << C_CLASS_NATIVE_FUNCS ".godotsharp_method_bind_ptrcall(" + << CS_PARAM_METHODBIND ", " << (p_icall.is_static ? "IntPtr.Zero" : CS_PARAM_INSTANCE) + << ", " << (p_icall.get_arguments_count() ? C_LOCAL_PTRCALL_ARGS : "null") + << ", " << (!ret_void ? "&" C_LOCAL_RET ");\n" : "null);\n"); } + // Return statement + if (!ret_void) { if (return_type->c_out.is_empty()) { - p_output.append("\treturn " C_LOCAL_RET ";\n"); - } else if (return_type->ret_as_byref_arg) { - p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name, "arg_ret")); + r_output << base_indent << "return " C_LOCAL_RET ";\n"; } else { - p_output.append(sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, return_type->name)); + r_output << sformat(return_type->c_out, return_type->c_type_out, C_LOCAL_RET, + return_type->name, String(), String(), base_indent); } } + }; + + if (p_icall.get_arguments_count()) { + if (p_icall.is_vararg) { + String vararg_arg = "arg" + argc_str; + String real_argc_str = itos(p_icall.get_arguments_count() - 1); // Arguments count without vararg + + p_icall.get_arguments_count(); + + r_output << INDENT2 "int vararg_length = " << vararg_arg << ".Length;\n" + << INDENT2 "int total_length = " << real_argc_str << " + vararg_length;\n"; + + r_output << INDENT2 "Span<godot_variant.movable> varargs_span = vararg_length <= VarArgsSpanThreshold ?\n" + << INDENT3 "stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() :\n" + << INDENT3 "new godot_variant.movable[vararg_length];\n"; + + r_output << INDENT2 "Span<IntPtr> " C_LOCAL_PTRCALL_ARGS "_span = total_length <= VarArgsSpanThreshold ?\n" + << INDENT3 "stackalloc IntPtr[VarArgsSpanThreshold] :\n" + << INDENT3 "new IntPtr[total_length];\n"; + + r_output << INDENT2 "using var variantSpanDisposer = new VariantSpanDisposer(varargs_span);\n"; - p_output.append(CLOSE_BLOCK "\n"); + r_output << INDENT2 "fixed (godot_variant.movable* varargs = &MemoryMarshal.GetReference(varargs_span))\n" + << INDENT2 "fixed (IntPtr* " C_LOCAL_PTRCALL_ARGS " = " + "&MemoryMarshal.GetReference(" C_LOCAL_PTRCALL_ARGS "_span))\n" + << OPEN_BLOCK_L2; - if (im_icall->editor_only) { - p_output.append("#endif // TOOLS_ENABLED\n"); + r_output << c_in_statements.as_string(); + + r_output << INDENT3 "for (int i = 0; i < vararg_length; i++) " OPEN_BLOCK + << INDENT4 "varargs[i] = " << vararg_arg << "[i].NativeVar;\n" + << INDENT4 C_LOCAL_PTRCALL_ARGS "[" << real_argc_str << " + i] = new IntPtr(&varargs[i]);\n" + << CLOSE_BLOCK_L3; + + generate_call_and_return_stmts(INDENT3); + + r_output << CLOSE_BLOCK_L2; + } else { + r_output << c_in_statements.as_string(); + + r_output << INDENT2 "void** " C_LOCAL_PTRCALL_ARGS " = stackalloc void*[" + << argc_str << "] { " << c_args_var_content.as_string() << " };\n"; + + generate_call_and_return_stmts(INDENT2); } + } else { + generate_call_and_return_stmts(INDENT2); } + r_output << CLOSE_BLOCK_L1; + + return OK; +} + +Error BindingsGenerator::_save_file(const String &p_path, const StringBuilder &p_content) { + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE); + ERR_FAIL_COND_V_MSG(file.is_null(), ERR_FILE_CANT_WRITE, "Cannot open file: '" + p_path + "'."); + + file->store_string(p_content.as_string()); + return OK; } @@ -2514,27 +2526,6 @@ const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_null(con return nullptr; } -const BindingsGenerator::TypeInterface *BindingsGenerator::_get_type_or_placeholder(const TypeReference &p_typeref) { - const TypeInterface *found = _get_type_or_null(p_typeref); - - if (found) { - return found; - } - - ERR_PRINT(String() + "Type not found. Creating placeholder: '" + p_typeref.cname.operator String() + "'."); - - HashMap<StringName, TypeInterface>::ConstIterator match = placeholder_types.find(p_typeref.cname); - - if (match) { - return &match->value; - } - - TypeInterface placeholder; - TypeInterface::create_placeholder_type(placeholder, p_typeref.cname); - - return &placeholder_types.insert(placeholder.cname, placeholder)->value; -} - const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters) { if (p_generic_type_parameters.is_empty()) { return ""; @@ -2548,7 +2539,8 @@ const String BindingsGenerator::_get_generic_type_parameters(const TypeInterface int i = 0; String params = "<"; for (const TypeReference ¶m_type : p_generic_type_parameters) { - const TypeInterface *param_itype = _get_type_or_placeholder(param_type); + const TypeInterface *param_itype = _get_type_or_null(param_type); + ERR_FAIL_NULL_V(param_itype, ""); ERR_FAIL_COND_V_MSG(param_itype->is_singleton, "", "Generic type parameter is a singleton: '" + param_itype->name + "'."); @@ -2666,8 +2658,6 @@ bool BindingsGenerator::_arg_default_value_is_assignable_to_type(const Variant & case Variant::RECT2: case Variant::VECTOR3: case Variant::RID: - case Variant::ARRAY: - case Variant::DICTIONARY: case Variant::PACKED_BYTE_ARRAY: case Variant::PACKED_INT32_ARRAY: case Variant::PACKED_INT64_ARRAY: @@ -2680,6 +2670,10 @@ bool BindingsGenerator::_arg_default_value_is_assignable_to_type(const Variant & case Variant::CALLABLE: case Variant::SIGNAL: return p_arg_type.name == Variant::get_type_name(p_val.get_type()); + case Variant::ARRAY: + return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Array_generic; + case Variant::DICTIONARY: + return p_arg_type.name == Variant::get_type_name(p_val.get_type()) || p_arg_type.cname == name_cache.type_Dictionary_generic; case Variant::OBJECT: return p_arg_type.is_object_type; case Variant::VECTOR2I: @@ -2744,18 +2738,27 @@ bool BindingsGenerator::_populate_object_type_interfaces() { itype.is_ref_counted = ClassDB::is_parent_class(type_cname, name_cache.type_RefCounted); itype.memory_own = itype.is_ref_counted; - itype.c_out = "\treturn "; + itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToGodotObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromGodotObject(%0)"; + + itype.c_out = "%5return "; itype.c_out += C_METHOD_UNMANAGED_GET_MANAGED; - itype.c_out += itype.is_ref_counted ? "(%1.ptr());\n" : "(%1);\n"; + itype.c_out += itype.is_ref_counted ? "(%1.Reference);\n" : "(%1);\n"; - itype.cs_in = itype.is_singleton ? BINDINGS_PTR_FIELD : "Object." CS_SMETHOD_GETINSTANCE "(%0)"; + itype.cs_type = itype.proxy_name; - itype.c_type = "Object*"; + if (itype.is_singleton) { + itype.cs_in_expr = "Object." CS_STATIC_METHOD_GETINSTANCE "(" CS_PROPERTY_SINGLETON ")"; + } else { + itype.cs_in_expr = "Object." CS_STATIC_METHOD_GETINSTANCE "(%0)"; + } + + itype.cs_out = "%5return (%2)%0(%1);"; + + itype.c_arg_in = "(void*)%s"; + itype.c_type = "IntPtr"; itype.c_type_in = itype.c_type; - itype.c_type_out = "MonoObject*"; - itype.cs_type = itype.proxy_name; - itype.im_type_in = "IntPtr"; - itype.im_type_out = itype.proxy_name; + itype.c_type_out = "Object"; // Populate properties @@ -2846,6 +2849,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { if (method_info.flags & METHOD_FLAG_VIRTUAL) { imethod.is_virtual = true; + itype.has_virtual_methods = true; } PropertyInfo return_info = method_info.return_val; @@ -2887,7 +2891,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { String() + "Return type is reference but hint is not '" _STR(PROPERTY_HINT_RESOURCE_TYPE) "'." + " Are you returning a reference type by pointer? Method: '" + itype.name + "." + imethod.name + "'."); } else if (return_info.type == Variant::ARRAY && return_info.hint == PROPERTY_HINT_ARRAY_TYPE) { - imethod.return_type.cname = Variant::get_type_name(return_info.type); + imethod.return_type.cname = Variant::get_type_name(return_info.type) + "_@generic"; imethod.return_type.generic_type_parameters.push_back(TypeReference(return_info.hint_string)); } else if (return_info.hint == PROPERTY_HINT_RESOURCE_TYPE) { imethod.return_type.cname = return_info.hint_string; @@ -2919,7 +2923,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.class_name != StringName()) { iarg.type.cname = arginfo.class_name; } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { - iarg.type.cname = Variant::get_type_name(arginfo.type); + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; @@ -2985,7 +2989,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { ERR_FAIL_COND_V_MSG(itype.find_property_by_name(imethod.cname), false, "Method name conflicts with property: '" + itype.name + "." + imethod.name + "'."); - // Classes starting with an underscore are ignored unless they're used as a property setter or getter + // Methods starting with an underscore are ignored unless they're used as a property setter or getter if (!imethod.is_virtual && imethod.name[0] == '_') { for (const PropertyInterface &iprop : itype.properties) { if (iprop.setter == imethod.name || iprop.getter == imethod.name) { @@ -3027,7 +3031,7 @@ bool BindingsGenerator::_populate_object_type_interfaces() { } else if (arginfo.class_name != StringName()) { iarg.type.cname = arginfo.class_name; } else if (arginfo.type == Variant::ARRAY && arginfo.hint == PROPERTY_HINT_ARRAY_TYPE) { - iarg.type.cname = Variant::get_type_name(arginfo.type); + iarg.type.cname = Variant::get_type_name(arginfo.type) + "_@generic"; iarg.type.generic_type_parameters.push_back(TypeReference(arginfo.hint_string)); } else if (arginfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { iarg.type.cname = arginfo.hint_string; @@ -3132,6 +3136,8 @@ bool BindingsGenerator::_populate_object_type_interfaces() { enum_itype.cname = StringName(enum_itype.name); enum_itype.proxy_name = itype.proxy_name + "." + enum_proxy_name; TypeInterface::postsetup_enum_type(enum_itype); + enum_itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToInt32(%0)"; + enum_itype.cs_managed_to_variant = "VariantUtils.CreateFromInt((int)%0)"; enum_types.insert(enum_itype.cname, enum_itype); } @@ -3169,7 +3175,7 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar switch (p_val.get_type()) { case Variant::NIL: // Either Object type or Variant - r_iarg.default_argument = "null"; + r_iarg.default_argument = "default"; break; // Atomic types case Variant::BOOL: @@ -3189,8 +3195,13 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar case Variant::STRING_NAME: case Variant::NODE_PATH: if (r_iarg.type.cname == name_cache.type_StringName || r_iarg.type.cname == name_cache.type_NodePath) { - r_iarg.default_argument = "(%s)\"" + r_iarg.default_argument + "\""; - r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + if (r_iarg.default_argument.length() > 0) { + r_iarg.default_argument = "(%s)\"" + r_iarg.default_argument + "\""; + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + } else { + // No need for a special `in` statement to change `null` to `""`. Marshaling takes care of this already. + r_iarg.default_argument = "null"; + } } else { CRASH_COND(r_iarg.type.cname != name_cache.type_String); r_iarg.default_argument = "\"" + r_iarg.default_argument + "\""; @@ -3236,8 +3247,11 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.default_argument = "null"; break; case Variant::DICTIONARY: - r_iarg.default_argument = "new %s()"; - r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + ERR_FAIL_COND_V_MSG(!p_val.operator Dictionary().is_empty(), false, + "Default value of type 'Dictionary' must be an empty dictionary."); + // The [cs_in] expression already interprets null values as empty dictionaries. + r_iarg.default_argument = "null"; + r_iarg.def_param_mode = ArgumentInterface::CONSTANT; break; case Variant::RID: ERR_FAIL_COND_V_MSG(r_iarg.type.cname != name_cache.type_RID, false, @@ -3246,11 +3260,14 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar ERR_FAIL_COND_V_MSG(!p_val.is_zero(), false, "Parameter of type '" + String(r_iarg.type.cname) + "' can only have null/zero as the default value."); - r_iarg.default_argument = "null"; + r_iarg.default_argument = "default"; break; case Variant::ARRAY: - r_iarg.default_argument = "new %s { }"; - r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + ERR_FAIL_COND_V_MSG(!p_val.operator Array().is_empty(), false, + "Default value of type 'Array' must be an empty array."); + // The [cs_in] expression already interprets null values as empty arrays. + r_iarg.default_argument = "null"; + r_iarg.def_param_mode = ArgumentInterface::CONSTANT; break; case Variant::PACKED_BYTE_ARRAY: case Variant::PACKED_INT32_ARRAY: @@ -3325,12 +3342,12 @@ bool BindingsGenerator::_arg_default_value_from_variant(const Variant &p_val, Ar r_iarg.default_argument = "default"; break; default: - CRASH_NOW_MSG("Unexpected Variant type: " + itos(p_val.get_type())); + ERR_FAIL_V_MSG(false, "Unexpected Variant type: " + itos(p_val.get_type())); break; } - if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "null") { - r_iarg.def_param_mode = ArgumentInterface::NULLABLE_REF; + if (r_iarg.def_param_mode == ArgumentInterface::CONSTANT && r_iarg.type.cname == name_cache.type_Variant && r_iarg.default_argument != "default") { + r_iarg.def_param_mode = ArgumentInterface::NULLABLE_VAL; } return true; @@ -3344,16 +3361,12 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { #define INSERT_STRUCT_TYPE(m_type) \ { \ itype = TypeInterface::create_value_type(String(#m_type)); \ - itype.c_in = "\t%0 %1_in = MARSHALLED_IN(" #m_type ", %1);\n"; \ - itype.c_out = "\t*%3 = MARSHALLED_OUT(" #m_type ", %1);\n"; \ - itype.c_arg_in = "&%s_in"; \ - itype.c_type_in = "GDMonoMarshal::M_" #m_type "*"; \ - itype.c_type_out = "GDMonoMarshal::M_" #m_type; \ - itype.cs_in = "ref %s"; \ - /* in cs_out, im_type_out (%3) includes the 'out ' part */ \ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; \ - itype.im_type_out = "out " + itype.cs_type; \ - itype.ret_as_byref_arg = true; \ + itype.c_type_in = #m_type "*"; \ + itype.c_type_out = itype.cs_type; \ + itype.cs_in_expr = "&%0"; \ + itype.cs_in_expr_is_unsafe = true; \ + itype.cs_variant_to_managed = "VariantUtils.ConvertTo%2(%0)"; \ + itype.cs_managed_to_variant = "VariantUtils.CreateFrom%2(%0)"; \ builtin_types.insert(itype.cname, itype); \ } @@ -3370,54 +3383,60 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { INSERT_STRUCT_TYPE(AABB) INSERT_STRUCT_TYPE(Color) INSERT_STRUCT_TYPE(Plane) + INSERT_STRUCT_TYPE(Vector4) + INSERT_STRUCT_TYPE(Vector4i) + INSERT_STRUCT_TYPE(Projection) #undef INSERT_STRUCT_TYPE // bool itype = TypeInterface::create_value_type(String("bool")); - { - // MonoBoolean <---> bool - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; - itype.c_type = "bool"; - itype.c_type_in = "MonoBoolean"; - itype.c_type_out = itype.c_type_in; - itype.c_arg_in = "&%s_in"; - } - itype.im_type_in = itype.name; - itype.im_type_out = itype.name; + itype.cs_in_expr = "%0.ToGodotBool()"; + itype.cs_out = "%5return %0(%1).ToBool();"; + itype.c_type = "godot_bool"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.c_type; + itype.c_arg_in = "&%s"; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromBool(%1);\n"; + itype.cs_variant_to_managed = "VariantUtils.ConvertToBool(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromBool(%0)"; builtin_types.insert(itype.cname, itype); // Integer types { // C interface for 'uint32_t' is the same as that of enums. Remember to apply // any of the changes done here to 'TypeInterface::postsetup_enum_type' as well. -#define INSERT_INT_TYPE(m_name, m_c_type_in_out, m_c_type) \ - { \ - itype = TypeInterface::create_value_type(String(m_name)); \ - { \ - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; \ - itype.c_out = "\treturn (%0)%1;\n"; \ - itype.c_type = #m_c_type; \ - itype.c_arg_in = "&%s_in"; \ - } \ - itype.c_type_in = #m_c_type_in_out; \ - itype.c_type_out = itype.c_type_in; \ - itype.im_type_in = itype.name; \ - itype.im_type_out = itype.name; \ - builtin_types.insert(itype.cname, itype); \ +#define INSERT_INT_TYPE(m_name, m_int_struct_name) \ + { \ + itype = TypeInterface::create_value_type(String(m_name)); \ + if (itype.name != "long" && itype.name != "ulong") { \ + itype.c_in = "%5%0 %1_in = %1;\n"; \ + itype.c_out = "%5return (%0)%1;\n"; \ + itype.c_type = "long"; \ + itype.c_arg_in = "&%s_in"; \ + } else { \ + itype.c_arg_in = "&%s"; \ + } \ + itype.c_type_in = itype.name; \ + itype.c_type_out = itype.name; \ + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromInt(%1);\n"; \ + itype.cs_variant_to_managed = "VariantUtils.ConvertTo" m_int_struct_name "(%0)"; \ + itype.cs_managed_to_variant = "VariantUtils.CreateFromInt(%0)"; \ + builtin_types.insert(itype.cname, itype); \ } // The expected type for all integers in ptrcall is 'int64_t', so that's what we use for 'c_type' - INSERT_INT_TYPE("sbyte", int8_t, int64_t); - INSERT_INT_TYPE("short", int16_t, int64_t); - INSERT_INT_TYPE("int", int32_t, int64_t); - INSERT_INT_TYPE("long", int64_t, int64_t); - INSERT_INT_TYPE("byte", uint8_t, int64_t); - INSERT_INT_TYPE("ushort", uint16_t, int64_t); - INSERT_INT_TYPE("uint", uint32_t, int64_t); - INSERT_INT_TYPE("ulong", uint64_t, int64_t); + INSERT_INT_TYPE("sbyte", "Int8"); + INSERT_INT_TYPE("short", "Int16"); + INSERT_INT_TYPE("int", "Int32"); + INSERT_INT_TYPE("long", "Int64"); + INSERT_INT_TYPE("byte", "UInt8"); + INSERT_INT_TYPE("ushort", "UInt16"); + INSERT_INT_TYPE("uint", "UInt32"); + INSERT_INT_TYPE("ulong", "UInt64"); + +#undef INSERT_INT_TYPE } // Floating point types @@ -3427,18 +3446,19 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "float"; itype.cname = itype.name; itype.proxy_name = "float"; + itype.cs_type = itype.proxy_name; { // The expected type for 'float' in ptrcall is 'double' - itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - itype.c_out = "\treturn (%0)%1;\n"; + itype.c_in = "%5%0 %1_in = %1;\n"; + itype.c_out = "%5return (%0)%1;\n"; itype.c_type = "double"; - itype.c_type_in = "float"; - itype.c_type_out = "float"; itype.c_arg_in = "&%s_in"; } - itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.c_type_in = itype.proxy_name; + itype.c_type_out = itype.proxy_name; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n"; + itype.cs_variant_to_managed = "VariantUtils.ConvertToFloat32(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromFloat(%0)"; builtin_types.insert(itype.cname, itype); // double @@ -3446,15 +3466,14 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "double"; itype.cname = itype.name; itype.proxy_name = "double"; - { - itype.c_type = "double"; - itype.c_type_in = "double"; - itype.c_type_out = "double"; - itype.c_arg_in = "&%s"; - } itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.c_type = "double"; + itype.c_arg_in = "&%s"; + itype.c_type_in = itype.proxy_name; + itype.c_type_out = itype.proxy_name; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromFloat(%1);\n"; + itype.cs_variant_to_managed = "VariantUtils.ConvertToFloat64(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromFloat(%0)"; builtin_types.insert(itype.cname, itype); } @@ -3463,15 +3482,17 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "String"; itype.cname = itype.name; itype.proxy_name = "string"; - itype.c_in = "\t%0 %1_in = " C_METHOD_MONOSTR_TO_GODOT "(%1);\n"; - itype.c_out = "\treturn " C_METHOD_MONOSTR_FROM_GODOT "(%1);\n"; - itype.c_arg_in = "&%s_in"; - itype.c_type = itype.name; - itype.c_type_in = "MonoString*"; - itype.c_type_out = "MonoString*"; itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOSTR_TO_GODOT "(%1);\n"; + itype.c_out = "%5return " C_METHOD_MONOSTR_FROM_GODOT "(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = "godot_string"; + itype.c_type_in = itype.cs_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = true; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromString(%1);\n"; + itype.cs_variant_to_managed = "VariantUtils.ConvertToStringObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromString(%0)"; builtin_types.insert(itype.cname, itype); // StringName @@ -3479,17 +3500,19 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "StringName"; itype.cname = itype.name; itype.proxy_name = "StringName"; - itype.c_in = "\t%0 %1_in = %1 ? *%1 : StringName();\n"; - itype.c_out = "\treturn memnew(StringName(%1));\n"; - itype.c_arg_in = "&%s_in"; - itype.c_type = itype.name; - itype.c_type_in = itype.c_type + "*"; - itype.c_type_out = itype.c_type + "*"; itype.cs_type = itype.proxy_name; - itype.cs_in = "StringName." CS_SMETHOD_GETINSTANCE "(%0)"; - itype.cs_out = "return new %2(%0(%1));"; - itype.im_type_in = "IntPtr"; - itype.im_type_out = "IntPtr"; + itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)"; + // Cannot pass null StringName to ptrcall + itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s"; + itype.c_type = "godot_string_name"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.cs_type; + itype.c_in_vararg = "%5using godot_variant %1_in = VariantUtils.CreateFromStringName(%1);\n"; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToStringNameObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromStringName(%0)"; builtin_types.insert(itype.cname, itype); // NodePath @@ -3497,15 +3520,18 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "NodePath"; itype.cname = itype.name; itype.proxy_name = "NodePath"; - itype.c_out = "\treturn memnew(NodePath(%1));\n"; - itype.c_type = itype.name; - itype.c_type_in = itype.c_type + "*"; - itype.c_type_out = itype.c_type + "*"; itype.cs_type = itype.proxy_name; - itype.cs_in = "NodePath." CS_SMETHOD_GETINSTANCE "(%0)"; - itype.cs_out = "return new %2(%0(%1));"; - itype.im_type_in = "IntPtr"; - itype.im_type_out = "IntPtr"; + itype.cs_in_expr = "(%1)(%0?.NativeValue ?? default)"; + // Cannot pass null NodePath to ptrcall + itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s"; + itype.c_type = "godot_node_path"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToNodePathObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromNodePath(%0)"; builtin_types.insert(itype.cname, itype); // RID @@ -3513,45 +3539,45 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "RID"; itype.cname = itype.name; itype.proxy_name = "RID"; - itype.c_out = "\treturn memnew(RID(%1));\n"; - itype.c_type = itype.name; - itype.c_type_in = itype.c_type + "*"; - itype.c_type_out = itype.c_type + "*"; itype.cs_type = itype.proxy_name; - itype.cs_in = "RID." CS_SMETHOD_GETINSTANCE "(%0)"; - itype.cs_out = "return new %2(%0(%1));"; - itype.im_type_in = "IntPtr"; - itype.im_type_out = "IntPtr"; + itype.c_arg_in = "&%s"; + itype.c_type = itype.cs_type; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.c_type; + itype.cs_variant_to_managed = "VariantUtils.ConvertToRID(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromRID(%0)"; builtin_types.insert(itype.cname, itype); // Variant itype = TypeInterface(); itype.name = "Variant"; itype.cname = itype.name; - itype.proxy_name = "object"; - itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_VARIANT "(%1);\n"; - itype.c_out = "\treturn " C_METHOD_MANAGED_FROM_VARIANT "(%1);\n"; - itype.c_arg_in = "&%s_in"; - itype.c_type = itype.name; - itype.c_type_in = "MonoObject*"; - itype.c_type_out = "MonoObject*"; + itype.proxy_name = "Variant"; itype.cs_type = itype.proxy_name; - itype.im_type_in = "object"; - itype.im_type_out = itype.proxy_name; + itype.c_in = "%5%0 %1_in = (%0)%1.NativeVar;\n"; + itype.c_out = "%5return Variant.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = "godot_variant"; + itype.c_type_in = itype.cs_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "Variant.CreateCopyingBorrowed(%0)"; + itype.cs_managed_to_variant = "%0.CopyNativeVariant()"; builtin_types.insert(itype.cname, itype); // Callable itype = TypeInterface::create_value_type(String("Callable")); - itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_CALLABLE "(*%1);\n"; - itype.c_out = "\t*%3 = " C_METHOD_MANAGED_FROM_CALLABLE "(%1);\n"; + itype.cs_in_expr = "%0"; + itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_CALLABLE "(in %1);\n"; + itype.c_out = "%5return " C_METHOD_MANAGED_FROM_CALLABLE "(in %1);\n"; itype.c_arg_in = "&%s_in"; - itype.c_type_in = "GDMonoMarshal::M_Callable*"; - itype.c_type_out = "GDMonoMarshal::M_Callable"; - itype.cs_in = "ref %s"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; - itype.im_type_out = "out " + itype.cs_type; - itype.ret_as_byref_arg = true; + itype.c_type = "godot_callable"; + itype.c_type_in = "in " + itype.cs_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToCallableManaged(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromCallable(%0)"; builtin_types.insert(itype.cname, itype); // Signal @@ -3559,66 +3585,65 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "Signal"; itype.cname = itype.name; itype.proxy_name = "SignalInfo"; - itype.c_in = "\t%0 %1_in = " C_METHOD_MANAGED_TO_SIGNAL "(*%1);\n"; - itype.c_out = "\t*%3 = " C_METHOD_MANAGED_FROM_SIGNAL "(%1);\n"; - itype.c_arg_in = "&%s_in"; - itype.c_type = itype.name; - itype.c_type_in = "GDMonoMarshal::M_SignalInfo*"; - itype.c_type_out = "GDMonoMarshal::M_SignalInfo"; - itype.cs_in = "ref %s"; - /* in cs_out, im_type_out (%3) includes the 'out ' part */ - itype.cs_out = "%0(%1, %3 argRet); return argRet;"; itype.cs_type = itype.proxy_name; - itype.im_type_in = "ref " + itype.cs_type; - itype.im_type_out = "out " + itype.cs_type; - itype.ret_as_byref_arg = true; + itype.cs_in_expr = "%0"; + itype.c_in = "%5using %0 %1_in = " C_METHOD_MANAGED_TO_SIGNAL "(in %1);\n"; + itype.c_out = "%5return " C_METHOD_MANAGED_FROM_SIGNAL "(&%1);\n"; + itype.c_arg_in = "&%s_in"; + itype.c_type = "godot_signal"; + itype.c_type_in = "in " + itype.cs_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToSignalInfo(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromSignalInfo(%0)"; builtin_types.insert(itype.cname, itype); // VarArg (fictitious type to represent variable arguments) itype = TypeInterface(); itype.name = "VarArg"; itype.cname = itype.name; - itype.proxy_name = "object[]"; - itype.c_in = "\t%0 %1_in = " C_METHOD_MONOARRAY_TO(Array) "(%1);\n"; + itype.proxy_name = "Variant[]"; + itype.cs_type = "params Variant[]"; + itype.cs_in_expr = "%0 ?? Array.Empty<Variant>()"; + // c_type, c_in and c_arg_in are hard-coded in the generator. + // c_out and c_type_out are not applicable to VarArg. itype.c_arg_in = "&%s_in"; - itype.c_type = "Array"; - itype.c_type_in = "MonoArray*"; - itype.cs_type = "params object[]"; - itype.im_type_in = "object[]"; + itype.c_type_in = "Variant[]"; builtin_types.insert(itype.cname, itype); -#define INSERT_ARRAY_FULL(m_name, m_type, m_proxy_t) \ - { \ - itype = TypeInterface(); \ - itype.name = #m_name; \ - itype.cname = itype.name; \ - itype.proxy_name = #m_proxy_t "[]"; \ - itype.c_in = "\t%0 %1_in = " C_METHOD_MONOARRAY_TO(m_type) "(%1);\n"; \ - itype.c_out = "\treturn " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \ - itype.c_arg_in = "&%s_in"; \ - itype.c_type = #m_type; \ - itype.c_type_in = "MonoArray*"; \ - itype.c_type_out = "MonoArray*"; \ - itype.cs_type = itype.proxy_name; \ - itype.im_type_in = itype.proxy_name; \ - itype.im_type_out = itype.proxy_name; \ - builtin_types.insert(itype.name, itype); \ +#define INSERT_ARRAY_FULL(m_name, m_type, m_managed_type, m_proxy_t) \ + { \ + itype = TypeInterface(); \ + itype.name = #m_name; \ + itype.cname = itype.name; \ + itype.proxy_name = #m_proxy_t "[]"; \ + itype.cs_type = itype.proxy_name; \ + itype.c_in = "%5using %0 %1_in = " C_METHOD_MONOARRAY_TO(m_type) "(%1);\n"; \ + itype.c_out = "%5return " C_METHOD_MONOARRAY_FROM(m_type) "(%1);\n"; \ + itype.c_arg_in = "&%s_in"; \ + itype.c_type = #m_managed_type; \ + itype.c_type_in = itype.proxy_name; \ + itype.c_type_out = itype.proxy_name; \ + itype.c_type_is_disposable_struct = true; \ + itype.cs_variant_to_managed = "VariantUtils.ConvertAs%2ToSystemArray(%0)"; \ + itype.cs_managed_to_variant = "VariantUtils.CreateFrom%2(%0)"; \ + builtin_types.insert(itype.name, itype); \ } -#define INSERT_ARRAY(m_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_proxy_t) +#define INSERT_ARRAY(m_type, m_managed_type, m_proxy_t) INSERT_ARRAY_FULL(m_type, m_type, m_managed_type, m_proxy_t) - INSERT_ARRAY(PackedInt32Array, int); - INSERT_ARRAY(PackedInt64Array, long); - INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, byte); + INSERT_ARRAY(PackedInt32Array, godot_packed_int32_array, int); + INSERT_ARRAY(PackedInt64Array, godot_packed_int64_array, long); + INSERT_ARRAY_FULL(PackedByteArray, PackedByteArray, godot_packed_byte_array, byte); - INSERT_ARRAY(PackedFloat32Array, float); - INSERT_ARRAY(PackedFloat64Array, double); + INSERT_ARRAY(PackedFloat32Array, godot_packed_float32_array, float); + INSERT_ARRAY(PackedFloat64Array, godot_packed_float64_array, double); - INSERT_ARRAY(PackedStringArray, string); + INSERT_ARRAY(PackedStringArray, godot_packed_string_array, string); - INSERT_ARRAY(PackedColorArray, Color); - INSERT_ARRAY(PackedVector2Array, Vector2); - INSERT_ARRAY(PackedVector3Array, Vector3); + INSERT_ARRAY(PackedColorArray, godot_packed_color_array, Color); + INSERT_ARRAY(PackedVector2Array, godot_packed_vector2_array, Vector2); + INSERT_ARRAY(PackedVector3Array, godot_packed_vector3_array, Vector3); #undef INSERT_ARRAY @@ -3628,15 +3653,24 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.cname = itype.name; itype.proxy_name = itype.name; itype.type_parameter_count = 1; - itype.c_out = "\treturn memnew(Array(%1));\n"; - itype.c_type = itype.name; - itype.c_type_in = itype.c_type + "*"; - itype.c_type_out = itype.c_type + "*"; itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name; - itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; - itype.cs_out = "return new %2(%0(%1));"; - itype.im_type_in = "IntPtr"; - itype.im_type_out = "IntPtr"; + itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue"; + itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s"; + itype.c_type = "godot_array"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToArrayObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromArray(%0)"; + builtin_types.insert(itype.cname, itype); + + // Array_@generic + // Re-use Array's itype + itype.name = "Array_@generic"; + itype.cname = itype.name; + itype.cs_out = "%5return new %2(%0(%1));"; builtin_types.insert(itype.cname, itype); // Dictionary @@ -3645,15 +3679,24 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.cname = itype.name; itype.proxy_name = itype.name; itype.type_parameter_count = 2; - itype.c_out = "\treturn memnew(Dictionary(%1));\n"; - itype.c_type = itype.name; - itype.c_type_in = itype.c_type + "*"; - itype.c_type_out = itype.c_type + "*"; itype.cs_type = BINDINGS_NAMESPACE_COLLECTIONS "." + itype.proxy_name; - itype.cs_in = "%0." CS_SMETHOD_GETINSTANCE "()"; - itype.cs_out = "return new %2(%0(%1));"; - itype.im_type_in = "IntPtr"; - itype.im_type_out = "IntPtr"; + itype.cs_in_expr = "(%1)(%0 ?? new()).NativeValue"; + itype.c_out = "%5return %0.CreateTakingOwnershipOfDisposableValue(%1);\n"; + itype.c_arg_in = "&%s"; + itype.c_type = "godot_dictionary"; + itype.c_type_in = itype.c_type; + itype.c_type_out = itype.cs_type; + itype.c_type_is_disposable_struct = false; // [c_out] takes ownership + itype.c_ret_needs_default_initialization = true; + itype.cs_variant_to_managed = "VariantUtils.ConvertToDictionaryObject(%0)"; + itype.cs_managed_to_variant = "VariantUtils.CreateFromDictionary(%0)"; + builtin_types.insert(itype.cname, itype); + + // Dictionary_@generic + // Re-use Dictionary's itype + itype.name = "Dictionary_@generic"; + itype.cname = itype.name; + itype.cs_out = "%5return new %2(%0(%1));"; builtin_types.insert(itype.cname, itype); // void (fictitious type to represent the return type of methods that do not return anything) @@ -3661,12 +3704,10 @@ void BindingsGenerator::_populate_builtin_type_interfaces() { itype.name = "void"; itype.cname = itype.name; itype.proxy_name = itype.name; - itype.c_type = itype.name; + itype.cs_type = itype.proxy_name; + itype.c_type = itype.proxy_name; itype.c_type_in = itype.c_type; itype.c_type_out = itype.c_type; - itype.cs_type = itype.proxy_name; - itype.im_type_in = itype.proxy_name; - itype.im_type_out = itype.proxy_name; builtin_types.insert(itype.cname, itype); } @@ -3721,6 +3762,8 @@ void BindingsGenerator::_populate_global_constants() { enum_itype.cname = ienum.cname; enum_itype.proxy_name = enum_itype.name; TypeInterface::postsetup_enum_type(enum_itype); + enum_itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToInt32(%0)"; + enum_itype.cs_managed_to_variant = "VariantUtils.CreateFromInt((int)%0)"; enum_types.insert(enum_itype.cname, enum_itype); int prefix_length = _determine_enum_prefix(ienum); @@ -3753,6 +3796,8 @@ void BindingsGenerator::_populate_global_constants() { enum_itype.cname = enum_cname; enum_itype.proxy_name = enum_itype.name; TypeInterface::postsetup_enum_type(enum_itype); + enum_itype.cs_variant_to_managed = "(%1)VariantUtils.ConvertToInt32(%0)"; + enum_itype.cs_managed_to_variant = "VariantUtils.CreateFromInt((int)%0)"; enum_types.insert(enum_itype.cname, enum_itype); } } @@ -3791,21 +3836,18 @@ void BindingsGenerator::_initialize() { // Generate internal calls (after populating type interfaces and global constants) - core_custom_icalls.clear(); - editor_custom_icalls.clear(); - for (const KeyValue<StringName, TypeInterface> &E : obj_types) { - _generate_method_icalls(E.value); + const TypeInterface &itype = E.value; + Error err = _populate_method_icalls_table(itype); + ERR_FAIL_COND_MSG(err != OK, "Failed to generate icalls table for type: " + itype.name); } initialized = true; } static String generate_all_glue_option = "--generate-mono-glue"; -static String generate_cs_glue_option = "--generate-mono-cs-glue"; -static String generate_cpp_glue_option = "--generate-mono-cpp-glue"; -static void handle_cmdline_options(String glue_dir_path, String cs_dir_path, String cpp_dir_path) { +static void handle_cmdline_options(String glue_dir_path) { BindingsGenerator bindings_generator; bindings_generator.set_log_print_enabled(true); @@ -3814,43 +3856,25 @@ static void handle_cmdline_options(String glue_dir_path, String cs_dir_path, Str return; } - if (glue_dir_path.length()) { - if (bindings_generator.generate_glue(glue_dir_path) != OK) { - ERR_PRINT(generate_all_glue_option + ": Failed to generate the C++ glue."); - } + CRASH_COND(glue_dir_path.is_empty()); - if (bindings_generator.generate_cs_api(glue_dir_path.plus_file(API_SOLUTION_NAME)) != OK) { - ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API."); - } - } - - if (cs_dir_path.length()) { - if (bindings_generator.generate_cs_api(cs_dir_path) != OK) { - ERR_PRINT(generate_cs_glue_option + ": Failed to generate the C# API."); - } + if (bindings_generator.generate_cs_api(glue_dir_path.plus_file(API_SOLUTION_NAME)) != OK) { + ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API."); } +} - if (cpp_dir_path.length()) { - if (bindings_generator.generate_glue(cpp_dir_path) != OK) { - ERR_PRINT(generate_cpp_glue_option + ": Failed to generate the C++ glue."); - } - } +static void cleanup_and_exit_godot() { + // Exit once done + Main::cleanup(true); + ::exit(0); } void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) { - const int NUM_OPTIONS = 2; - String glue_dir_path; - String cs_dir_path; - String cpp_dir_path; - - int options_left = NUM_OPTIONS; - - bool exit_godot = false; const List<String>::Element *elem = p_cmdline_args.front(); - while (elem && options_left) { + while (elem) { if (elem->get() == generate_all_glue_option) { const List<String>::Element *path_elem = elem->next(); @@ -3859,48 +3883,20 @@ void BindingsGenerator::handle_cmdline_args(const List<String> &p_cmdline_args) elem = elem->next(); } else { ERR_PRINT(generate_all_glue_option + ": No output directory specified (expected path to '{GODOT_ROOT}/modules/mono/glue')."); - exit_godot = true; - } - - --options_left; - } else if (elem->get() == generate_cs_glue_option) { - const List<String>::Element *path_elem = elem->next(); - - if (path_elem) { - cs_dir_path = path_elem->get(); - elem = elem->next(); - } else { - ERR_PRINT(generate_cs_glue_option + ": No output directory specified."); - exit_godot = true; + // Exit once done with invalid command line arguments + cleanup_and_exit_godot(); } - --options_left; - } else if (elem->get() == generate_cpp_glue_option) { - const List<String>::Element *path_elem = elem->next(); - - if (path_elem) { - cpp_dir_path = path_elem->get(); - elem = elem->next(); - } else { - ERR_PRINT(generate_cpp_glue_option + ": No output directory specified."); - exit_godot = true; - } - - --options_left; + break; } elem = elem->next(); } - if (glue_dir_path.length() || cs_dir_path.length() || cpp_dir_path.length()) { - handle_cmdline_options(glue_dir_path, cs_dir_path, cpp_dir_path); - exit_godot = true; - } - - if (exit_godot) { + if (glue_dir_path.length()) { + handle_cmdline_options(glue_dir_path); // Exit once done - Main::cleanup(true); - ::exit(0); + cleanup_and_exit_godot(); } } diff --git a/modules/mono/editor/bindings_generator.h b/modules/mono/editor/bindings_generator.h index ee170e4558..c1295385dc 100644 --- a/modules/mono/editor/bindings_generator.h +++ b/modules/mono/editor/bindings_generator.h @@ -229,6 +229,23 @@ class BindingsGenerator { bool is_ref_counted = false; /** + * Determines whether the native return value of this type must be disposed + * by the generated internal call (think of `godot_string`, whose destructor + * must be called). Some structs that are disposable may still disable this + * flag if the ownership is transferred. + */ + bool c_type_is_disposable_struct = false; + + /** + * Determines whether the native return value of this type must be zero initialized + * before its address is passed to ptrcall. This is required for types whose destructor + * is called before being assigned the return value by `PtrToArg::encode`, e.g.: + * Array, Dictionary, String, StringName, Variant. + * It's not necessary to set this to `true` if [c_type_is_disposable_struct] is already `true`. + */ + bool c_ret_needs_default_initialization = false; + + /** * Used only by Object-derived types. * Determines if this type is not abstract (incomplete). * e.g.: CanvasItem cannot be instantiated. @@ -242,31 +259,34 @@ class BindingsGenerator { */ bool memory_own = false; - /** - * This must be set to true for any struct bigger than 32-bits. Those cannot be passed/returned by value - * with internal calls, so we must use pointers instead. Returns must be replace with out parameters. - * In this case, [c_out] and [cs_out] must have a different format, explained below. - * The Mono IL interpreter icall trampolines don't support passing structs bigger than 32-bits by value (at least not on WASM). - */ - bool ret_as_byref_arg = false; - // !! The comments of the following fields make reference to other fields via square brackets, e.g.: [field_name] // !! When renaming those fields, make sure to rename their references in the comments // --- C INTERFACE --- - static const char *DEFAULT_VARARG_C_IN; + /** + * One or more statements that transform the parameter before being passed as argument of a ptrcall. + * If the statement adds a local that must be passed as the argument instead of the parameter, + * the expression with the name of that local must be specified with [c_arg_in]. + * Formatting elements: + * %0: [c_type] of the parameter + * %1: name of the parameter + * %2-4: reserved + * %5: indentation text + */ + String c_in; /** - * One or more statements that manipulate the parameter before being passed as argument of a ptrcall. + * One or more statements that transform the parameter before being passed as argument of a vararg call. * If the statement adds a local that must be passed as the argument instead of the parameter, * the name of that local must be specified with [c_arg_in]. - * For variadic methods, this field is required and, if empty, [DEFAULT_VARARG_C_IN] is used instead. * Formatting elements: * %0: [c_type] of the parameter * %1: name of the parameter + * %2-4: reserved + * %5: indentation text */ - String c_in; + String c_in_vararg; /** * Determines the expression that will be passed as argument to ptrcall. @@ -291,7 +311,8 @@ class BindingsGenerator { * %0: [c_type_out] of the return type * %1: name of the variable to be returned * %2: [name] of the return type - * %3: name of the parameter that must be assigned the return value + * %3-4: reserved + * %5: indentation text */ String c_out; @@ -327,7 +348,21 @@ class BindingsGenerator { * An expression that overrides the way the parameter is passed to the internal call. * If empty, the parameter is passed as is. * Formatting elements: - * %0 or %s: name of the parameter + * %0: name of the parameter + * %1: [c_type] of the parameter + */ + String cs_in_expr; + bool cs_in_expr_is_unsafe = false; + + /** + * One or more statements that transform the parameter before being passed to the internal call. + * If the statement adds a local that must be passed as the argument instead of the parameter, + * the expression with the name of that local must be specified with [cs_in_expr]. + * Formatting elements: + * %0: [c_type] of the parameter + * %1: name of the parameter + * %2-4: reserved + * %5: indentation text */ String cs_in; @@ -338,7 +373,9 @@ class BindingsGenerator { * %0: internal method name * %1: internal method call arguments without surrounding parenthesis * %2: [cs_type] of the return type - * %3: [im_type_out] of the return type + * %3: [c_type_out] of the return type + * %4: reserved + * %5: indentation text */ String cs_out; @@ -349,14 +386,20 @@ class BindingsGenerator { String cs_type; /** - * Type used for parameters of internal call methods. + * Formatting elements: + * %0: input expression of type `in godot_variant` + * %1: [cs_type] of this type + * %2: [name] of this type */ - String im_type_in; + String cs_variant_to_managed; /** - * Type used for the return type of internal call methods. + * Formatting elements: + * %0: input expression + * %1: [cs_type] of this type + * %2: [name] of this type */ - String im_type_out; + String cs_managed_to_variant; const DocData::ClassDoc *class_doc = nullptr; @@ -366,6 +409,8 @@ class BindingsGenerator { List<MethodInterface> methods; List<SignalInterface> signals_; + bool has_virtual_methods = false; + const MethodInterface *find_method_by_name(const StringName &p_cname) const { for (const MethodInterface &E : methods) { if (E.cname == p_cname) { @@ -432,8 +477,8 @@ class BindingsGenerator { itype.c_type = itype.name; itype.cs_type = itype.proxy_name; - itype.im_type_in = "ref " + itype.proxy_name; - itype.im_type_out = itype.proxy_name; + itype.c_type_in = itype.proxy_name + "*"; + itype.c_type_out = itype.proxy_name; itype.class_doc = &EditorHelp::get_doc_data()->class_list[itype.proxy_name]; } @@ -467,65 +512,27 @@ class BindingsGenerator { return itype; } - static void create_placeholder_type(TypeInterface &r_itype, const StringName &p_cname) { - r_itype.name = p_cname; - r_itype.cname = p_cname; - r_itype.proxy_name = r_itype.name; - - r_itype.c_type = r_itype.name; - r_itype.c_type_in = "MonoObject*"; - r_itype.c_type_out = "MonoObject*"; - r_itype.cs_type = r_itype.proxy_name; - r_itype.im_type_in = r_itype.proxy_name; - r_itype.im_type_out = r_itype.proxy_name; - } - - static void postsetup_enum_type(TypeInterface &r_enum_itype) { - // C interface for enums is the same as that of 'uint32_t'. Remember to apply - // any of the changes done here to the 'uint32_t' type interface as well. - - r_enum_itype.c_arg_in = "&%s_in"; - { - // The expected types for parameters and return value in ptrcall are 'int64_t' or 'uint64_t'. - r_enum_itype.c_in = "\t%0 %1_in = (%0)%1;\n"; - r_enum_itype.c_out = "\treturn (%0)%1;\n"; - r_enum_itype.c_type = "int64_t"; - } - r_enum_itype.c_type_in = "int32_t"; - r_enum_itype.c_type_out = r_enum_itype.c_type_in; - - r_enum_itype.cs_type = r_enum_itype.proxy_name; - r_enum_itype.cs_in = "(int)%s"; - r_enum_itype.cs_out = "return (%2)%0(%1);"; - r_enum_itype.im_type_in = "int"; - r_enum_itype.im_type_out = "int"; - r_enum_itype.class_doc = &EditorHelp::get_doc_data()->class_list[r_enum_itype.proxy_name]; - } + static void postsetup_enum_type(TypeInterface &r_enum_itype); TypeInterface() {} }; struct InternalCall { String name; - String im_type_out; // Return type for the C# method declaration. Also used as companion of [unique_siq] - String im_sig; // Signature for the C# method declaration String unique_sig; // Unique signature to avoid duplicates in containers bool editor_only = false; - InternalCall() {} + bool is_vararg = false; + bool is_static = false; + TypeReference return_type; + List<TypeReference> argument_types; - InternalCall(const String &p_name, const String &p_im_type_out, const String &p_im_sig = String(), const String &p_unique_sig = String()) { - name = p_name; - im_type_out = p_im_type_out; - im_sig = p_im_sig; - unique_sig = p_unique_sig; - editor_only = false; - } + _FORCE_INLINE_ int get_arguments_count() const { return argument_types.size(); } + + InternalCall() {} - InternalCall(ClassDB::APIType api_type, const String &p_name, const String &p_im_type_out, const String &p_im_sig = String(), const String &p_unique_sig = String()) { + InternalCall(ClassDB::APIType api_type, const String &p_name, const String &p_unique_sig = String()) { name = p_name; - im_type_out = p_im_type_out; - im_sig = p_im_sig; unique_sig = p_unique_sig; editor_only = api_type == ClassDB::API_EDITOR; } @@ -540,7 +547,6 @@ class BindingsGenerator { HashMap<StringName, TypeInterface> obj_types; - HashMap<StringName, TypeInterface> placeholder_types; HashMap<StringName, TypeInterface> builtin_types; HashMap<StringName, TypeInterface> enum_types; @@ -548,13 +554,9 @@ class BindingsGenerator { List<ConstantInterface> global_constants; List<InternalCall> method_icalls; + /// Stores the unique internal calls from [method_icalls] that are assigned to each method. HashMap<const MethodInterface *, const InternalCall *> method_icalls_map; - List<const InternalCall *> generated_icall_funcs; - - List<InternalCall> core_custom_icalls; - List<InternalCall> editor_custom_icalls; - HashMap<StringName, List<StringName>> blacklisted_methods; void _initialize_blacklisted_methods(); @@ -571,6 +573,8 @@ class BindingsGenerator { StringName type_String = StaticCString::create("String"); StringName type_StringName = StaticCString::create("StringName"); StringName type_NodePath = StaticCString::create("NodePath"); + StringName type_Array_generic = StaticCString::create("Array_@generic"); + StringName type_Dictionary_generic = StaticCString::create("Dictionary_@generic"); StringName type_at_GlobalScope = StaticCString::create("@GlobalScope"); StringName enum_Error = StaticCString::create("Error"); @@ -595,12 +599,14 @@ class BindingsGenerator { StringName type_Vector4i = StaticCString::create("Vector4i"); // Object not included as it must be checked for all derived classes - static constexpr int nullable_types_count = 17; + static constexpr int nullable_types_count = 18; StringName nullable_types[nullable_types_count] = { type_String, type_StringName, type_NodePath, + type_Array_generic, + type_Dictionary_generic, StaticCString::create(_STR(Array)), StaticCString::create(_STR(Dictionary)), StaticCString::create(_STR(Callable)), @@ -636,17 +642,6 @@ class BindingsGenerator { NameCache name_cache; - const List<InternalCall>::Element *find_icall_by_name(const String &p_name, const List<InternalCall> &p_list) { - const List<InternalCall>::Element *it = p_list.front(); - while (it) { - if (it->get().name == p_name) { - return it; - } - it = it->next(); - } - return nullptr; - } - const ConstantInterface *find_constant_by_name(const String &p_name, const List<ConstantInterface> &p_constants) const { for (const ConstantInterface &E : p_constants) { if (E.name == p_name) { @@ -657,18 +652,38 @@ class BindingsGenerator { return nullptr; } - inline String get_unique_sig(const TypeInterface &p_type) { - if (p_type.is_ref_counted) { - return "Ref"; - } else if (p_type.is_object_type) { + inline String get_arg_unique_sig(const TypeInterface &p_type) { + // For parameters, we treat reference and non-reference derived types the same. + if (p_type.is_object_type) { return "Obj"; } else if (p_type.is_enum) { return "int"; + } else if (p_type.cname == name_cache.type_Array_generic) { + return "Array"; + } else if (p_type.cname == name_cache.type_Dictionary_generic) { + return "Dictionary"; } return p_type.name; } + inline String get_ret_unique_sig(const TypeInterface *p_type) { + // Reference derived return types are treated differently. + if (p_type->is_ref_counted) { + return "Ref"; + } else if (p_type->is_object_type) { + return "Obj"; + } else if (p_type->is_enum) { + return "int"; + } else if (p_type->cname == name_cache.type_Array_generic) { + return "Array"; + } else if (p_type->cname == name_cache.type_Dictionary_generic) { + return "Dictionary"; + } + + return p_type->name; + } + String bbcode_to_xml(const String &p_bbcode, const TypeInterface *p_itype); void _append_xml_method(StringBuilder &p_xml_output, const TypeInterface *p_target_itype, const StringName &p_target_cname, const String &p_link_target, const Vector<String> &p_link_target_parts); @@ -682,10 +697,9 @@ class BindingsGenerator { int _determine_enum_prefix(const EnumInterface &p_ienum); void _apply_prefix_to_enum_constants(EnumInterface &p_ienum, int p_prefix_length); - void _generate_method_icalls(const TypeInterface &p_itype); + Error _populate_method_icalls_table(const TypeInterface &p_itype); const TypeInterface *_get_type_or_null(const TypeReference &p_typeref); - const TypeInterface *_get_type_or_placeholder(const TypeReference &p_typeref); const String _get_generic_type_parameters(const TypeInterface &p_itype, const List<TypeReference> &p_generic_type_parameters); @@ -706,11 +720,11 @@ class BindingsGenerator { Error _generate_cs_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, int &p_method_bind_count, StringBuilder &p_output); Error _generate_cs_signal(const BindingsGenerator::TypeInterface &p_itype, const BindingsGenerator::SignalInterface &p_isignal, StringBuilder &p_output); + Error _generate_cs_native_calls(const InternalCall &p_icall, StringBuilder &r_output); + void _generate_array_extensions(StringBuilder &p_output); void _generate_global_constants(StringBuilder &p_output); - Error _generate_glue_method(const TypeInterface &p_itype, const MethodInterface &p_imethod, StringBuilder &p_output); - Error _save_file(const String &p_path, const StringBuilder &p_content); void _log(const char *p_format, ...) _PRINTF_FORMAT_ATTRIBUTE_2_3; @@ -721,15 +735,12 @@ public: Error generate_cs_core_project(const String &p_proj_dir); Error generate_cs_editor_project(const String &p_proj_dir); Error generate_cs_api(const String &p_output_dir); - Error generate_glue(const String &p_output_dir); _FORCE_INLINE_ bool is_log_print_enabled() { return log_print_enabled; } _FORCE_INLINE_ void set_log_print_enabled(bool p_enabled) { log_print_enabled = p_enabled; } _FORCE_INLINE_ bool is_initialized() { return initialized; } - static uint32_t get_version(); - static void handle_cmdline_args(const List<String> &p_cmdline_args); BindingsGenerator() { diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index f830c7ffe1..1ef78c3ac2 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -46,179 +46,81 @@ #include "main/main.h" #include "../csharp_script.h" -#include "../glue/cs_glue_version.gen.h" #include "../godotsharp_dirs.h" -#include "../mono_gd/gd_mono_marshal.h" #include "../utils/macos_utils.h" #include "code_completion.h" -#include "godotsharp_export.h" -MonoString *godot_icall_GodotSharpDirs_ResDataDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_data_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResMetadataDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_metadata_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResAssembliesBaseDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_base_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResAssembliesDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_assemblies_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResConfigDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_config_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResTempDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_base_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_ResTempAssembliesDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_res_temp_assemblies_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_MonoUserDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_user_dir()); -} +#include "../interop_types.h" -MonoString *godot_icall_GodotSharpDirs_MonoLogsDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_logs_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_MonoSolutionsDir() { -#ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_mono_solutions_dir()); -#else - return nullptr; +#ifdef __cplusplus +extern "C" { #endif -} -MonoString *godot_icall_GodotSharpDirs_BuildLogsDirs() { -#ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_build_logs_dir()); -#else - return nullptr; -#endif +void godot_icall_GodotSharpDirs_ResMetadataDir(godot_string *r_dest) { + memnew_placement(r_dest, String(GodotSharpDirs::get_res_metadata_dir())); } -MonoString *godot_icall_GodotSharpDirs_ProjectSlnPath() { -#ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_sln_path()); -#else - return nullptr; -#endif +void godot_icall_GodotSharpDirs_MonoUserDir(godot_string *r_dest) { + memnew_placement(r_dest, String(GodotSharpDirs::get_mono_user_dir())); } -MonoString *godot_icall_GodotSharpDirs_ProjectCsProjPath() { +void godot_icall_GodotSharpDirs_BuildLogsDirs(godot_string *r_dest) { #ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_project_csproj_path()); + memnew_placement(r_dest, String(GodotSharpDirs::get_build_logs_dir())); #else return nullptr; #endif } -MonoString *godot_icall_GodotSharpDirs_DataEditorToolsDir() { +void godot_icall_GodotSharpDirs_DataEditorToolsDir(godot_string *r_dest) { #ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_tools_dir()); + memnew_placement(r_dest, String(GodotSharpDirs::get_data_editor_tools_dir())); #else return nullptr; #endif } -MonoString *godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir() { -#ifdef TOOLS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_editor_prebuilt_api_dir()); -#else - return nullptr; -#endif -} - -MonoString *godot_icall_GodotSharpDirs_DataMonoEtcDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_etc_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_DataMonoLibDir() { - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_lib_dir()); -} - -MonoString *godot_icall_GodotSharpDirs_DataMonoBinDir() { -#ifdef WINDOWS_ENABLED - return GDMonoMarshal::mono_string_from_godot(GodotSharpDirs::get_data_mono_bin_dir()); -#else - return nullptr; -#endif -} - -void godot_icall_EditorProgress_Create(MonoString *p_task, MonoString *p_label, int32_t p_amount, MonoBoolean p_can_cancel) { - String task = GDMonoMarshal::mono_string_to_godot(p_task); - String label = GDMonoMarshal::mono_string_to_godot(p_label); +void godot_icall_EditorProgress_Create(const godot_string *p_task, const godot_string *p_label, int32_t p_amount, bool p_can_cancel) { + String task = *reinterpret_cast<const String *>(p_task); + String label = *reinterpret_cast<const String *>(p_label); EditorNode::progress_add_task(task, label, p_amount, (bool)p_can_cancel); } -void godot_icall_EditorProgress_Dispose(MonoString *p_task) { - String task = GDMonoMarshal::mono_string_to_godot(p_task); +void godot_icall_EditorProgress_Dispose(const godot_string *p_task) { + String task = *reinterpret_cast<const String *>(p_task); EditorNode::progress_end_task(task); } -MonoBoolean godot_icall_EditorProgress_Step(MonoString *p_task, MonoString *p_state, int32_t p_step, MonoBoolean p_force_refresh) { - String task = GDMonoMarshal::mono_string_to_godot(p_task); - String state = GDMonoMarshal::mono_string_to_godot(p_state); +bool godot_icall_EditorProgress_Step(const godot_string *p_task, const godot_string *p_state, int32_t p_step, bool p_force_refresh) { + String task = *reinterpret_cast<const String *>(p_task); + String state = *reinterpret_cast<const String *>(p_state); return EditorNode::progress_task_step(task, state, p_step, (bool)p_force_refresh); } -uint32_t godot_icall_ExportPlugin_GetExportedAssemblyDependencies(MonoObject *p_initial_assemblies, - MonoString *p_build_config, MonoString *p_custom_bcl_dir, MonoObject *r_assembly_dependencies) { - Dictionary initial_dependencies = GDMonoMarshal::mono_object_to_variant(p_initial_assemblies); - String build_config = GDMonoMarshal::mono_string_to_godot(p_build_config); - String custom_bcl_dir = GDMonoMarshal::mono_string_to_godot(p_custom_bcl_dir); - Dictionary assembly_dependencies = GDMonoMarshal::mono_object_to_variant(r_assembly_dependencies); - - return GodotSharpExport::get_exported_assembly_dependencies(initial_dependencies, build_config, custom_bcl_dir, assembly_dependencies); -} - -MonoString *godot_icall_Internal_UpdateApiAssembliesFromPrebuilt(MonoString *p_config) { - String config = GDMonoMarshal::mono_string_to_godot(p_config); - String error_str = GDMono::get_singleton()->update_api_assemblies_from_prebuilt(config); - return GDMonoMarshal::mono_string_from_godot(error_str); -} - -MonoString *godot_icall_Internal_FullExportTemplatesDir() { +void godot_icall_Internal_FullExportTemplatesDir(godot_string *r_dest) { String full_templates_dir = EditorPaths::get_singleton()->get_export_templates_dir().plus_file(VERSION_FULL_CONFIG); - return GDMonoMarshal::mono_string_from_godot(full_templates_dir); -} - -MonoString *godot_icall_Internal_SimplifyGodotPath(MonoString *p_path) { - String path = GDMonoMarshal::mono_string_to_godot(p_path); - return GDMonoMarshal::mono_string_from_godot(path.simplify_path()); + memnew_placement(r_dest, String(full_templates_dir)); } -MonoBoolean godot_icall_Internal_IsMacOSAppBundleInstalled(MonoString *p_bundle_id) { +bool godot_icall_Internal_IsMacOSAppBundleInstalled(const godot_string *p_bundle_id) { #ifdef MACOS_ENABLED - String bundle_id = GDMonoMarshal::mono_string_to_godot(p_bundle_id); - return (MonoBoolean)macos_is_app_bundle_installed(bundle_id); + String bundle_id = *reinterpret_cast<const String *>(p_bundle_id); + return (bool)macos_is_app_bundle_installed(bundle_id); #else (void)p_bundle_id; // UNUSED - return (MonoBoolean) false; + return (bool)false; #endif } -MonoBoolean godot_icall_Internal_GodotIs32Bits() { +bool godot_icall_Internal_GodotIs32Bits() { return sizeof(void *) == 4; } -MonoBoolean godot_icall_Internal_GodotIsRealTDouble() { +bool godot_icall_Internal_GodotIsRealTDouble() { #ifdef REAL_T_IS_DOUBLE - return (MonoBoolean) true; + return (bool)true; #else - return (MonoBoolean) false; + return (bool)false; #endif } @@ -226,23 +128,15 @@ void godot_icall_Internal_GodotMainIteration() { Main::iteration(); } -uint64_t godot_icall_Internal_GetCoreApiHash() { - return ClassDB::get_api_hash(ClassDB::API_CORE); -} - -uint64_t godot_icall_Internal_GetEditorApiHash() { - return ClassDB::get_api_hash(ClassDB::API_EDITOR); -} - -MonoBoolean godot_icall_Internal_IsAssembliesReloadingNeeded() { +bool godot_icall_Internal_IsAssembliesReloadingNeeded() { #ifdef GD_MONO_HOT_RELOAD - return (MonoBoolean)CSharpLanguage::get_singleton()->is_assembly_reloading_needed(); + return (bool)CSharpLanguage::get_singleton()->is_assembly_reloading_needed(); #else - return (MonoBoolean) false; + return (bool)false; #endif } -void godot_icall_Internal_ReloadAssemblies(MonoBoolean p_soft_reload) { +void godot_icall_Internal_ReloadAssemblies(bool p_soft_reload) { #ifdef GD_MONO_HOT_RELOAD mono_bind::GodotSharp::get_singleton()->call_deferred(SNAME("_reload_assemblies"), (bool)p_soft_reload); #endif @@ -252,24 +146,15 @@ void godot_icall_Internal_EditorDebuggerNodeReloadScripts() { EditorDebuggerNode::get_singleton()->reload_scripts(); } -MonoBoolean godot_icall_Internal_ScriptEditorEdit(MonoObject *p_resource, int32_t p_line, int32_t p_col, MonoBoolean p_grab_focus) { - Ref<Resource> resource = GDMonoMarshal::mono_object_to_variant(p_resource); - return (MonoBoolean)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus); +bool godot_icall_Internal_ScriptEditorEdit(Resource *p_resource, int32_t p_line, int32_t p_col, bool p_grab_focus) { + Ref<Resource> resource = p_resource; + return (bool)ScriptEditor::get_singleton()->edit(resource, p_line, p_col, (bool)p_grab_focus); } void godot_icall_Internal_EditorNodeShowScriptScreen() { EditorNode::get_singleton()->call("_editor_select", EditorNode::EDITOR_SCRIPT); } -MonoString *godot_icall_Internal_MonoWindowsInstallRoot() { -#ifdef WINDOWS_ENABLED - String install_root_dir = GDMono::get_singleton()->get_mono_reg_info().install_root_dir; - return GDMonoMarshal::mono_string_from_godot(install_root_dir); -#else - return nullptr; -#endif -} - void godot_icall_Internal_EditorRunPlay() { EditorNode::get_singleton()->run_play(); } @@ -285,114 +170,93 @@ void godot_icall_Internal_ScriptEditorDebugger_ReloadScripts() { } } -MonoArray *godot_icall_Internal_CodeCompletionRequest(int32_t p_kind, MonoString *p_script_file) { - String script_file = GDMonoMarshal::mono_string_to_godot(p_script_file); +void godot_icall_Internal_CodeCompletionRequest(int32_t p_kind, const godot_string *p_script_file, godot_packed_array *r_ret) { + String script_file = *reinterpret_cast<const String *>(p_script_file); PackedStringArray suggestions = gdmono::get_code_completion((gdmono::CompletionKind)p_kind, script_file); - return GDMonoMarshal::PackedStringArray_to_mono_array(suggestions); + memnew_placement(r_ret, PackedStringArray(suggestions)); } float godot_icall_Globals_EditorScale() { return EDSCALE; } -MonoObject *godot_icall_Globals_GlobalDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { - String setting = GDMonoMarshal::mono_string_to_godot(p_setting); - Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); +void godot_icall_Globals_GlobalDef(const godot_string *p_setting, const godot_variant *p_default_value, bool p_restart_if_changed, godot_variant *r_result) { + String setting = *reinterpret_cast<const String *>(p_setting); + Variant default_value = *reinterpret_cast<const Variant *>(p_default_value); Variant result = _GLOBAL_DEF(setting, default_value, (bool)p_restart_if_changed); - return GDMonoMarshal::variant_to_mono_object(result); + memnew_placement(r_result, Variant(result)); } -MonoObject *godot_icall_Globals_EditorDef(MonoString *p_setting, MonoObject *p_default_value, MonoBoolean p_restart_if_changed) { - String setting = GDMonoMarshal::mono_string_to_godot(p_setting); - Variant default_value = GDMonoMarshal::mono_object_to_variant(p_default_value); +void godot_icall_Globals_EditorDef(const godot_string *p_setting, const godot_variant *p_default_value, bool p_restart_if_changed, godot_variant *r_result) { + String setting = *reinterpret_cast<const String *>(p_setting); + Variant default_value = *reinterpret_cast<const Variant *>(p_default_value); Variant result = _EDITOR_DEF(setting, default_value, (bool)p_restart_if_changed); - return GDMonoMarshal::variant_to_mono_object(result); + memnew_placement(r_result, Variant(result)); } -MonoObject *godot_icall_Globals_EditorShortcut(MonoString *p_setting) { - String setting = GDMonoMarshal::mono_string_to_godot(p_setting); +void godot_icall_Globals_EditorShortcut(const godot_string *p_setting, godot_variant *r_result) { + String setting = *reinterpret_cast<const String *>(p_setting); Ref<Shortcut> result = ED_GET_SHORTCUT(setting); - return GDMonoMarshal::variant_to_mono_object(result); + memnew_placement(r_result, Variant(result)); } -MonoString *godot_icall_Globals_TTR(MonoString *p_text) { - String text = GDMonoMarshal::mono_string_to_godot(p_text); - return GDMonoMarshal::mono_string_from_godot(TTR(text)); +void godot_icall_Globals_TTR(const godot_string *p_text, godot_string *r_dest) { + String text = *reinterpret_cast<const String *>(p_text); + memnew_placement(r_dest, String(TTR(text))); } -MonoString *godot_icall_Utils_OS_GetPlatformName() { +void godot_icall_Utils_OS_GetPlatformName(godot_string *r_dest) { String os_name = OS::get_singleton()->get_name(); - return GDMonoMarshal::mono_string_from_godot(os_name); + memnew_placement(r_dest, String(os_name)); } -MonoBoolean godot_icall_Utils_OS_UnixFileHasExecutableAccess(MonoString *p_file_path) { +bool godot_icall_Utils_OS_UnixFileHasExecutableAccess(const godot_string *p_file_path) { #ifdef UNIX_ENABLED - String file_path = GDMonoMarshal::mono_string_to_godot(p_file_path); + String file_path = *reinterpret_cast<const String *>(p_file_path); return access(file_path.utf8().get_data(), X_OK) == 0; #else ERR_FAIL_V(false); #endif } -void register_editor_internal_calls() { - // GodotSharpDirs - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResDataDir", godot_icall_GodotSharpDirs_ResDataDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResMetadataDir", godot_icall_GodotSharpDirs_ResMetadataDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesBaseDir", godot_icall_GodotSharpDirs_ResAssembliesBaseDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResAssembliesDir", godot_icall_GodotSharpDirs_ResAssembliesDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResConfigDir", godot_icall_GodotSharpDirs_ResConfigDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempDir", godot_icall_GodotSharpDirs_ResTempDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesBaseDir", godot_icall_GodotSharpDirs_ResTempAssembliesBaseDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ResTempAssembliesDir", godot_icall_GodotSharpDirs_ResTempAssembliesDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoUserDir", godot_icall_GodotSharpDirs_MonoUserDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoLogsDir", godot_icall_GodotSharpDirs_MonoLogsDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_MonoSolutionsDir", godot_icall_GodotSharpDirs_MonoSolutionsDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_BuildLogsDirs", godot_icall_GodotSharpDirs_BuildLogsDirs); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectSlnPath", godot_icall_GodotSharpDirs_ProjectSlnPath); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_ProjectCsProjPath", godot_icall_GodotSharpDirs_ProjectCsProjPath); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorToolsDir", godot_icall_GodotSharpDirs_DataEditorToolsDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataEditorPrebuiltApiDir", godot_icall_GodotSharpDirs_DataEditorPrebuiltApiDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoEtcDir", godot_icall_GodotSharpDirs_DataMonoEtcDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoLibDir", godot_icall_GodotSharpDirs_DataMonoLibDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.GodotSharpDirs::internal_DataMonoBinDir", godot_icall_GodotSharpDirs_DataMonoBinDir); - - // EditorProgress - GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Create", godot_icall_EditorProgress_Create); - GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Dispose", godot_icall_EditorProgress_Dispose); - GDMonoUtils::add_internal_call("GodotTools.Internals.EditorProgress::internal_Step", godot_icall_EditorProgress_Step); - - // ExportPlugin - GDMonoUtils::add_internal_call("GodotTools.Export.ExportPlugin::internal_GetExportedAssemblyDependencies", godot_icall_ExportPlugin_GetExportedAssemblyDependencies); - - // Internals - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_UpdateApiAssembliesFromPrebuilt", godot_icall_Internal_UpdateApiAssembliesFromPrebuilt); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_FullExportTemplatesDir", godot_icall_Internal_FullExportTemplatesDir); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_SimplifyGodotPath", godot_icall_Internal_SimplifyGodotPath); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsMacOSAppBundleInstalled", godot_icall_Internal_IsMacOSAppBundleInstalled); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIs32Bits", godot_icall_Internal_GodotIs32Bits); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotIsRealTDouble", godot_icall_Internal_GodotIsRealTDouble); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GodotMainIteration", godot_icall_Internal_GodotMainIteration); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetCoreApiHash", godot_icall_Internal_GetCoreApiHash); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_GetEditorApiHash", godot_icall_Internal_GetEditorApiHash); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_IsAssembliesReloadingNeeded", godot_icall_Internal_IsAssembliesReloadingNeeded); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ReloadAssemblies", godot_icall_Internal_ReloadAssemblies); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorDebuggerNodeReloadScripts", godot_icall_Internal_EditorDebuggerNodeReloadScripts); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorEdit", godot_icall_Internal_ScriptEditorEdit); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorNodeShowScriptScreen", godot_icall_Internal_EditorNodeShowScriptScreen); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_MonoWindowsInstallRoot", godot_icall_Internal_MonoWindowsInstallRoot); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunPlay", godot_icall_Internal_EditorRunPlay); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_EditorRunStop", godot_icall_Internal_EditorRunStop); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_ScriptEditorDebugger_ReloadScripts", godot_icall_Internal_ScriptEditorDebugger_ReloadScripts); - GDMonoUtils::add_internal_call("GodotTools.Internals.Internal::internal_CodeCompletionRequest", godot_icall_Internal_CodeCompletionRequest); - - // Globals - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorScale", godot_icall_Globals_EditorScale); - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_GlobalDef", godot_icall_Globals_GlobalDef); - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorDef", godot_icall_Globals_EditorDef); - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_EditorShortcut", godot_icall_Globals_EditorShortcut); - GDMonoUtils::add_internal_call("GodotTools.Internals.Globals::internal_TTR", godot_icall_Globals_TTR); - - // Utils.OS - GDMonoUtils::add_internal_call("GodotTools.Utils.OS::GetPlatformName", godot_icall_Utils_OS_GetPlatformName); - GDMonoUtils::add_internal_call("GodotTools.Utils.OS::UnixFileHasExecutableAccess", godot_icall_Utils_OS_UnixFileHasExecutableAccess); +#ifdef __cplusplus +} +#endif + +// The order in this array must match the declaration order of +// the methods in 'GodotTools/Internals/Internal.cs'. +static const void *unmanaged_callbacks[]{ + (void *)godot_icall_GodotSharpDirs_ResMetadataDir, + (void *)godot_icall_GodotSharpDirs_MonoUserDir, + (void *)godot_icall_GodotSharpDirs_BuildLogsDirs, + (void *)godot_icall_GodotSharpDirs_DataEditorToolsDir, + (void *)godot_icall_EditorProgress_Create, + (void *)godot_icall_EditorProgress_Dispose, + (void *)godot_icall_EditorProgress_Step, + (void *)godot_icall_Internal_FullExportTemplatesDir, + (void *)godot_icall_Internal_IsMacOSAppBundleInstalled, + (void *)godot_icall_Internal_GodotIs32Bits, + (void *)godot_icall_Internal_GodotIsRealTDouble, + (void *)godot_icall_Internal_GodotMainIteration, + (void *)godot_icall_Internal_IsAssembliesReloadingNeeded, + (void *)godot_icall_Internal_ReloadAssemblies, + (void *)godot_icall_Internal_EditorDebuggerNodeReloadScripts, + (void *)godot_icall_Internal_ScriptEditorEdit, + (void *)godot_icall_Internal_EditorNodeShowScriptScreen, + (void *)godot_icall_Internal_EditorRunPlay, + (void *)godot_icall_Internal_EditorRunStop, + (void *)godot_icall_Internal_ScriptEditorDebugger_ReloadScripts, + (void *)godot_icall_Internal_CodeCompletionRequest, + (void *)godot_icall_Globals_EditorScale, + (void *)godot_icall_Globals_GlobalDef, + (void *)godot_icall_Globals_EditorDef, + (void *)godot_icall_Globals_EditorShortcut, + (void *)godot_icall_Globals_TTR, + (void *)godot_icall_Utils_OS_GetPlatformName, + (void *)godot_icall_Utils_OS_UnixFileHasExecutableAccess, +}; + +const void **godotsharp::get_editor_interop_funcs(int32_t &r_size) { + r_size = sizeof(unmanaged_callbacks); + return unmanaged_callbacks; } diff --git a/modules/mono/editor/editor_internal_calls.h b/modules/mono/editor/editor_internal_calls.h index 8262ac211a..35391f1f04 100644 --- a/modules/mono/editor/editor_internal_calls.h +++ b/modules/mono/editor/editor_internal_calls.h @@ -31,6 +31,10 @@ #ifndef EDITOR_INTERNAL_CALLS_H #define EDITOR_INTERNAL_CALLS_H -void register_editor_internal_calls(); +#include "core/typedefs.h" + +namespace godotsharp { +const void **get_editor_interop_funcs(int32_t &r_size); +} #endif // EDITOR_INTERNAL_CALLS_H diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp deleted file mode 100644 index f9ea403334..0000000000 --- a/modules/mono/editor/godotsharp_export.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/*************************************************************************/ -/* godotsharp_export.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "godotsharp_export.h" - -#include <mono/metadata/image.h> - -#include "core/config/project_settings.h" -#include "core/io/file_access_pack.h" -#include "core/os/os.h" - -#include "../mono_gd/gd_mono.h" -#include "../mono_gd/gd_mono_assembly.h" -#include "../mono_gd/gd_mono_cache.h" -#include "../utils/macros.h" - -namespace GodotSharpExport { - -MonoAssemblyName *new_mono_assembly_name() { - // Mono has no public API to create an empty MonoAssemblyName and the struct is private. - // As such the only way to create it is with a stub name and then clear it. - - MonoAssemblyName *aname = mono_assembly_name_new("stub"); - CRASH_COND(aname == nullptr); - mono_assembly_name_free(aname); // Frees the string fields, not the struct - return aname; -} - -struct AssemblyRefInfo { - String name; - uint16_t major = 0; - uint16_t minor = 0; - uint16_t build = 0; - uint16_t revision = 0; -}; - -AssemblyRefInfo get_assemblyref_name(MonoImage *p_image, int index) { - const MonoTableInfo *table_info = mono_image_get_table_info(p_image, MONO_TABLE_ASSEMBLYREF); - - uint32_t cols[MONO_ASSEMBLYREF_SIZE]; - - mono_metadata_decode_row(table_info, index, cols, MONO_ASSEMBLYREF_SIZE); - - return { - String::utf8(mono_metadata_string_heap(p_image, cols[MONO_ASSEMBLYREF_NAME])), - (uint16_t)cols[MONO_ASSEMBLYREF_MAJOR_VERSION], - (uint16_t)cols[MONO_ASSEMBLYREF_MINOR_VERSION], - (uint16_t)cols[MONO_ASSEMBLYREF_BUILD_NUMBER], - (uint16_t)cols[MONO_ASSEMBLYREF_REV_NUMBER] - }; -} - -Error get_assembly_dependencies(GDMonoAssembly *p_assembly, MonoAssemblyName *reusable_aname, const Vector<String> &p_search_dirs, Dictionary &r_assembly_dependencies) { - MonoImage *image = p_assembly->get_image(); - - for (int i = 0; i < mono_image_get_table_rows(image, MONO_TABLE_ASSEMBLYREF); i++) { - AssemblyRefInfo ref_info = get_assemblyref_name(image, i); - - const String &ref_name = ref_info.name; - - if (r_assembly_dependencies.has(ref_name)) { - continue; - } - - mono_assembly_get_assemblyref(image, i, reusable_aname); - - GDMonoAssembly *ref_assembly = nullptr; - if (!GDMono::get_singleton()->load_assembly(ref_name, reusable_aname, &ref_assembly, /* refonly: */ true, p_search_dirs)) { - ERR_FAIL_V_MSG(ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + ref_name + "'."); - } - - r_assembly_dependencies[ref_name] = ref_assembly->get_path(); - - Error err = get_assembly_dependencies(ref_assembly, reusable_aname, p_search_dirs, r_assembly_dependencies); - ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot load one of the dependencies for the assembly: '" + ref_name + "'."); - } - - return OK; -} - -Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies, - const String &p_build_config, const String &p_custom_bcl_dir, Dictionary &r_assembly_dependencies) { - MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.Domain.ProjectExport"); - ERR_FAIL_NULL_V(export_domain, FAILED); - _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(export_domain); - - _GDMONO_SCOPE_DOMAIN_(export_domain); - - Vector<String> search_dirs; - GDMonoAssembly::fill_search_dirs(search_dirs, p_build_config, p_custom_bcl_dir); - - if (p_custom_bcl_dir.length()) { - // Only one mscorlib can be loaded. We need this workaround to make sure we get it from the right BCL directory. - r_assembly_dependencies["mscorlib"] = p_custom_bcl_dir.plus_file("mscorlib.dll").simplify_path(); - } - - for (const Variant *key = p_initial_assemblies.next(); key; key = p_initial_assemblies.next(key)) { - String assembly_name = *key; - String assembly_path = p_initial_assemblies[*key]; - - GDMonoAssembly *assembly = nullptr; - bool load_success = GDMono::get_singleton()->load_assembly_from(assembly_name, assembly_path, &assembly, /* refonly: */ true); - - ERR_FAIL_COND_V_MSG(!load_success, ERR_CANT_RESOLVE, "Cannot load assembly (refonly): '" + assembly_name + "'."); - - MonoAssemblyName *reusable_aname = new_mono_assembly_name(); - SCOPE_EXIT { mono_free(reusable_aname); }; - - Error err = get_assembly_dependencies(assembly, reusable_aname, search_dirs, r_assembly_dependencies); - if (err != OK) { - return err; - } - } - - return OK; -} -} // namespace GodotSharpExport diff --git a/modules/mono/editor/godotsharp_export.h b/modules/mono/editor/godotsharp_export.h deleted file mode 100644 index 60620b5f4d..0000000000 --- a/modules/mono/editor/godotsharp_export.h +++ /dev/null @@ -1,48 +0,0 @@ -/*************************************************************************/ -/* godotsharp_export.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GODOTSHARP_EXPORT_H -#define GODOTSHARP_EXPORT_H - -#include "core/error/error_list.h" -#include "core/string/ustring.h" -#include "core/variant/dictionary.h" - -#include "../mono_gd/gd_mono_header.h" - -namespace GodotSharpExport { - -Error get_assembly_dependencies(GDMonoAssembly *p_assembly, const Vector<String> &p_search_dirs, Dictionary &r_dependencies); - -Error get_exported_assembly_dependencies(const Dictionary &p_initial_assemblies, - const String &p_build_config, const String &p_custom_lib_dir, Dictionary &r_assembly_dependencies); -} // namespace GodotSharpExport - -#endif // GODOTSHARP_EXPORT_H diff --git a/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs b/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs index 2ca81ab7cd..c34f1a17f3 100644 --- a/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs +++ b/modules/mono/editor/script_templates/CharacterBody2D/basic_movement.cs @@ -9,7 +9,7 @@ public partial class _CLASS_ : _BASE_ public const float JumpVelocity = -400.0f; // Get the gravity from the project settings to be synced with RigidDynamicBody nodes. - public float gravity = (float)ProjectSettings.GetSetting("physics/2d/default_gravity"); + public float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle(); public override void _PhysicsProcess(float delta) { diff --git a/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs b/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs index a6935fe497..188bbb775c 100644 --- a/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs +++ b/modules/mono/editor/script_templates/CharacterBody3D/basic_movement.cs @@ -9,7 +9,7 @@ public partial class _CLASS_ : _BASE_ public const float JumpVelocity = 4.5f; // Get the gravity from the project settings to be synced with RigidDynamicBody nodes. - public float gravity = (float)ProjectSettings.GetSetting("physics/3d/default_gravity"); + public float gravity = ProjectSettings.GetSetting("physics/3d/default_gravity").AsSingle(); public override void _PhysicsProcess(float delta) { diff --git a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs index a1b93e7daa..bb482e0d6a 100644 --- a/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs +++ b/modules/mono/editor/script_templates/VisualShaderNodeCustom/basic.cs @@ -55,7 +55,7 @@ public partial class VisualShaderNode_CLASS_ : _BASE_ return 0; } - public override string _GetCode(Godot.Collections.Array inputVars, Godot.Collections.Array outputVars, Shader.Mode mode, VisualShader.Type type) + public override string _GetCode(Godot.Collections.Array<string> inputVars, Godot.Collections.Array<string> outputVars, Shader.Mode mode, VisualShader.Type type) { return ""; } diff --git a/modules/mono/glue/GodotSharp/.editorconfig b/modules/mono/glue/GodotSharp/.editorconfig new file mode 100644 index 0000000000..d4e71b1bd9 --- /dev/null +++ b/modules/mono/glue/GodotSharp/.editorconfig @@ -0,0 +1,8 @@ +[**/Generated/**.cs] +# Validate parameter is non-null before using it +# Useful for generated code, as it disables nullable +dotnet_diagnostic.CA1062.severity = error +# CA1069: Enums should not have duplicate values +dotnet_diagnostic.CA1069.severity = none +# CA1708: Identifiers should differ by more than case +dotnet_diagnostic.CA1708.severity = none diff --git a/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml b/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml new file mode 100644 index 0000000000..2dc350d4f2 --- /dev/null +++ b/modules/mono/glue/GodotSharp/ExternalAnnotations/System.Runtime.InteropServices.xml @@ -0,0 +1,5 @@ +<assembly name="System.Runtime.InteropServices"> + <member name="T:System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute"> + <attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" /> + </member> +</assembly> diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs new file mode 100644 index 0000000000..686023a077 --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/CallbacksInfo.cs @@ -0,0 +1,24 @@ +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators.Internal; + +internal struct CallbacksData +{ + public CallbacksData(INamedTypeSymbol nativeTypeSymbol, INamedTypeSymbol funcStructSymbol) + { + NativeTypeSymbol = nativeTypeSymbol; + FuncStructSymbol = funcStructSymbol; + Methods = NativeTypeSymbol.GetMembers() + .Where(symbol => symbol is IMethodSymbol { IsPartialDefinition: true }) + .Cast<IMethodSymbol>() + .ToImmutableArray(); + } + + public INamedTypeSymbol NativeTypeSymbol { get; } + + public INamedTypeSymbol FuncStructSymbol { get; } + + public ImmutableArray<IMethodSymbol> Methods { get; } +} diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs new file mode 100644 index 0000000000..16e96c725a --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Common.cs @@ -0,0 +1,65 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators.Internal; + +internal static class Common +{ + public static void ReportNonPartialUnmanagedCallbacksClass( + GeneratorExecutionContext context, + ClassDeclarationSyntax cds, INamedTypeSymbol symbol + ) + { + string message = + "Missing partial modifier on declaration of type '" + + $"{symbol.FullQualifiedName()}' which has attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; + + string description = $"{message}. Classes with attribute '{GeneratorClasses.GenerateUnmanagedCallbacksAttr}' " + + "must be declared with the partial modifier."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-INTERNAL-G0001", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + cds.GetLocation(), + cds.SyntaxTree.FilePath)); + } + + public static void ReportNonPartialUnmanagedCallbacksOuterClass( + GeneratorExecutionContext context, + TypeDeclarationSyntax outerTypeDeclSyntax + ) + { + var outerSymbol = context.Compilation + .GetSemanticModel(outerTypeDeclSyntax.SyntaxTree) + .GetDeclaredSymbol(outerTypeDeclSyntax); + + string fullQualifiedName = outerSymbol is INamedTypeSymbol namedTypeSymbol ? + namedTypeSymbol.FullQualifiedName() : + "type not found"; + + string message = + $"Missing partial modifier on declaration of type '{fullQualifiedName}', " + + $"which contains one or more subclasses with attribute " + + $"'{GeneratorClasses.GenerateUnmanagedCallbacksAttr}'"; + + string description = $"{message}. Classes with attribute " + + $"'{GeneratorClasses.GenerateUnmanagedCallbacksAttr}' and their " + + "containing types must be declared with the partial modifier."; + + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor(id: "GODOT-INTERNAL-G0002", + title: message, + messageFormat: message, + category: "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description), + outerTypeDeclSyntax.GetLocation(), + outerTypeDeclSyntax.SyntaxTree.FilePath)); + } +} diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs new file mode 100644 index 0000000000..fac362479a --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/ExtensionMethods.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators.Internal; + +internal static class ExtensionMethods +{ + public static AttributeData? GetGenerateUnmanagedCallbacksAttribute(this INamedTypeSymbol symbol) + => symbol.GetAttributes() + .FirstOrDefault(a => a.AttributeClass?.IsGenerateUnmanagedCallbacksAttribute() ?? false); + + private static bool HasGenerateUnmanagedCallbacksAttribute( + this ClassDeclarationSyntax cds, Compilation compilation, + out INamedTypeSymbol? symbol + ) + { + var sm = compilation.GetSemanticModel(cds.SyntaxTree); + + var classTypeSymbol = sm.GetDeclaredSymbol(cds); + if (classTypeSymbol == null) + { + symbol = null; + return false; + } + + if (!classTypeSymbol.GetAttributes() + .Any(a => a.AttributeClass?.IsGenerateUnmanagedCallbacksAttribute() ?? false)) + { + symbol = null; + return false; + } + + symbol = classTypeSymbol; + return true; + } + + private static bool IsGenerateUnmanagedCallbacksAttribute(this INamedTypeSymbol symbol) + => symbol.ToString() == GeneratorClasses.GenerateUnmanagedCallbacksAttr; + + public static IEnumerable<(ClassDeclarationSyntax cds, INamedTypeSymbol symbol)> SelectUnmanagedCallbacksClasses( + this IEnumerable<ClassDeclarationSyntax> source, + Compilation compilation + ) + { + foreach (var cds in source) + { + if (cds.HasGenerateUnmanagedCallbacksAttribute(compilation, out var symbol)) + yield return (cds, symbol!); + } + } + + public static bool IsNested(this TypeDeclarationSyntax cds) + => cds.Parent is TypeDeclarationSyntax; + + public static bool IsPartial(this TypeDeclarationSyntax cds) + => cds.Modifiers.Any(SyntaxKind.PartialKeyword); + + public static bool AreAllOuterTypesPartial( + this TypeDeclarationSyntax cds, + out TypeDeclarationSyntax? typeMissingPartial + ) + { + SyntaxNode? outerSyntaxNode = cds.Parent; + + while (outerSyntaxNode is TypeDeclarationSyntax outerTypeDeclSyntax) + { + if (!outerTypeDeclSyntax.IsPartial()) + { + typeMissingPartial = outerTypeDeclSyntax; + return false; + } + + outerSyntaxNode = outerSyntaxNode.Parent; + } + + typeMissingPartial = null; + return true; + } + + public static string GetDeclarationKeyword(this INamedTypeSymbol namedTypeSymbol) + { + string? keyword = namedTypeSymbol.DeclaringSyntaxReferences + .OfType<TypeDeclarationSyntax>().FirstOrDefault()? + .Keyword.Text; + + return keyword ?? namedTypeSymbol.TypeKind switch + { + TypeKind.Interface => "interface", + TypeKind.Struct => "struct", + _ => "class" + }; + } + + private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = + SymbolDisplayFormat.FullyQualifiedFormat + .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + + public static string FullQualifiedName(this ITypeSymbol symbol) + => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); + + public static string NameWithTypeParameters(this INamedTypeSymbol symbol) + { + return symbol.IsGenericType ? + string.Concat(symbol.Name, "<", string.Join(", ", symbol.TypeParameters), ">") : + symbol.Name; + } + + public static string FullQualifiedName(this INamespaceSymbol symbol) + => symbol.ToDisplayString(FullyQualifiedFormatOmitGlobal); + + public static string SanitizeQualifiedNameForUniqueHint(this string qualifiedName) + => qualifiedName + // AddSource() doesn't support angle brackets + .Replace("<", "(Of ") + .Replace(">", ")"); +} diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/GeneratorClasses.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/GeneratorClasses.cs new file mode 100644 index 0000000000..1bbb33f5a1 --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/GeneratorClasses.cs @@ -0,0 +1,6 @@ +namespace Godot.SourceGenerators.Internal; + +internal static class GeneratorClasses +{ + public const string GenerateUnmanagedCallbacksAttr = "Godot.SourceGenerators.Internal.GenerateUnmanagedCallbacksAttribute"; +} diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Godot.SourceGenerators.Internal.csproj b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Godot.SourceGenerators.Internal.csproj new file mode 100644 index 0000000000..4d1a5bb76c --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/Godot.SourceGenerators.Internal.csproj @@ -0,0 +1,11 @@ +<Project Sdk="Microsoft.NET.Sdk"> + <PropertyGroup> + <TargetFramework>netstandard2.0</TargetFramework> + <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> + </PropertyGroup> + <ItemGroup> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.10.0" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" /> + </ItemGroup> +</Project> diff --git a/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs new file mode 100644 index 0000000000..da578309bc --- /dev/null +++ b/modules/mono/glue/GodotSharp/Godot.SourceGenerators.Internal/UnmanagedCallbacksGenerator.cs @@ -0,0 +1,463 @@ +using System.Text; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Godot.SourceGenerators.Internal; + +[Generator] +public class UnmanagedCallbacksGenerator : ISourceGenerator +{ + public void Initialize(GeneratorInitializationContext context) + { + context.RegisterForPostInitialization(ctx => { GenerateAttribute(ctx); }); + } + + public void Execute(GeneratorExecutionContext context) + { + INamedTypeSymbol[] unmanagedCallbacksClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType<ClassDeclarationSyntax>() + .SelectUnmanagedCallbacksClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + { + if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial)) + { + Common.ReportNonPartialUnmanagedCallbacksOuterClass(context, typeMissingPartial!); + return false; + } + + return true; + } + + Common.ReportNonPartialUnmanagedCallbacksClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct<INamedTypeSymbol>(SymbolEqualityComparer.Default) + .ToArray(); + + foreach (var symbol in unmanagedCallbacksClasses) + { + var attr = symbol.GetGenerateUnmanagedCallbacksAttribute(); + if (attr == null || attr.ConstructorArguments.Length != 1) + { + // TODO: Report error or throw exception, this is an invalid case and should never be reached + System.Diagnostics.Debug.Fail("FAILED!"); + continue; + } + + var funcStructType = (INamedTypeSymbol?)attr.ConstructorArguments[0].Value; + if (funcStructType == null) + { + // TODO: Report error or throw exception, this is an invalid case and should never be reached + System.Diagnostics.Debug.Fail("FAILED!"); + continue; + } + + var data = new CallbacksData(symbol, funcStructType); + GenerateInteropMethodImplementations(context, data); + GenerateUnmanagedCallbacksStruct(context, data); + } + } + + private void GenerateAttribute(GeneratorPostInitializationContext context) + { + string source = @"using System; + +namespace Godot.SourceGenerators.Internal +{ +internal class GenerateUnmanagedCallbacksAttribute : Attribute +{ + public Type FuncStructType { get; } + + public GenerateUnmanagedCallbacksAttribute(Type funcStructType) + { + FuncStructType = funcStructType; + } +} +}"; + + context.AddSource("GenerateUnmanagedCallbacksAttribute.generated", + SourceText.From(source, Encoding.UTF8)); + } + + private void GenerateInteropMethodImplementations(GeneratorExecutionContext context, CallbacksData data) + { + var symbol = data.NativeTypeSymbol; + + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + bool isInnerClass = symbol.ContainingType != null; + + var source = new StringBuilder(); + var methodSource = new StringBuilder(); + var methodCallArguments = new StringBuilder(); + var methodSourceAfterCall = new StringBuilder(); + + source.Append( + @"using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.Bridge; +using Godot.NativeInterop; + +#pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores + +"); + + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append("\n{\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("[System.Runtime.CompilerServices.SkipLocalsInit]\n"); + source.Append($"unsafe partial class {symbol.Name}\n"); + source.Append("{\n"); + source.Append($" private static {data.FuncStructSymbol.FullQualifiedName()} _unmanagedCallbacks;\n\n"); + + foreach (var callback in data.Methods) + { + methodSource.Clear(); + methodCallArguments.Clear(); + methodSourceAfterCall.Clear(); + + source.Append(" [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]\n"); + source.Append($" {SyntaxFacts.GetText(callback.DeclaredAccessibility)} "); + + if (callback.IsStatic) + source.Append("static "); + + source.Append("partial "); + source.Append(callback.ReturnType.FullQualifiedName()); + source.Append(' '); + source.Append(callback.Name); + source.Append('('); + + for (int i = 0; i < callback.Parameters.Length; i++) + { + var parameter = callback.Parameters[i]; + + source.Append(parameter.ToDisplayString()); + source.Append(' '); + source.Append(parameter.Name); + + if (parameter.RefKind == RefKind.Out) + { + // Only assign default if the parameter won't be passed by-ref or copied later. + if (IsGodotInteropStruct(parameter.Type)) + methodSource.Append($" {parameter.Name} = default;\n"); + } + + if (IsByRefParameter(parameter)) + { + if (IsGodotInteropStruct(parameter.Type)) + { + methodSource.Append(" "); + AppendCustomUnsafeAsPointer(methodSource, parameter, out string varName); + methodCallArguments.Append(varName); + } + else if (parameter.Type.IsValueType) + { + methodSource.Append(" "); + AppendCopyToStackAndGetPointer(methodSource, parameter, out string varName); + methodCallArguments.Append($"&{varName}"); + + if (parameter.RefKind is RefKind.Out or RefKind.Ref) + { + methodSourceAfterCall.Append($" {parameter.Name} = {varName};\n"); + } + } + else + { + // If it's a by-ref param and we can't get the pointer + // just pass it by-ref and let it be pinned. + AppendRefKind(methodCallArguments, parameter.RefKind) + .Append(' ') + .Append(parameter.Name); + } + } + else + { + methodCallArguments.Append(parameter.Name); + } + + if (i < callback.Parameters.Length - 1) + { + source.Append(", "); + methodCallArguments.Append(", "); + } + } + + source.Append(")\n"); + source.Append(" {\n"); + + source.Append(methodSource); + source.Append(" "); + + if (!callback.ReturnsVoid) + { + if (methodSourceAfterCall.Length != 0) + source.Append($"{callback.ReturnType.FullQualifiedName()} ret = "); + else + source.Append("return "); + } + + source.Append($"_unmanagedCallbacks.{callback.Name}("); + source.Append(methodCallArguments); + source.Append(");\n"); + + if (methodSourceAfterCall.Length != 0) + { + source.Append(methodSourceAfterCall); + + if (!callback.ReturnsVoid) + source.Append(" return ret;\n"); + } + + source.Append(" }\n\n"); + } + + source.Append("}\n"); + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + source.Append("\n}"); + + source.Append("\n\n#pragma warning restore CA1707\n"); + + context.AddSource($"{data.NativeTypeSymbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private void GenerateUnmanagedCallbacksStruct(GeneratorExecutionContext context, CallbacksData data) + { + var symbol = data.FuncStructSymbol; + + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + bool isInnerClass = symbol.ContainingType != null; + + var source = new StringBuilder(); + + source.Append( + @"using System.Runtime.InteropServices; +using Godot.NativeInterop; + +#pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores + +"); + if (hasNamespace) + { + source.Append("namespace "); + source.Append(classNs); + source.Append("\n{\n"); + } + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("partial "); + source.Append(containingType.GetDeclarationKeyword()); + source.Append(" "); + source.Append(containingType.NameWithTypeParameters()); + source.Append("\n{\n"); + + containingType = containingType.ContainingType; + } + } + + source.Append("[StructLayout(LayoutKind.Sequential)]\n"); + source.Append($"unsafe partial struct {symbol.Name}\n{{\n"); + + foreach (var callback in data.Methods) + { + source.Append(" "); + source.Append(callback.DeclaredAccessibility == Accessibility.Public ? "public " : "internal "); + + source.Append("delegate* unmanaged<"); + + foreach (var parameter in callback.Parameters) + { + if (IsByRefParameter(parameter)) + { + if (IsGodotInteropStruct(parameter.Type) || parameter.Type.IsValueType) + { + AppendPointerType(source, parameter.Type); + } + else + { + // If it's a by-ref param and we can't get the pointer + // just pass it by-ref and let it be pinned. + AppendRefKind(source, parameter.RefKind) + .Append(' ') + .Append(parameter.Type.FullQualifiedName()); + } + } + else + { + source.Append(parameter.Type.FullQualifiedName()); + } + + source.Append(", "); + } + + source.Append(callback.ReturnType.FullQualifiedName()); + source.Append($"> {callback.Name};\n"); + } + + source.Append("}\n"); + + if (isInnerClass) + { + var containingType = symbol.ContainingType; + + while (containingType != null) + { + source.Append("}\n"); // outer class + + containingType = containingType.ContainingType; + } + } + + if (hasNamespace) + source.Append("}\n"); + + source.Append("\n#pragma warning restore CA1707\n"); + + context.AddSource($"{symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated", + SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static bool IsGodotInteropStruct(ITypeSymbol type) => + GodotInteropStructs.Contains(type.FullQualifiedName()); + + private static bool IsByRefParameter(IParameterSymbol parameter) => + parameter.RefKind is RefKind.In or RefKind.Out or RefKind.Ref; + + private static StringBuilder AppendRefKind(StringBuilder source, RefKind refKind) => + refKind switch + { + RefKind.In => source.Append("in"), + RefKind.Out => source.Append("out"), + RefKind.Ref => source.Append("ref"), + _ => source, + }; + + private static void AppendPointerType(StringBuilder source, ITypeSymbol type) + { + source.Append(type.FullQualifiedName()); + source.Append('*'); + } + + private static void AppendCustomUnsafeAsPointer(StringBuilder source, IParameterSymbol parameter, + out string varName) + { + varName = $"{parameter.Name}_ptr"; + + AppendPointerType(source, parameter.Type); + source.Append(' '); + source.Append(varName); + source.Append(" = "); + + source.Append('('); + AppendPointerType(source, parameter.Type); + source.Append(')'); + + if (parameter.RefKind == RefKind.In) + source.Append("CustomUnsafe.ReadOnlyRefAsPointer(in "); + else + source.Append("CustomUnsafe.AsPointer(ref "); + + source.Append(parameter.Name); + + source.Append(");\n"); + } + + private static void AppendCopyToStackAndGetPointer(StringBuilder source, IParameterSymbol parameter, + out string varName) + { + varName = $"{parameter.Name}_copy"; + + source.Append(parameter.Type.FullQualifiedName()); + source.Append(' '); + source.Append(varName); + if (parameter.RefKind is RefKind.In or RefKind.Ref) + { + source.Append(" = "); + source.Append(parameter.Name); + } + + source.Append(";\n"); + } + + private static readonly string[] GodotInteropStructs = + { + "Godot.NativeInterop.godot_ref", + "Godot.NativeInterop.godot_variant_call_error", + "Godot.NativeInterop.godot_variant", + "Godot.NativeInterop.godot_string", + "Godot.NativeInterop.godot_string_name", + "Godot.NativeInterop.godot_node_path", + "Godot.NativeInterop.godot_signal", + "Godot.NativeInterop.godot_callable", + "Godot.NativeInterop.godot_array", + "Godot.NativeInterop.godot_dictionary", + "Godot.NativeInterop.godot_packed_byte_array", + "Godot.NativeInterop.godot_packed_int32_array", + "Godot.NativeInterop.godot_packed_int64_array", + "Godot.NativeInterop.godot_packed_float32_array", + "Godot.NativeInterop.godot_packed_float64_array", + "Godot.NativeInterop.godot_packed_string_array", + "Godot.NativeInterop.godot_packed_vector2_array", + "Godot.NativeInterop.godot_packed_vector3_array", + "Godot.NativeInterop.godot_packed_color_array", + }; +} diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/GodotPlugins.csproj b/modules/mono/glue/GodotSharp/GodotPlugins/GodotPlugins.csproj new file mode 100644 index 0000000000..e720d3878c --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotPlugins/GodotPlugins.csproj @@ -0,0 +1,17 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <LangVersion>10</LangVersion> + <Nullable>enable</Nullable> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + + <!-- To generate the .runtimeconfig.json file--> + <EnableDynamicLoading>true</EnableDynamicLoading> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\GodotSharp\GodotSharp.csproj" /> + </ItemGroup> + +</Project> diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs new file mode 100644 index 0000000000..dad7464410 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotPlugins/Main.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using Godot.Bridge; +using Godot.NativeInterop; + +namespace GodotPlugins +{ + public static class Main + { + // IMPORTANT: + // Keeping strong references to the AssemblyLoadContext (our PluginLoadContext) prevents + // it from being unloaded. To avoid issues, we wrap the reference in this class, and mark + // all the methods that access it as non-inlineable. This way we prevent local references + // (either real or introduced by the JIT) to escape the scope of these methods due to + // inlining, which could keep the AssemblyLoadContext alive while trying to unload. + private sealed class PluginLoadContextWrapper + { + private PluginLoadContext? _pluginLoadContext; + + public string? AssemblyLoadedPath + { + [MethodImpl(MethodImplOptions.NoInlining)] + get => _pluginLoadContext?.AssemblyLoadedPath; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static (Assembly, PluginLoadContextWrapper) CreateAndLoadFromAssemblyName( + AssemblyName assemblyName, + string pluginPath, + ICollection<string> sharedAssemblies, + AssemblyLoadContext mainLoadContext + ) + { + var wrapper = new PluginLoadContextWrapper(); + wrapper._pluginLoadContext = new PluginLoadContext( + pluginPath, sharedAssemblies, mainLoadContext); + var assembly = wrapper._pluginLoadContext.LoadFromAssemblyName(assemblyName); + return (assembly, wrapper); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public WeakReference CreateWeakReference() + { + return new WeakReference(_pluginLoadContext, trackResurrection: true); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal void Unload() + { + _pluginLoadContext?.Unload(); + _pluginLoadContext = null; + } + } + + private static readonly List<AssemblyName> SharedAssemblies = new(); + private static readonly Assembly CoreApiAssembly = typeof(Godot.Object).Assembly; + private static Assembly? _editorApiAssembly; + private static PluginLoadContextWrapper? _projectLoadContext; + + private static readonly AssemblyLoadContext MainLoadContext = + AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? + AssemblyLoadContext.Default; + + private static DllImportResolver? _dllImportResolver; + + // Right now we do it this way for simplicity as hot-reload is disabled. It will need to be changed later. + [UnmanagedCallersOnly] + // ReSharper disable once UnusedMember.Local + private static unsafe godot_bool InitializeFromEngine(IntPtr godotDllHandle, godot_bool editorHint, + PluginsCallbacks* pluginsCallbacks, ManagedCallbacks* managedCallbacks, + IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) + { + try + { + _dllImportResolver = new GodotDllImportResolver(godotDllHandle).OnResolveDllImport; + + SharedAssemblies.Add(CoreApiAssembly.GetName()); + NativeLibrary.SetDllImportResolver(CoreApiAssembly, _dllImportResolver); + + AlcReloadCfg.Configure(alcReloadEnabled: editorHint.ToBool()); + NativeFuncs.Initialize(unmanagedCallbacks, unmanagedCallbacksSize); + + if (editorHint.ToBool()) + { + _editorApiAssembly = Assembly.Load("GodotSharpEditor"); + SharedAssemblies.Add(_editorApiAssembly.GetName()); + NativeLibrary.SetDllImportResolver(_editorApiAssembly, _dllImportResolver); + } + + *pluginsCallbacks = new() + { + LoadProjectAssemblyCallback = &LoadProjectAssembly, + LoadToolsAssemblyCallback = &LoadToolsAssembly, + UnloadProjectPluginCallback = &UnloadProjectPlugin, + }; + + *managedCallbacks = ManagedCallbacks.Create(); + + return godot_bool.True; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return godot_bool.False; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct PluginsCallbacks + { + public unsafe delegate* unmanaged<char*, godot_string*, godot_bool> LoadProjectAssemblyCallback; + public unsafe delegate* unmanaged<char*, IntPtr, int, IntPtr> LoadToolsAssemblyCallback; + public unsafe delegate* unmanaged<godot_bool> UnloadProjectPluginCallback; + } + + [UnmanagedCallersOnly] + private static unsafe godot_bool LoadProjectAssembly(char* nAssemblyPath, godot_string* outLoadedAssemblyPath) + { + try + { + if (_projectLoadContext != null) + return godot_bool.True; // Already loaded + + string assemblyPath = new(nAssemblyPath); + + (var projectAssembly, _projectLoadContext) = LoadPlugin(assemblyPath); + + string loadedAssemblyPath = _projectLoadContext.AssemblyLoadedPath ?? assemblyPath; + *outLoadedAssemblyPath = Marshaling.ConvertStringToNative(loadedAssemblyPath); + + ScriptManagerBridge.LookupScriptsInAssembly(projectAssembly); + + return godot_bool.True; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + private static unsafe IntPtr LoadToolsAssembly(char* nAssemblyPath, + IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) + { + try + { + string assemblyPath = new(nAssemblyPath); + + if (_editorApiAssembly == null) + throw new InvalidOperationException("The Godot editor API assembly is not loaded"); + + var (assembly, _) = LoadPlugin(assemblyPath); + + NativeLibrary.SetDllImportResolver(assembly, _dllImportResolver!); + + var method = assembly.GetType("GodotTools.GodotSharpEditor")? + .GetMethod("InternalCreateInstance", + BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + + if (method == null) + { + throw new MissingMethodException("GodotTools.GodotSharpEditor", + "InternalCreateInstance"); + } + + return (IntPtr?)method + .Invoke(null, new object[] { unmanagedCallbacks, unmanagedCallbacksSize }) + ?? IntPtr.Zero; + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return IntPtr.Zero; + } + } + + private static (Assembly, PluginLoadContextWrapper) LoadPlugin(string assemblyPath) + { + string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath); + + var sharedAssemblies = new List<string>(); + + foreach (var sharedAssembly in SharedAssemblies) + { + string? sharedAssemblyName = sharedAssembly.Name; + if (sharedAssemblyName != null) + sharedAssemblies.Add(sharedAssemblyName); + } + + return PluginLoadContextWrapper.CreateAndLoadFromAssemblyName( + new AssemblyName(assemblyName), assemblyPath, sharedAssemblies, MainLoadContext); + } + + [UnmanagedCallersOnly] + private static godot_bool UnloadProjectPlugin() + { + try + { + return UnloadPlugin(ref _projectLoadContext).ToGodotBool(); + } + catch (Exception e) + { + Console.Error.WriteLine(e); + return godot_bool.False; + } + } + + private static bool UnloadPlugin(ref PluginLoadContextWrapper? pluginLoadContext) + { + try + { + if (pluginLoadContext == null) + return true; + + Console.WriteLine("Unloading assembly load context..."); + + var alcWeakReference = pluginLoadContext.CreateWeakReference(); + + pluginLoadContext.Unload(); + pluginLoadContext = null; + + int startTimeMs = Environment.TickCount; + bool takingTooLong = false; + + while (alcWeakReference.IsAlive) + { + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); + GC.WaitForPendingFinalizers(); + + if (!alcWeakReference.IsAlive) + break; + + int elapsedTimeMs = Environment.TickCount - startTimeMs; + + if (!takingTooLong && elapsedTimeMs >= 2000) + { + takingTooLong = true; + + // TODO: How to log from GodotPlugins? (delegate pointer?) + Console.Error.WriteLine("Assembly unloading is taking longer than expected..."); + } + else if (elapsedTimeMs >= 5000) + { + // TODO: How to log from GodotPlugins? (delegate pointer?) + Console.Error.WriteLine( + "Failed to unload assemblies. Possible causes: Strong GC handles, running threads, etc."); + + return false; + } + } + + Console.WriteLine("Assembly load context unloaded successfully."); + + return true; + } + catch (Exception e) + { + // TODO: How to log exceptions from GodotPlugins? (delegate pointer?) + Console.Error.WriteLine(e); + return false; + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs new file mode 100644 index 0000000000..dcd572c65e --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; + +namespace GodotPlugins +{ + public class PluginLoadContext : AssemblyLoadContext + { + private readonly AssemblyDependencyResolver _resolver; + private readonly ICollection<string> _sharedAssemblies; + private readonly AssemblyLoadContext _mainLoadContext; + + public string? AssemblyLoadedPath { get; private set; } + + public PluginLoadContext(string pluginPath, ICollection<string> sharedAssemblies, + AssemblyLoadContext mainLoadContext) + : base(isCollectible: true) + { + _resolver = new AssemblyDependencyResolver(pluginPath); + _sharedAssemblies = sharedAssemblies; + _mainLoadContext = mainLoadContext; + } + + protected override Assembly? Load(AssemblyName assemblyName) + { + if (assemblyName.Name == null) + return null; + + if (_sharedAssemblies.Contains(assemblyName.Name)) + return _mainLoadContext.LoadFromAssemblyName(assemblyName); + + string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); + if (assemblyPath != null) + { + AssemblyLoadedPath = assemblyPath; + + // Load in memory to prevent locking the file + using var assemblyFile = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read); + string pdbPath = Path.ChangeExtension(assemblyPath, ".pdb"); + + if (File.Exists(pdbPath)) + { + using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read); + return LoadFromStream(assemblyFile, pdbFile); + } + + return LoadFromStream(assemblyFile); + } + + return null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (libraryPath != null) + return LoadUnmanagedDllFromPath(libraryPath); + + return IntPtr.Zero; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp.sln b/modules/mono/glue/GodotSharp/GodotSharp.sln index 4896d0a07d..8db42c2d1a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp.sln +++ b/modules/mono/glue/GodotSharp/GodotSharp.sln @@ -4,6 +4,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharp", "GodotSharp\Go EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotSharpEditor", "GodotSharpEditor\GodotSharpEditor.csproj", "{8FBEC238-D944-4074-8548-B3B524305905}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GodotPlugins", "GodotPlugins\GodotPlugins.csproj", "{944B77DB-497B-47F5-A1E3-81C35E3E9D4E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.SourceGenerators.Internal", "Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj", "{7749662B-E30C-419A-B745-13852573360A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +22,13 @@ Global {8FBEC238-D944-4074-8548-B3B524305905}.Debug|Any CPU.Build.0 = Debug|Any CPU {8FBEC238-D944-4074-8548-B3B524305905}.Release|Any CPU.ActiveCfg = Release|Any CPU {8FBEC238-D944-4074-8548-B3B524305905}.Release|Any CPU.Build.0 = Release|Any CPU + {944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {944B77DB-497B-47F5-A1E3-81C35E3E9D4E}.Release|Any CPU.Build.0 = Release|Any CPU + {7749662B-E30C-419A-B745-13852573360A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7749662B-E30C-419A-B745-13852573360A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7749662B-E30C-419A-B745-13852573360A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7749662B-E30C-419A-B745-13852573360A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings b/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings new file mode 100644 index 0000000000..ba65b61e95 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp.sln.DotSettings @@ -0,0 +1,8 @@ +<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GC/@EntryIndexedValue">GC</s:String> + <s:Boolean x:Key="/Default/UserDictionary/Words/=alcs/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=gdnative/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=godotsharp/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=icall/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=quat/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/UserDictionary/Words/=vcall/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs index 850ae7fc3b..f87f37bc43 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/AABB.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs index a412047196..81991c6626 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Array.cs @@ -1,47 +1,35 @@ using System; using System.Collections.Generic; using System.Collections; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot.Collections { - internal class ArraySafeHandle : SafeHandle - { - public ArraySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) - { - this.handle = handle; - } - - public override bool IsInvalid - { - get { return handle == IntPtr.Zero; } - } - - protected override bool ReleaseHandle() - { - Array.godot_icall_Array_Dtor(handle); - return true; - } - } - /// <summary> /// Wrapper around Godot's Array class, an array of Variant /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. Otherwise prefer .NET collections /// such as <see cref="System.Array"/> or <see cref="List{T}"/>. /// </summary> - public class Array : IList, IDisposable + public sealed class Array : + IList<Variant>, + IReadOnlyList<Variant>, + ICollection, + IDisposable { - private ArraySafeHandle _safeHandle; - private bool _disposed = false; + internal godot_array.movable NativeValue; + + private WeakReference<IDisposable> _weakReferenceToSelf; /// <summary> /// Constructs a new empty <see cref="Array"/>. /// </summary> public Array() { - _safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor()); + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } /// <summary> @@ -49,12 +37,12 @@ namespace Godot.Collections /// </summary> /// <param name="collection">The collection of elements to construct from.</param> /// <returns>A new Godot Array.</returns> - public Array(IEnumerable collection) : this() + public Array(IEnumerable<Variant> collection) : this() { if (collection == null) - throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'"); + throw new ArgumentNullException(nameof(collection)); - foreach (object element in collection) + foreach (Variant element in collection) Add(element); } @@ -63,31 +51,126 @@ namespace Godot.Collections /// </summary> /// <param name="array">The objects to put in the new array.</param> /// <returns>A new Godot Array.</returns> - public Array(params object[] array) : this() + public Array(Variant[] array) : this() { if (array == null) - { - throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); - } - _safeHandle = new ArraySafeHandle(godot_icall_Array_Ctor_MonoArray(array)); + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; } - internal Array(ArraySafeHandle handle) + public Array(Span<StringName> array) : this() { - _safeHandle = handle; + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; } - internal Array(IntPtr handle) + public Array(Span<NodePath> array) : this() { - _safeHandle = new ArraySafeHandle(handle); + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; + } + + public Array(Span<RID> array) : this() + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; + } + + // We must use ReadOnlySpan instead of Span here as this can accept implicit conversions + // from derived types (e.g.: Node[]). Implicit conversion from Derived[] to Base[] are + // fine as long as the array is not mutated. However, Span does this type checking at + // instantiation, so it's not possible to use it even when not mutating anything. + // ReSharper disable once RedundantNameQualifier + public Array(ReadOnlySpan<Godot.Object> array) : this() + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + NativeValue = (godot_array.movable)NativeFuncs.godotsharp_array_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + + int length = array.Length; + + Resize(length); + + for (int i = 0; i < length; i++) + this[i] = array[i]; } - internal IntPtr GetPtr() + private Array(godot_array nativeValueToOwn) { - if (_disposed) - throw new ObjectDisposedException(GetType().FullName); + NativeValue = (godot_array.movable)(nativeValueToOwn.IsAllocated ? + nativeValueToOwn : + NativeFuncs.godotsharp_array_new()); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } + + // Explicit name to make it very clear + internal static Array CreateTakingOwnershipOfDisposableValue(godot_array nativeValueToOwn) + => new Array(nativeValueToOwn); - return _safeHandle.DangerousGetHandle(); + ~Array() + { + Dispose(false); + } + + /// <summary> + /// Disposes of this <see cref="Array"/>. + /// </summary> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + // Always dispose `NativeValue` even if disposing is true + NativeValue.DangerousSelfRef.Dispose(); + + if (_weakReferenceToSelf != null) + { + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); + } } /// <summary> @@ -97,7 +180,10 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array Duplicate(bool deep = false) { - return new Array(godot_icall_Array_Duplicate(GetPtr(), deep)); + godot_array newArray; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_duplicate(ref self, deep.ToGodotBool(), out newArray); + return CreateTakingOwnershipOfDisposableValue(newArray); } /// <summary> @@ -107,7 +193,8 @@ namespace Godot.Collections /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns> public Error Resize(int newSize) { - return godot_icall_Array_Resize(GetPtr(), newSize); + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_resize(ref self, newSize); } /// <summary> @@ -115,7 +202,8 @@ namespace Godot.Collections /// </summary> public void Shuffle() { - godot_icall_Array_Shuffle(GetPtr()); + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_shuffle(ref self); } /// <summary> @@ -126,94 +214,136 @@ namespace Godot.Collections /// <returns>A new Godot Array with the contents of both arrays.</returns> public static Array operator +(Array left, Array right) { - return new Array(godot_icall_Array_Concatenate(left.GetPtr(), right.GetPtr())); - } - - // IDisposable - - /// <summary> - /// Disposes of this <see cref="Array"/>. - /// </summary> - public void Dispose() - { - if (_disposed) - return; - - if (_safeHandle != null) + if (left == null) { - _safeHandle.Dispose(); - _safeHandle = null; + if (right == null) + return new Array(); + + return right.Duplicate(deep: false); } - _disposed = true; - } + if (right == null) + return left.Duplicate(deep: false); + + int leftCount = left.Count; + int rightCount = right.Count; - // IList + Array newArray = left.Duplicate(deep: false); + newArray.Resize(leftCount + rightCount); - bool IList.IsReadOnly => false; + for (int i = 0; i < rightCount; i++) + newArray[i + leftCount] = right[i]; - bool IList.IsFixedSize => false; + return newArray; + } /// <summary> - /// Returns the object at the given <paramref name="index"/>. + /// Returns the item at the given <paramref name="index"/>. /// </summary> - /// <value>The object at the given <paramref name="index"/>.</value> - public object this[int index] + /// <value>The <see cref="Variant"/> item at the given <paramref name="index"/>.</value> + public unsafe Variant this[int index] { - get => godot_icall_Array_At(GetPtr(), index); - set => godot_icall_Array_SetAt(GetPtr(), index, value); + get + { + GetVariantBorrowElementAt(index, out godot_variant borrowElem); + return Variant.CreateCopyingBorrowed(borrowElem); + } + set + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + var self = (godot_array)NativeValue; + godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); + godot_variant* itemPtr = &ptrw[index]; + (*itemPtr).Dispose(); + *itemPtr = value.CopyNativeVariant(); + } } /// <summary> - /// Adds an object to the end of this <see cref="Array"/>. + /// Adds an item to the end of this <see cref="Array"/>. /// This is the same as <c>append</c> or <c>push_back</c> in GDScript. /// </summary> - /// <param name="value">The object to add.</param> - /// <returns>The new size after adding the object.</returns> - public int Add(object value) => godot_icall_Array_Add(GetPtr(), value); + /// <param name="item">The <see cref="Variant"/> item to add.</param> + public void Add(Variant item) + { + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + _ = NativeFuncs.godotsharp_array_add(ref self, variantValue); + } /// <summary> - /// Checks if this <see cref="Array"/> contains the given object. + /// Checks if this <see cref="Array"/> contains the given item. /// </summary> - /// <param name="value">The item to look for.</param> - /// <returns>Whether or not this array contains the given object.</returns> - public bool Contains(object value) => godot_icall_Array_Contains(GetPtr(), value); + /// <param name="item">The <see cref="Variant"/> item to look for.</param> + /// <returns>Whether or not this array contains the given item.</returns> + public bool Contains(Variant item) => IndexOf(item) != -1; /// <summary> /// Erases all items from this <see cref="Array"/>. /// </summary> - public void Clear() => godot_icall_Array_Clear(GetPtr()); + public void Clear() => Resize(0); /// <summary> - /// Searches this <see cref="Array"/> for an object + /// Searches this <see cref="Array"/> for an item /// and returns its index or -1 if not found. /// </summary> - /// <param name="value">The object to search for.</param> - /// <returns>The index of the object, or -1 if not found.</returns> - public int IndexOf(object value) => godot_icall_Array_IndexOf(GetPtr(), value); + /// <param name="item">The <see cref="Variant"/> item to search for.</param> + /// <returns>The index of the item, or -1 if not found.</returns> + public int IndexOf(Variant item) + { + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); + } /// <summary> - /// Inserts a new object at a given position in the array. + /// Inserts a new item at a given position in the array. /// The position must be a valid position of an existing item, /// or the position at the end of the array. /// Existing items will be moved to the right. /// </summary> /// <param name="index">The index to insert at.</param> - /// <param name="value">The object to insert.</param> - public void Insert(int index, object value) => godot_icall_Array_Insert(GetPtr(), index, value); + /// <param name="item">The <see cref="Variant"/> item to insert.</param> + public void Insert(int index, Variant item) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + godot_variant variantValue = (godot_variant)item.NativeVar; + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_insert(ref self, index, variantValue); + } /// <summary> - /// Removes the first occurrence of the specified <paramref name="value"/> + /// Removes the first occurrence of the specified <paramref name="item"/> /// from this <see cref="Array"/>. /// </summary> - /// <param name="value">The value to remove.</param> - public void Remove(object value) => godot_icall_Array_Remove(GetPtr(), value); + /// <param name="item">The value to remove.</param> + public bool Remove(Variant item) + { + int index = IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; + } /// <summary> /// Removes an element from this <see cref="Array"/> by index. /// </summary> /// <param name="index">The index of the element to remove.</param> - public void RemoveAt(int index) => godot_icall_Array_RemoveAt(GetPtr(), index); + public void RemoveAt(int index) + { + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_remove_at(ref self, index); + } // ICollection @@ -222,28 +352,77 @@ namespace Godot.Collections /// This is also known as the size or length of the array. /// </summary> /// <returns>The number of elements.</returns> - public int Count => godot_icall_Array_Count(GetPtr()); - - object ICollection.SyncRoot => this; + public int Count => NativeValue.DangerousSelfRef.Size; bool ICollection.IsSynchronized => false; + object ICollection.SyncRoot => false; + + bool ICollection<Variant>.IsReadOnly => false; + /// <summary> /// Copies the elements of this <see cref="Array"/> to the given - /// untyped C# array, starting at the given index. + /// <see cref="Variant"/> C# array, starting at the given index. /// </summary> /// <param name="array">The array to copy to.</param> - /// <param name="index">The index to start at.</param> - public void CopyTo(System.Array array, int index) + /// <param name="arrayIndex">The index to start at.</param> + public void CopyTo(Variant[] array, int arrayIndex) + { + if (array == null) + throw new ArgumentNullException(nameof(array), "Value cannot be null."); + + if (arrayIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); + } + + int count = Count; + + if (array.Length < (arrayIndex + count)) + { + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + } + + unsafe + { + for (int i = 0; i < count; i++) + { + array[arrayIndex] = Variant.CreateCopyingBorrowed(NativeValue.DangerousSelfRef.Elements[i]); + arrayIndex++; + } + } + } + + void ICollection.CopyTo(System.Array array, int index) { if (array == null) throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index), "Number was less than the array's lower bound in the first dimension."); + { + throw new ArgumentOutOfRangeException(nameof(index), + "Number was less than the array's lower bound in the first dimension."); + } + + int count = Count; - // Internal call may throw ArgumentException - godot_icall_Array_CopyTo(GetPtr(), array, index); + if (array.Length < (index + count)) + { + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + } + + unsafe + { + for (int i = 0; i < count; i++) + { + object obj = Marshaling.ConvertVariantToManagedObject(NativeValue.DangerousSelfRef.Elements[i]); + array.SetValue(obj, index); + index++; + } + } } // IEnumerable @@ -252,7 +431,7 @@ namespace Godot.Collections /// Gets an enumerator for this <see cref="Array"/>. /// </summary> /// <returns>An enumerator.</returns> - public IEnumerator GetEnumerator() + public IEnumerator<Variant> GetEnumerator() { int count = Count; @@ -262,77 +441,37 @@ namespace Godot.Collections } } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// <summary> /// Converts this <see cref="Array"/> to a string. /// </summary> /// <returns>A string representation of this array.</returns> public override string ToString() { - return godot_icall_Array_ToString(GetPtr()); + var self = (godot_array)NativeValue; + NativeFuncs.godotsharp_array_to_string(ref self, out godot_string str); + using (str) + return Marshaling.ConvertStringToManaged(str); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Array_Ctor(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Array_Ctor_MonoArray(System.Array array); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_Dtor(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_Array_At(IntPtr ptr, int index); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_Array_At_Generic(IntPtr ptr, int index, int elemTypeEncoding, IntPtr elemTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_SetAt(IntPtr ptr, int index, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Array_Count(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Array_Add(IntPtr ptr, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_Clear(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Array_Concatenate(IntPtr left, IntPtr right); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Array_Contains(IntPtr ptr, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_CopyTo(IntPtr ptr, System.Array array, int arrayIndex); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Array_Duplicate(IntPtr ptr, bool deep); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Array_IndexOf(IntPtr ptr, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_Insert(IntPtr ptr, int index, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Array_Remove(IntPtr ptr, object item); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_RemoveAt(IntPtr ptr, int index); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Error godot_icall_Array_Resize(IntPtr ptr, int newSize); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Error godot_icall_Array_Shuffle(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Array_Generic_GetElementTypeInfo(Type elemType, out int elemTypeEncoding, out IntPtr elemTypeClass); + /// <summary> + /// The variant returned via the <paramref name="elem"/> parameter is owned by the Array and must not be disposed. + /// </summary> + internal void GetVariantBorrowElementAt(int index, out godot_variant elem) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + GetVariantBorrowElementAtUnchecked(index, out elem); + } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_Array_ToString(IntPtr ptr); + /// <summary> + /// The variant returned via the <paramref name="elem"/> parameter is owned by the Array and must not be disposed. + /// </summary> + internal unsafe void GetVariantBorrowElementAtUnchecked(int index, out godot_variant elem) + { + elem = NativeValue.DangerousSelfRef.Elements[index]; + } } /// <summary> @@ -342,16 +481,45 @@ namespace Godot.Collections /// such as arrays or <see cref="List{T}"/>. /// </summary> /// <typeparam name="T">The type of the array.</typeparam> - public class Array<T> : IList<T>, ICollection<T>, IEnumerable<T> + [SuppressMessage("ReSharper", "RedundantExtendsListEntry")] + [SuppressMessage("Naming", "CA1710", MessageId = "Identifiers should have correct suffix")] + public sealed class Array<T> : + IList<T>, + IReadOnlyList<T>, + ICollection<T>, + IEnumerable<T> { - private Array _objectArray; + // ReSharper disable StaticMemberInGenericType + // Warning is about unique static fields being created for each generic type combination: + // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html + // In our case this is exactly what we want. - internal static int elemTypeEncoding; - internal static IntPtr elemTypeClass; + private static unsafe delegate* managed<in T, godot_variant> _convertToVariantCallback; + private static unsafe delegate* managed<in godot_variant, T> _convertToManagedCallback; - static Array() + // ReSharper restore StaticMemberInGenericType + + static unsafe Array() { - Array.godot_icall_Array_Generic_GetElementTypeInfo(typeof(T), out elemTypeEncoding, out elemTypeClass); + _convertToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<T>(); + _convertToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<T>(); + } + + private static unsafe void ValidateVariantConversionCallbacks() + { + if (_convertToVariantCallback == null || _convertToManagedCallback == null) + { + throw new InvalidOperationException( + $"The array element type is not supported for conversion to Variant: '{typeof(T).FullName}'"); + } + } + + private readonly Array _underlyingArray; + + internal ref godot_array.movable NativeValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _underlyingArray.NativeValue; } /// <summary> @@ -359,7 +527,9 @@ namespace Godot.Collections /// </summary> public Array() { - _objectArray = new Array(); + ValidateVariantConversionCallbacks(); + + _underlyingArray = new Array(); } /// <summary> @@ -369,10 +539,15 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array(IEnumerable<T> collection) { + ValidateVariantConversionCallbacks(); + if (collection == null) - throw new NullReferenceException($"Parameter '{nameof(collection)} cannot be null.'"); + throw new ArgumentNullException(nameof(collection)); + + _underlyingArray = new Array(); - _objectArray = new Array(collection); + foreach (T element in collection) + Add(element); } /// <summary> @@ -380,13 +555,17 @@ namespace Godot.Collections /// </summary> /// <param name="array">The items to put in the new array.</param> /// <returns>A new Godot Array.</returns> - public Array(params T[] array) : this() + public Array(T[] array) : this() { + ValidateVariantConversionCallbacks(); + if (array == null) - { - throw new NullReferenceException($"Parameter '{nameof(array)} cannot be null.'"); - } - _objectArray = new Array(array); + throw new ArgumentNullException(nameof(array)); + + _underlyingArray = new Array(); + + foreach (T element in array) + Add(element); } /// <summary> @@ -395,23 +574,14 @@ namespace Godot.Collections /// <param name="array">The untyped array to construct from.</param> public Array(Array array) { - _objectArray = array; - } + ValidateVariantConversionCallbacks(); - internal Array(IntPtr handle) - { - _objectArray = new Array(handle); + _underlyingArray = array; } - internal Array(ArraySafeHandle handle) - { - _objectArray = new Array(handle); - } - - internal IntPtr GetPtr() - { - return _objectArray.GetPtr(); - } + // Explicit name to make it very clear + internal static Array<T> CreateTakingOwnershipOfDisposableValue(godot_array nativeValueToOwn) + => new Array<T>(Array.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn)); /// <summary> /// Converts this typed <see cref="Array{T}"/> to an untyped <see cref="Array"/>. @@ -419,7 +589,7 @@ namespace Godot.Collections /// <param name="from">The typed array to convert.</param> public static explicit operator Array(Array<T> from) { - return from._objectArray; + return from?._underlyingArray; } /// <summary> @@ -429,7 +599,7 @@ namespace Godot.Collections /// <returns>A new Godot Array.</returns> public Array<T> Duplicate(bool deep = false) { - return new Array<T>(_objectArray.Duplicate(deep)); + return new Array<T>(_underlyingArray.Duplicate(deep)); } /// <summary> @@ -439,7 +609,7 @@ namespace Godot.Collections /// <returns><see cref="Error.Ok"/> if successful, or an error code.</returns> public Error Resize(int newSize) { - return _objectArray.Resize(newSize); + return _underlyingArray.Resize(newSize); } /// <summary> @@ -447,7 +617,7 @@ namespace Godot.Collections /// </summary> public void Shuffle() { - _objectArray.Shuffle(); + _underlyingArray.Shuffle(); } /// <summary> @@ -458,7 +628,18 @@ namespace Godot.Collections /// <returns>A new Godot Array with the contents of both arrays.</returns> public static Array<T> operator +(Array<T> left, Array<T> right) { - return new Array<T>(left._objectArray + right._objectArray); + if (left == null) + { + if (right == null) + return new Array<T>(); + + return right.Duplicate(deep: false); + } + + if (right == null) + return left.Duplicate(deep: false); + + return new Array<T>(left._underlyingArray + right._underlyingArray); } // IList<T> @@ -467,10 +648,23 @@ namespace Godot.Collections /// Returns the value at the given <paramref name="index"/>. /// </summary> /// <value>The value at the given <paramref name="index"/>.</value> - public T this[int index] + public unsafe T this[int index] { - get { return (T)Array.godot_icall_Array_At_Generic(GetPtr(), index, elemTypeEncoding, elemTypeClass); } - set { _objectArray[index] = value; } + get + { + _underlyingArray.GetVariantBorrowElementAt(index, out godot_variant borrowElem); + return _convertToManagedCallback(borrowElem); + } + set + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + var self = (godot_array)_underlyingArray.NativeValue; + godot_variant* ptrw = NativeFuncs.godotsharp_array_ptrw(ref self); + godot_variant* itemPtr = &ptrw[index]; + (*itemPtr).Dispose(); + *itemPtr = _convertToVariantCallback(value); + } } /// <summary> @@ -479,9 +673,11 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to search for.</param> /// <returns>The index of the item, or -1 if not found.</returns> - public int IndexOf(T item) + public unsafe int IndexOf(T item) { - return _objectArray.IndexOf(item); + using var variantValue = _convertToVariantCallback(item); + var self = (godot_array)_underlyingArray.NativeValue; + return NativeFuncs.godotsharp_array_index_of(ref self, variantValue); } /// <summary> @@ -492,9 +688,14 @@ namespace Godot.Collections /// </summary> /// <param name="index">The index to insert at.</param> /// <param name="item">The item to insert.</param> - public void Insert(int index, T item) + public unsafe void Insert(int index, T item) { - _objectArray.Insert(index, item); + if (index < 0 || index > Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + using var variantValue = _convertToVariantCallback(item); + var self = (godot_array)_underlyingArray.NativeValue; + NativeFuncs.godotsharp_array_insert(ref self, index, variantValue); } /// <summary> @@ -503,7 +704,7 @@ namespace Godot.Collections /// <param name="index">The index of the element to remove.</param> public void RemoveAt(int index) { - _objectArray.RemoveAt(index); + _underlyingArray.RemoveAt(index); } // ICollection<T> @@ -513,10 +714,7 @@ namespace Godot.Collections /// This is also known as the size or length of the array. /// </summary> /// <returns>The number of elements.</returns> - public int Count - { - get { return _objectArray.Count; } - } + public int Count => _underlyingArray.Count; bool ICollection<T>.IsReadOnly => false; @@ -526,9 +724,11 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to add.</param> /// <returns>The new size after adding the item.</returns> - public void Add(T item) + public unsafe void Add(T item) { - _objectArray.Add(item); + using var variantValue = _convertToVariantCallback(item); + var self = (godot_array)_underlyingArray.NativeValue; + _ = NativeFuncs.godotsharp_array_add(ref self, variantValue); } /// <summary> @@ -536,7 +736,7 @@ namespace Godot.Collections /// </summary> public void Clear() { - _objectArray.Clear(); + _underlyingArray.Clear(); } /// <summary> @@ -544,10 +744,7 @@ namespace Godot.Collections /// </summary> /// <param name="item">The item to look for.</param> /// <returns>Whether or not this array contains the given item.</returns> - public bool Contains(T item) - { - return _objectArray.Contains(item); - } + public bool Contains(T item) => IndexOf(item) != -1; /// <summary> /// Copies the elements of this <see cref="Array{T}"/> to the given @@ -561,19 +758,22 @@ namespace Godot.Collections throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); - - // TODO This may be quite slow because every element access is an internal call. - // It could be moved entirely to an internal call if we find out how to do the cast there. + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); + } - int count = _objectArray.Count; + int count = Count; if (array.Length < (arrayIndex + count)) - throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + { + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + } for (int i = 0; i < count; i++) { - array[arrayIndex] = (T)this[i]; + array[arrayIndex] = this[i]; arrayIndex++; } } @@ -586,7 +786,14 @@ namespace Godot.Collections /// <returns>A <see langword="bool"/> indicating success or failure.</returns> public bool Remove(T item) { - return Array.godot_icall_Array_Remove(GetPtr(), item); + int index = IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + + return false; } // IEnumerable<T> @@ -597,23 +804,26 @@ namespace Godot.Collections /// <returns>An enumerator.</returns> public IEnumerator<T> GetEnumerator() { - int count = _objectArray.Count; + int count = _underlyingArray.Count; for (int i = 0; i < count; i++) { - yield return (T)this[i]; + yield return this[i]; } } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// <summary> /// Converts this <see cref="Array{T}"/> to a string. /// </summary> /// <returns>A string representation of this array.</returns> - public override string ToString() => _objectArray.ToString(); + public override string ToString() => _underlyingArray.ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Array<T> from) => Variant.CreateFrom(from); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Array<T>(Variant from) => from.AsGodotArray<T>(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs index 2febf37f05..b7d633517a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/AssemblyHasScriptsAttribute.cs @@ -1,5 +1,7 @@ using System; +#nullable enable + namespace Godot { /// <summary> @@ -8,25 +10,28 @@ namespace Godot [AttributeUsage(AttributeTargets.Assembly)] public class AssemblyHasScriptsAttribute : Attribute { - private readonly bool requiresLookup; - private readonly System.Type[] scriptTypes; + public bool RequiresLookup { get; } + public Type[]? ScriptTypes { get; } /// <summary> /// Constructs a new AssemblyHasScriptsAttribute instance. /// </summary> public AssemblyHasScriptsAttribute() { - requiresLookup = true; + RequiresLookup = true; + ScriptTypes = null; } /// <summary> /// Constructs a new AssemblyHasScriptsAttribute instance. /// </summary> /// <param name="scriptTypes">The specified type(s) of scripts.</param> - public AssemblyHasScriptsAttribute(System.Type[] scriptTypes) + public AssemblyHasScriptsAttribute(Type[] scriptTypes) { - requiresLookup = false; - this.scriptTypes = scriptTypes; + RequiresLookup = false; + ScriptTypes = scriptTypes; } } } + +#nullable restore diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs deleted file mode 100644 index 0b00878e8c..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Godot -{ - /// <summary> - /// An attribute that disables Godot Generators. - /// </summary> - [AttributeUsage(AttributeTargets.Class)] - public class DisableGodotGeneratorsAttribute : Attribute { } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs index 46eb128d37..3d204bdf9f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs @@ -6,7 +6,7 @@ namespace Godot /// An attribute used to export objects. /// </summary> [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public class ExportAttribute : Attribute + public sealed class ExportAttribute : Attribute { private PropertyHint hint; private string hintString; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs deleted file mode 100644 index 8d4ff0fdb7..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/GodotMethodAttribute.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Godot -{ - /// <summary> - /// An attribute for a method. - /// </summary> - [AttributeUsage(AttributeTargets.Method)] - internal class GodotMethodAttribute : Attribute - { - private string methodName; - - public string MethodName { get { return methodName; } } - - /// <summary> - /// Constructs a new GodotMethodAttribute instance. - /// </summary> - /// <param name="methodName">The name of the method.</param> - public GodotMethodAttribute(string methodName) - { - this.methodName = methodName; - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs index 3ebb6612de..2c8a53ae1c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ScriptPathAttribute.cs @@ -8,7 +8,7 @@ namespace Godot [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class ScriptPathAttribute : Attribute { - private string path; + public string Path { get; } /// <summary> /// Constructs a new ScriptPathAttribute instance. @@ -16,7 +16,7 @@ namespace Godot /// <param name="path">The file path to the script</param> public ScriptPathAttribute(string path) { - this.path = path; + Path = path; } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs index 437878818c..646681a9b1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -167,7 +162,7 @@ namespace Godot case 2: return Column2; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } set @@ -184,7 +179,7 @@ namespace Godot Column2 = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } } @@ -386,7 +381,7 @@ namespace Godot case 2: return Row2; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } @@ -413,7 +408,7 @@ namespace Godot Row2 = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs new file mode 100644 index 0000000000..ac2e2fae3c --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/AlcReloadCfg.cs @@ -0,0 +1,18 @@ +namespace Godot.Bridge; + +public static class AlcReloadCfg +{ + private static bool _configured = false; + + public static void Configure(bool alcReloadEnabled) + { + if (_configured) + return; + + _configured = true; + + IsAlcReloadingEnabled = alcReloadEnabled; + } + + internal static bool IsAlcReloadingEnabled = false; +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs new file mode 100644 index 0000000000..ae44f8f4ba --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs @@ -0,0 +1,253 @@ +using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + internal static class CSharpInstanceBridge + { + [UnmanagedCallersOnly] + internal static unsafe godot_bool Call(IntPtr godotObjectGCHandle, godot_string_name* method, + godot_variant** args, int argCount, godot_variant_call_error* refCallError, godot_variant* ret) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + { + *ret = default; + (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL; + return godot_bool.False; + } + + bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method), + new NativeVariantPtrArgs(args), + argCount, out godot_variant retValue); + + if (!methodInvoked) + { + *ret = default; + // This is important, as it tells Object::call that no method was called. + // Otherwise, it would prevent Object::call from calling native methods. + (*refCallError).Error = godot_variant_call_error_error.GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD; + return godot_bool.False; + } + + *ret = retValue; + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *ret = default; + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool Set(IntPtr godotObjectGCHandle, godot_string_name* name, godot_variant* value) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + throw new InvalidOperationException(); + + if (godotObject.SetGodotClassPropertyValue(CustomUnsafe.AsRef(name), CustomUnsafe.AsRef(value))) + { + return godot_bool.True; + } + + var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name))); + + Variant valueManaged = Variant.CreateCopyingBorrowed(*value); + + return godotObject._Set(nameManaged, valueManaged).ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool Get(IntPtr godotObjectGCHandle, godot_string_name* name, + godot_variant* outRet) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + throw new InvalidOperationException(); + + if (godotObject.GetGodotClassPropertyValue(CustomUnsafe.AsRef(name), out godot_variant outRetValue)) + { + *outRet = outRetValue; + return godot_bool.True; + } + + var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name))); + + Variant ret = godotObject._Get(nameManaged); + + if (ret.VariantType == Variant.Type.Nil) + { + *outRet = default; + return godot_bool.False; + } + + *outRet = Marshaling.ConvertManagedObjectToVariant(ret); + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outRet = default; + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static void CallDispose(IntPtr godotObjectGCHandle, godot_bool okIfNull) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (okIfNull.ToBool()) + godotObject?.Dispose(); + else + godotObject!.Dispose(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe void CallToString(IntPtr godotObjectGCHandle, godot_string* outRes, godot_bool* outValid) + { + try + { + var self = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (self == null) + { + *outRes = default; + *outValid = godot_bool.False; + return; + } + + var resultStr = self.ToString(); + + if (resultStr == null) + { + *outRes = default; + *outValid = godot_bool.False; + return; + } + + *outRes = Marshaling.ConvertStringToNative(resultStr); + *outValid = godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outRes = default; + *outValid = godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool HasMethodUnknownParams(IntPtr godotObjectGCHandle, godot_string_name* method) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + return godot_bool.False; + + return godotObject.HasGodotClassMethod(CustomUnsafe.AsRef(method)).ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void SerializeState( + IntPtr godotObjectGCHandle, + godot_dictionary* propertiesState, + godot_dictionary* signalEventsState + ) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + return; + + // Call OnBeforeSerialize + + // ReSharper disable once SuspiciousTypeConversion.Global + if (godotObject is ISerializationListener serializationListener) + serializationListener.OnBeforeSerialize(); + + // Save instance state + + using var info = GodotSerializationInfo.CreateCopyingBorrowed( + *propertiesState, *signalEventsState); + + godotObject.SaveGodotObjectData(info); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe void DeserializeState( + IntPtr godotObjectGCHandle, + godot_dictionary* propertiesState, + godot_dictionary* signalEventsState + ) + { + try + { + var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; + + if (godotObject == null) + return; + + // Restore instance state + + using var info = GodotSerializationInfo.CreateCopyingBorrowed( + *propertiesState, *signalEventsState); + + godotObject.RestoreGodotObjectData(info); + + // Call OnAfterDeserialize + + // ReSharper disable once SuspiciousTypeConversion.Global + if (godotObject is ISerializationListener serializationListener) + serializationListener.OnAfterDeserialize(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs new file mode 100644 index 0000000000..456a118b90 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GCHandleBridge.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + internal static class GCHandleBridge + { + [UnmanagedCallersOnly] + internal static void FreeGCHandle(IntPtr gcHandlePtr) + { + try + { + CustomGCHandle.Free(GCHandle.FromIntPtr(gcHandlePtr)); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs new file mode 100644 index 0000000000..8f26967dcd --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/GodotSerializationInfo.cs @@ -0,0 +1,87 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Godot.NativeInterop; + +namespace Godot.Bridge; + +public class GodotSerializationInfo : IDisposable +{ + private readonly Collections.Dictionary _properties; + private readonly Collections.Dictionary _signalEvents; + + public void Dispose() + { + _properties?.Dispose(); + _signalEvents?.Dispose(); + + GC.SuppressFinalize(this); + } + + private GodotSerializationInfo(in godot_dictionary properties, in godot_dictionary signalEvents) + { + _properties = Collections.Dictionary.CreateTakingOwnershipOfDisposableValue(properties); + _signalEvents = Collections.Dictionary.CreateTakingOwnershipOfDisposableValue(signalEvents); + } + + internal static GodotSerializationInfo CreateCopyingBorrowed( + in godot_dictionary properties, in godot_dictionary signalEvents) + { + return new(NativeFuncs.godotsharp_dictionary_new_copy(properties), + NativeFuncs.godotsharp_dictionary_new_copy(signalEvents)); + } + + public void AddProperty(StringName name, Variant value) + { + _properties[name] = value; + } + + public bool TryGetProperty(StringName name, out Variant value) + { + return _properties.TryGetValue(name, out value); + } + + public void AddSignalEventDelegate(StringName name, Delegate eventDelegate) + { + var serializedData = new Collections.Array(); + + if (DelegateUtils.TrySerializeDelegate(eventDelegate, serializedData)) + { + _signalEvents[name] = serializedData; + } + else if (OS.IsStdoutVerbose()) + { + Console.WriteLine($"Failed to serialize event signal delegate: {name}"); + } + } + + public bool TryGetSignalEventDelegate<T>(StringName name, [MaybeNullWhen(false)] out T value) + where T : Delegate + { + if (_signalEvents.TryGetValue(name, out Variant serializedData)) + { + if (DelegateUtils.TryDeserializeDelegate(serializedData.AsGodotArray(), out var eventDelegate)) + { + value = eventDelegate as T; + + if (value == null) + { + Console.WriteLine($"Cannot cast the deserialized event signal delegate: {name}. " + + $"Expected '{typeof(T).FullName}'; got '{eventDelegate.GetType().FullName}'."); + return false; + } + + return true; + } + else if (OS.IsStdoutVerbose()) + { + Console.WriteLine($"Failed to deserialize event signal delegate: {name}"); + } + + value = null; + return false; + } + + value = null; + return false; + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs new file mode 100644 index 0000000000..57240624bc --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs @@ -0,0 +1,89 @@ +using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe struct ManagedCallbacks + { + // @formatter:off + public delegate* unmanaged<IntPtr, godot_variant**, int, godot_bool*, void> SignalAwaiter_SignalCallback; + public delegate* unmanaged<IntPtr, godot_variant**, uint, godot_variant*, void> DelegateUtils_InvokeWithVariantArgs; + public delegate* unmanaged<IntPtr, IntPtr, godot_bool> DelegateUtils_DelegateEquals; + public delegate* unmanaged<IntPtr, godot_array*, godot_bool> DelegateUtils_TrySerializeDelegateWithGCHandle; + public delegate* unmanaged<godot_array*, IntPtr*, godot_bool> DelegateUtils_TryDeserializeDelegateWithGCHandle; + public delegate* unmanaged<void> ScriptManagerBridge_FrameCallback; + public delegate* unmanaged<godot_string_name*, IntPtr, IntPtr> ScriptManagerBridge_CreateManagedForGodotObjectBinding; + public delegate* unmanaged<IntPtr, IntPtr, godot_variant**, int, godot_bool> ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance; + public delegate* unmanaged<IntPtr, godot_string_name*, void> ScriptManagerBridge_GetScriptNativeName; + public delegate* unmanaged<IntPtr, IntPtr, void> ScriptManagerBridge_SetGodotObjectPtr; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_bool*, void> ScriptManagerBridge_RaiseEventSignal; + public delegate* unmanaged<IntPtr, IntPtr, godot_bool> ScriptManagerBridge_ScriptIsOrInherits; + public delegate* unmanaged<IntPtr, godot_string*, godot_bool> ScriptManagerBridge_AddScriptBridge; + public delegate* unmanaged<godot_string*, godot_ref*, void> ScriptManagerBridge_GetOrCreateScriptBridgeForPath; + public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge; + public delegate* unmanaged<IntPtr, godot_bool> ScriptManagerBridge_TryReloadRegisteredScriptWithClass; + public delegate* unmanaged<IntPtr, godot_bool*, godot_array*, godot_dictionary*, godot_dictionary*, godot_ref*, void> ScriptManagerBridge_UpdateScriptClassInfo; + public delegate* unmanaged<IntPtr, IntPtr*, godot_bool, godot_bool> ScriptManagerBridge_SwapGCHandleForType; + public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, godot_string*, void*, int, void>, void> ScriptManagerBridge_GetPropertyInfoList; + public delegate* unmanaged<IntPtr, delegate* unmanaged<IntPtr, void*, int, void>, void> ScriptManagerBridge_GetPropertyDefaultValues; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant**, int, godot_variant_call_error*, godot_variant*, godot_bool> CSharpInstanceBridge_Call; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Set; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_variant*, godot_bool> CSharpInstanceBridge_Get; + public delegate* unmanaged<IntPtr, godot_bool, void> CSharpInstanceBridge_CallDispose; + public delegate* unmanaged<IntPtr, godot_string*, godot_bool*, void> CSharpInstanceBridge_CallToString; + public delegate* unmanaged<IntPtr, godot_string_name*, godot_bool> CSharpInstanceBridge_HasMethodUnknownParams; + public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_SerializeState; + public delegate* unmanaged<IntPtr, godot_dictionary*, godot_dictionary*, void> CSharpInstanceBridge_DeserializeState; + public delegate* unmanaged<IntPtr, void> GCHandleBridge_FreeGCHandle; + public delegate* unmanaged<void*, void> DebuggingUtils_GetCurrentStackInfo; + public delegate* unmanaged<void> DisposablesTracker_OnGodotShuttingDown; + public delegate* unmanaged<godot_bool, void> GD_OnCoreApiAssemblyLoaded; + // @formatter:on + + public static ManagedCallbacks Create() + { + return new() + { + // @formatter:off + SignalAwaiter_SignalCallback = &SignalAwaiter.SignalCallback, + DelegateUtils_InvokeWithVariantArgs = &DelegateUtils.InvokeWithVariantArgs, + DelegateUtils_DelegateEquals = &DelegateUtils.DelegateEquals, + DelegateUtils_TrySerializeDelegateWithGCHandle = &DelegateUtils.TrySerializeDelegateWithGCHandle, + DelegateUtils_TryDeserializeDelegateWithGCHandle = &DelegateUtils.TryDeserializeDelegateWithGCHandle, + ScriptManagerBridge_FrameCallback = &ScriptManagerBridge.FrameCallback, + ScriptManagerBridge_CreateManagedForGodotObjectBinding = &ScriptManagerBridge.CreateManagedForGodotObjectBinding, + ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = &ScriptManagerBridge.CreateManagedForGodotObjectScriptInstance, + ScriptManagerBridge_GetScriptNativeName = &ScriptManagerBridge.GetScriptNativeName, + ScriptManagerBridge_SetGodotObjectPtr = &ScriptManagerBridge.SetGodotObjectPtr, + ScriptManagerBridge_RaiseEventSignal = &ScriptManagerBridge.RaiseEventSignal, + ScriptManagerBridge_ScriptIsOrInherits = &ScriptManagerBridge.ScriptIsOrInherits, + ScriptManagerBridge_AddScriptBridge = &ScriptManagerBridge.AddScriptBridge, + ScriptManagerBridge_GetOrCreateScriptBridgeForPath = &ScriptManagerBridge.GetOrCreateScriptBridgeForPath, + ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge, + ScriptManagerBridge_TryReloadRegisteredScriptWithClass = &ScriptManagerBridge.TryReloadRegisteredScriptWithClass, + ScriptManagerBridge_UpdateScriptClassInfo = &ScriptManagerBridge.UpdateScriptClassInfo, + ScriptManagerBridge_SwapGCHandleForType = &ScriptManagerBridge.SwapGCHandleForType, + ScriptManagerBridge_GetPropertyInfoList = &ScriptManagerBridge.GetPropertyInfoList, + ScriptManagerBridge_GetPropertyDefaultValues = &ScriptManagerBridge.GetPropertyDefaultValues, + CSharpInstanceBridge_Call = &CSharpInstanceBridge.Call, + CSharpInstanceBridge_Set = &CSharpInstanceBridge.Set, + CSharpInstanceBridge_Get = &CSharpInstanceBridge.Get, + CSharpInstanceBridge_CallDispose = &CSharpInstanceBridge.CallDispose, + CSharpInstanceBridge_CallToString = &CSharpInstanceBridge.CallToString, + CSharpInstanceBridge_HasMethodUnknownParams = &CSharpInstanceBridge.HasMethodUnknownParams, + CSharpInstanceBridge_SerializeState = &CSharpInstanceBridge.SerializeState, + CSharpInstanceBridge_DeserializeState = &CSharpInstanceBridge.DeserializeState, + GCHandleBridge_FreeGCHandle = &GCHandleBridge.FreeGCHandle, + DebuggingUtils_GetCurrentStackInfo = &DebuggingUtils.GetCurrentStackInfo, + DisposablesTracker_OnGodotShuttingDown = &DisposablesTracker.OnGodotShuttingDown, + GD_OnCoreApiAssemblyLoaded = &GD.OnCoreApiAssemblyLoaded, + // @formatter:on + }; + } + + public static void Create(IntPtr outManagedCallbacks) + => *(ManagedCallbacks*)outManagedCallbacks = Create(); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs new file mode 100644 index 0000000000..647ae436ff --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/MethodInfo.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Godot.Bridge; + +#nullable enable + +public struct MethodInfo +{ + public StringName Name { get; init; } + public PropertyInfo ReturnVal { get; init; } + public MethodFlags Flags { get; init; } + public int Id { get; init; } = 0; + public List<PropertyInfo>? Arguments { get; init; } + public List<Variant>? DefaultArguments { get; init; } + + public MethodInfo(StringName name, PropertyInfo returnVal, MethodFlags flags, + List<PropertyInfo>? arguments, List<Variant>? defaultArguments) + { + Name = name; + ReturnVal = returnVal; + Flags = flags; + Arguments = arguments; + DefaultArguments = defaultArguments; + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs new file mode 100644 index 0000000000..80d6f7b4a5 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs @@ -0,0 +1,24 @@ +namespace Godot.Bridge; + +#nullable enable + +public struct PropertyInfo +{ + public Variant.Type Type { get; init; } + public StringName Name { get; init; } + public PropertyHint Hint { get; init; } + public string HintString { get; init; } + public PropertyUsageFlags Usage { get; init; } + public bool Exported { get; init; } + + public PropertyInfo(Variant.Type type, StringName name, PropertyHint hint, string hintString, + PropertyUsageFlags usage, bool exported) + { + Type = type; + Name = name; + Hint = hint; + HintString = hintString; + Usage = usage; + Exported = exported; + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs new file mode 100644 index 0000000000..0dc5ba7678 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs @@ -0,0 +1,1028 @@ +#nullable enable + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.Runtime.Serialization; +using Godot.NativeInterop; + +namespace Godot.Bridge +{ + // TODO: Make class internal once we replace LookupScriptsInAssembly (the only public member) with source generators + public static partial class ScriptManagerBridge + { + private static ConcurrentDictionary<AssemblyLoadContext, ConcurrentDictionary<Type, byte>> + _alcData = new(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void OnAlcUnloading(AssemblyLoadContext alc) + { + if (_alcData.TryRemove(alc, out var typesInAlc)) + { + foreach (var type in typesInAlc.Keys) + { + if (_scriptTypeBiMap.RemoveByScriptType(type, out IntPtr scriptPtr) && + !_pathTypeBiMap.TryGetScriptPath(type, out _)) + { + // For scripts without a path, we need to keep the class qualified name for reloading + _scriptDataForReload.TryAdd(scriptPtr, + (type.Assembly.GetName().Name, type.FullName ?? type.ToString())); + } + + _pathTypeBiMap.RemoveByScriptType(type); + } + } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void AddTypeForAlcReloading(Type type) + { + var alc = AssemblyLoadContext.GetLoadContext(type.Assembly); + if (alc == null) + return; + + var typesInAlc = _alcData.GetOrAdd(alc, + static alc => + { + alc.Unloading += OnAlcUnloading; + return new(); + }); + typesInAlc.TryAdd(type, 0); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void TrackAlcForUnloading(AssemblyLoadContext alc) + { + _ = _alcData.GetOrAdd(alc, + static alc => + { + alc.Unloading += OnAlcUnloading; + return new(); + }); + } + + private static ScriptTypeBiMap _scriptTypeBiMap = new(); + private static PathScriptTypeBiMap _pathTypeBiMap = new(); + + private static ConcurrentDictionary<IntPtr, (string? assemblyName, string classFullName)> + _scriptDataForReload = new(); + + [UnmanagedCallersOnly] + internal static void FrameCallback() + { + try + { + Dispatcher.DefaultGodotTaskScheduler?.Activate(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe IntPtr CreateManagedForGodotObjectBinding(godot_string_name* nativeTypeName, + IntPtr godotObject) + { + // TODO: Optimize with source generators and delegate pointers + + try + { + using var stringName = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(nativeTypeName))); + string nativeTypeNameStr = stringName.ToString(); + + Type nativeType = TypeGetProxyClass(nativeTypeNameStr) ?? throw new InvalidOperationException( + "Wrapper class not found for type: " + nativeTypeNameStr); + var obj = (Object)FormatterServices.GetUninitializedObject(nativeType); + + var ctor = nativeType.GetConstructor( + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, + null, Type.EmptyTypes, null); + + obj.NativePtr = godotObject; + + _ = ctor!.Invoke(obj, null); + + return GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(obj)); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return IntPtr.Zero; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool CreateManagedForGodotObjectScriptInstance(IntPtr scriptPtr, + IntPtr godotObject, + godot_variant** args, int argCount) + { + // TODO: Optimize with source generators and delegate pointers + + try + { + // Performance is not critical here as this will be replaced with source generators. + Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + var obj = (Object)FormatterServices.GetUninitializedObject(scriptType); + + var ctor = scriptType + .GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(c => c.GetParameters().Length == argCount) + .FirstOrDefault(); + + if (ctor == null) + { + if (argCount == 0) + { + throw new MissingMemberException( + $"Cannot create script instance. The class '{scriptType.FullName}' does not define a parameterless constructor."); + } + else + { + throw new MissingMemberException( + $"The class '{scriptType.FullName}' does not define a constructor that takes x parameters."); + } + } + + var parameters = ctor.GetParameters(); + int paramCount = parameters.Length; + + var invokeParams = new object?[paramCount]; + + for (int i = 0; i < paramCount; i++) + { + invokeParams[i] = Marshaling.ConvertVariantToManagedObjectOfType( + *args[i], parameters[i].ParameterType); + } + + obj.NativePtr = godotObject; + + _ = ctor.Invoke(obj, invokeParams); + + + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void GetScriptNativeName(IntPtr scriptPtr, godot_string_name* outRes) + { + try + { + // Performance is not critical here as this will be replaced with source generators. + if (!_scriptTypeBiMap.TryGetScriptType(scriptPtr, out Type? scriptType)) + { + *outRes = default; + return; + } + + var native = Object.InternalGetClassNativeBase(scriptType); + + var field = native?.GetField("NativeName", BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.Public | BindingFlags.NonPublic); + + if (field == null) + { + *outRes = default; + return; + } + + var nativeName = (StringName?)field.GetValue(null); + + if (nativeName == null) + { + *outRes = default; + return; + } + + *outRes = NativeFuncs.godotsharp_string_name_new_copy((godot_string_name)nativeName.NativeValue); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outRes = default; + } + } + + [UnmanagedCallersOnly] + internal static void SetGodotObjectPtr(IntPtr gcHandlePtr, IntPtr newPtr) + { + try + { + var target = (Object?)GCHandle.FromIntPtr(gcHandlePtr).Target; + if (target != null) + target.NativePtr = newPtr; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + private static Type? TypeGetProxyClass(string nativeTypeNameStr) + { + // Performance is not critical here as this will be replaced with a generated dictionary. + + if (nativeTypeNameStr[0] == '_') + nativeTypeNameStr = nativeTypeNameStr.Substring(1); + + Type? wrapperType = typeof(Object).Assembly.GetType("Godot." + nativeTypeNameStr); + + if (wrapperType == null) + { + wrapperType = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor")? + .GetType("Godot." + nativeTypeNameStr); + } + + static bool IsStatic(Type type) => type.IsAbstract && type.IsSealed; + + if (wrapperType != null && IsStatic(wrapperType)) + { + // A static class means this is a Godot singleton class. If an instance is needed we use Godot.Object. + return typeof(Object); + } + + return wrapperType; + } + + // Called from GodotPlugins + // ReSharper disable once UnusedMember.Local + public static void LookupScriptsInAssembly(Assembly assembly) + { + static void LookupScriptForClass(Type type) + { + var scriptPathAttr = type.GetCustomAttributes(inherit: false) + .OfType<ScriptPathAttribute>() + .FirstOrDefault(); + + if (scriptPathAttr == null) + return; + + _pathTypeBiMap.Add(scriptPathAttr.Path, type); + + if (AlcReloadCfg.IsAlcReloadingEnabled) + { + AddTypeForAlcReloading(type); + } + } + + var assemblyHasScriptsAttr = assembly.GetCustomAttributes(inherit: false) + .OfType<AssemblyHasScriptsAttribute>() + .FirstOrDefault(); + + if (assemblyHasScriptsAttr == null) + return; + + if (assemblyHasScriptsAttr.RequiresLookup) + { + // This is supported for scenarios where specifying all types would be cumbersome, + // such as when disabling C# source generators (for whatever reason) or when using a + // language other than C# that has nothing similar to source generators to automate it. + + var typeOfGodotObject = typeof(Object); + + foreach (var type in assembly.GetTypes()) + { + if (type.IsNested) + continue; + + if (!typeOfGodotObject.IsAssignableFrom(type)) + continue; + + LookupScriptForClass(type); + } + } + else + { + // This is the most likely scenario as we use C# source generators + + var scriptTypes = assemblyHasScriptsAttr.ScriptTypes; + + if (scriptTypes != null) + { + for (int i = 0; i < scriptTypes.Length; i++) + { + LookupScriptForClass(scriptTypes[i]); + } + } + } + } + + [UnmanagedCallersOnly] + internal static unsafe void RaiseEventSignal(IntPtr ownerGCHandlePtr, + godot_string_name* eventSignalName, godot_variant** args, int argCount, godot_bool* outOwnerIsNull) + { + try + { + var owner = (Object?)GCHandle.FromIntPtr(ownerGCHandlePtr).Target; + + if (owner == null) + { + *outOwnerIsNull = godot_bool.True; + return; + } + + *outOwnerIsNull = godot_bool.False; + + owner.RaiseGodotClassSignalCallbacks(CustomUnsafe.AsRef(eventSignalName), + new NativeVariantPtrArgs(args), argCount); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outOwnerIsNull = godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static godot_bool ScriptIsOrInherits(IntPtr scriptPtr, IntPtr scriptPtrMaybeBase) + { + try + { + if (!_scriptTypeBiMap.TryGetScriptType(scriptPtr, out Type? scriptType)) + return godot_bool.False; + + if (!_scriptTypeBiMap.TryGetScriptType(scriptPtrMaybeBase, out Type? maybeBaseType)) + return godot_bool.False; + + return (scriptType == maybeBaseType || maybeBaseType.IsAssignableFrom(scriptType)).ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool AddScriptBridge(IntPtr scriptPtr, godot_string* scriptPath) + { + try + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + if (!_scriptTypeBiMap.IsScriptRegistered(scriptPtr)) + { + string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath); + + if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType)) + return godot_bool.False; + + _scriptTypeBiMap.Add(scriptPtr, scriptType); + } + } + + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void GetOrCreateScriptBridgeForPath(godot_string* scriptPath, godot_ref* outScript) + { + string scriptPathStr = Marshaling.ConvertStringToManaged(*scriptPath); + + if (!_pathTypeBiMap.TryGetScriptType(scriptPathStr, out Type? scriptType)) + { + NativeFuncs.godotsharp_internal_new_csharp_script(outScript); + return; + } + + GetOrCreateScriptBridgeForType(scriptType, outScript); + } + + private static unsafe void GetOrCreateScriptBridgeForType(Type scriptType, godot_ref* outScript) + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + if (_scriptTypeBiMap.TryGetScriptPtr(scriptType, out IntPtr scriptPtr)) + { + // Use existing + NativeFuncs.godotsharp_ref_new_from_ref_counted_ptr(out *outScript, scriptPtr); + return; + } + + // This path is slower, but it's only executed for the first instantiation of the type + CreateScriptBridgeForType(scriptType, outScript); + } + } + + internal static unsafe void GetOrLoadOrCreateScriptForType(Type scriptType, godot_ref* outScript) + { + static bool GetPathOtherwiseGetOrCreateScript(Type scriptType, godot_ref* outScript, + [MaybeNullWhen(false)] out string scriptPath) + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + if (_scriptTypeBiMap.TryGetScriptPtr(scriptType, out IntPtr scriptPtr)) + { + // Use existing + NativeFuncs.godotsharp_ref_new_from_ref_counted_ptr(out *outScript, scriptPtr); + scriptPath = null; + return false; + } + + // This path is slower, but it's only executed for the first instantiation of the type + + if (_pathTypeBiMap.TryGetScriptPath(scriptType, out scriptPath)) + return true; + + CreateScriptBridgeForType(scriptType, outScript); + scriptPath = null; + return false; + } + } + + if (GetPathOtherwiseGetOrCreateScript(scriptType, outScript, out string? scriptPath)) + { + // This path is slower, but it's only executed for the first instantiation of the type + + // This must be done outside the read-write lock, as the script resource loading can lock it + using godot_string scriptPathIn = Marshaling.ConvertStringToNative(scriptPath); + if (!NativeFuncs.godotsharp_internal_script_load(scriptPathIn, outScript).ToBool()) + { + GD.PushError($"Cannot load script for type '{scriptType.FullName}'. Path: '{scriptPath}'."); + + // If loading of the script fails, best we can do create a new script + // with no path, as we do for types without an associated script file. + GetOrCreateScriptBridgeForType(scriptType, outScript); + } + } + } + + private static unsafe void CreateScriptBridgeForType(Type scriptType, godot_ref* outScript) + { + NativeFuncs.godotsharp_internal_new_csharp_script(outScript); + IntPtr scriptPtr = outScript->Reference; + + // Caller takes care of locking + _scriptTypeBiMap.Add(scriptPtr, scriptType); + + NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr); + } + + [UnmanagedCallersOnly] + internal static void RemoveScriptBridge(IntPtr scriptPtr) + { + try + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + _scriptTypeBiMap.Remove(scriptPtr); + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static godot_bool TryReloadRegisteredScriptWithClass(IntPtr scriptPtr) + { + try + { + lock (_scriptTypeBiMap.ReadWriteLock) + { + if (_scriptTypeBiMap.TryGetScriptType(scriptPtr, out _)) + { + // NOTE: + // Currently, we reload all scripts, not only the ones from the unloaded ALC. + // As such, we need to handle this case instead of treating it as an error. + NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr); + return godot_bool.True; + } + + if (!_scriptDataForReload.TryGetValue(scriptPtr, out var dataForReload)) + { + GD.PushError("Missing class qualified name for reloading script"); + return godot_bool.False; + } + + _ = _scriptDataForReload.TryRemove(scriptPtr, out _); + + if (dataForReload.assemblyName == null) + { + GD.PushError( + $"Missing assembly name of class '{dataForReload.classFullName}' for reloading script"); + return godot_bool.False; + } + + var scriptType = ReflectionUtils.FindTypeInLoadedAssemblies(dataForReload.assemblyName, + dataForReload.classFullName); + + if (scriptType == null) + { + // The class was removed, can't reload + return godot_bool.False; + } + + // ReSharper disable once RedundantNameQualifier + if (!typeof(Godot.Object).IsAssignableFrom(scriptType)) + { + // The class no longer inherits Godot.Object, can't reload + return godot_bool.False; + } + + _scriptTypeBiMap.Add(scriptPtr, scriptType); + + NativeFuncs.godotsharp_internal_reload_registered_script(scriptPtr); + + return godot_bool.True; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void UpdateScriptClassInfo(IntPtr scriptPtr, godot_bool* outTool, + godot_array* outMethodsDest, godot_dictionary* outRpcFunctionsDest, + godot_dictionary* outEventSignalsDest, godot_ref* outBaseScript) + { + try + { + // Performance is not critical here as this will be replaced with source generators. + var scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + + *outTool = scriptType.GetCustomAttributes(inherit: false) + .OfType<ToolAttribute>() + .Any().ToGodotBool(); + + if (!(*outTool).ToBool() && scriptType.IsNested) + { + *outTool = (scriptType.DeclaringType?.GetCustomAttributes(inherit: false) + .OfType<ToolAttribute>() + .Any() ?? false).ToGodotBool(); + } + + if (!(*outTool).ToBool() && scriptType.Assembly.GetName().Name == "GodotTools") + *outTool = godot_bool.True; + + // Methods + + // Performance is not critical here as this will be replaced with source generators. + using var methods = new Collections.Array(); + + Type? top = scriptType; + Type native = Object.InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + var methodList = GetMethodListForType(top); + + if (methodList != null) + { + foreach (var method in methodList) + { + var methodInfo = new Collections.Dictionary(); + + methodInfo.Add("name", method.Name); + + var methodParams = new Collections.Array(); + + if (method.Arguments != null) + { + foreach (var param in method.Arguments) + { + methodParams.Add(new Collections.Dictionary() + { + { "name", param.Name }, + { "type", (int)param.Type }, + { "usage", (int)param.Usage } + }); + } + } + + methodInfo.Add("params", methodParams); + + methods.Add(methodInfo); + } + } + + top = top.BaseType; + } + + *outMethodsDest = NativeFuncs.godotsharp_array_new_copy( + (godot_array)methods.NativeValue); + + // RPC functions + + Collections.Dictionary rpcFunctions = new(); + + top = scriptType; + + while (top != null && top != native) + { + foreach (var method in top.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | + BindingFlags.NonPublic | BindingFlags.Public)) + { + if (method.IsStatic) + continue; + + string methodName = method.Name; + + if (rpcFunctions.ContainsKey(methodName)) + continue; + + var rpcAttr = method.GetCustomAttributes(inherit: false) + .OfType<RPCAttribute>().FirstOrDefault(); + + if (rpcAttr == null) + continue; + + var rpcConfig = new Collections.Dictionary(); + + rpcConfig["rpc_mode"] = (long)rpcAttr.Mode; + rpcConfig["call_local"] = rpcAttr.CallLocal; + rpcConfig["transfer_mode"] = (long)rpcAttr.TransferMode; + rpcConfig["channel"] = rpcAttr.TransferChannel; + + rpcFunctions.Add(methodName, rpcConfig); + } + + top = top.BaseType; + } + + *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new_copy( + (godot_dictionary)(rpcFunctions).NativeValue); + + // Event signals + + // Performance is not critical here as this will be replaced with source generators. + using var signals = new Collections.Dictionary(); + + top = scriptType; + + while (top != null && top != native) + { + var signalList = GetSignalListForType(top); + + if (signalList != null) + { + foreach (var signal in signalList) + { + string signalName = signal.Name; + + if (signals.ContainsKey(signalName)) + continue; + + var signalParams = new Collections.Array(); + + if (signal.Arguments != null) + { + foreach (var param in signal.Arguments) + { + signalParams.Add(new Collections.Dictionary() + { + { "name", param.Name }, + { "type", (int)param.Type }, + { "usage", (int)param.Usage } + }); + } + } + + signals.Add(signalName, signalParams); + } + } + + top = top.BaseType; + } + + *outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new_copy( + (godot_dictionary)signals.NativeValue); + + // Base script + + var baseType = scriptType.BaseType; + if (baseType != null && baseType != native) + { + GetOrLoadOrCreateScriptForType(baseType, outBaseScript); + } + else + { + *outBaseScript = default; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outTool = godot_bool.False; + *outRpcFunctionsDest = NativeFuncs.godotsharp_dictionary_new(); + *outEventSignalsDest = NativeFuncs.godotsharp_dictionary_new(); + *outBaseScript = default; + } + } + + private static List<MethodInfo>? GetSignalListForType(Type type) + { + var getGodotSignalListMethod = type.GetMethod( + "GetGodotSignalList", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (getGodotSignalListMethod == null) + return null; + + return (List<MethodInfo>?)getGodotSignalListMethod.Invoke(null, null); + } + + private static List<MethodInfo>? GetMethodListForType(Type type) + { + var getGodotMethodListMethod = type.GetMethod( + "GetGodotMethodList", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (getGodotMethodListMethod == null) + return null; + + return (List<MethodInfo>?)getGodotMethodListMethod.Invoke(null, null); + } + + // ReSharper disable once InconsistentNaming + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + [StructLayout(LayoutKind.Sequential)] + private ref struct godotsharp_property_info + { + // Careful with padding... + public godot_string_name Name; // Not owned + public godot_string HintString; + public int Type; + public int Hint; + public int Usage; + public godot_bool Exported; + + public void Dispose() + { + HintString.Dispose(); + } + } + + [UnmanagedCallersOnly] + internal static unsafe void GetPropertyInfoList(IntPtr scriptPtr, + delegate* unmanaged<IntPtr, godot_string*, void*, int, void> addPropInfoFunc) + { + try + { + Type scriptType = _scriptTypeBiMap.GetScriptType(scriptPtr); + GetPropertyInfoListForType(scriptType, scriptPtr, addPropInfoFunc); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + private static unsafe void GetPropertyInfoListForType(Type type, IntPtr scriptPtr, + delegate* unmanaged<IntPtr, godot_string*, void*, int, void> addPropInfoFunc) + { + try + { + var getGodotPropertyListMethod = type.GetMethod( + "GetGodotPropertyList", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (getGodotPropertyListMethod == null) + return; + + var properties = (List<PropertyInfo>?) + getGodotPropertyListMethod.Invoke(null, null); + + if (properties == null || properties.Count <= 0) + return; + + int length = properties.Count; + + // There's no recursion here, so it's ok to go with a big enough number for most cases + // stackMaxSize = stackMaxLength * sizeof(godotsharp_property_info) + const int stackMaxLength = 32; + bool useStack = length < stackMaxLength; + + godotsharp_property_info* interopProperties; + + if (useStack) + { + // Weird limitation, hence the need for aux: + // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." + var aux = stackalloc godotsharp_property_info[length]; + interopProperties = aux; + } + else + { + interopProperties = ((godotsharp_property_info*)NativeMemory.Alloc((nuint)length))!; + } + + try + { + for (int i = 0; i < length; i++) + { + var property = properties[i]; + + godotsharp_property_info interopProperty = new() + { + Type = (int)property.Type, + Name = (godot_string_name)property.Name.NativeValue, // Not owned + Hint = (int)property.Hint, + HintString = Marshaling.ConvertStringToNative(property.HintString), + Usage = (int)property.Usage, + Exported = property.Exported.ToGodotBool() + }; + + interopProperties[i] = interopProperty; + } + + using godot_string currentClassName = Marshaling.ConvertStringToNative(type.Name); + + addPropInfoFunc(scriptPtr, ¤tClassName, interopProperties, length); + + // We're borrowing the StringName's without making an owning copy, so the + // managed collection needs to be kept alive until `addPropInfoFunc` returns. + GC.KeepAlive(properties); + } + finally + { + for (int i = 0; i < length; i++) + interopProperties[i].Dispose(); + + if (!useStack) + NativeMemory.Free(interopProperties); + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + // ReSharper disable once InconsistentNaming + [SuppressMessage("ReSharper", "NotAccessedField.Local")] + [StructLayout(LayoutKind.Sequential)] + private ref struct godotsharp_property_def_val_pair + { + // Careful with padding... + public godot_string_name Name; // Not owned + public godot_variant Value; + + public void Dispose() + { + Value.Dispose(); + } + } + + [UnmanagedCallersOnly] + internal static unsafe void GetPropertyDefaultValues(IntPtr scriptPtr, + delegate* unmanaged<IntPtr, void*, int, void> addDefValFunc) + { + try + { + Type? top = _scriptTypeBiMap.GetScriptType(scriptPtr); + Type native = Object.InternalGetClassNativeBase(top); + + while (top != null && top != native) + { + GetPropertyDefaultValuesForType(top, scriptPtr, addDefValFunc); + + top = top.BaseType; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [SkipLocalsInit] + private static unsafe void GetPropertyDefaultValuesForType(Type type, IntPtr scriptPtr, + delegate* unmanaged<IntPtr, void*, int, void> addDefValFunc) + { + try + { + var getGodotPropertyDefaultValuesMethod = type.GetMethod( + "GetGodotPropertyDefaultValues", + BindingFlags.DeclaredOnly | BindingFlags.Static | + BindingFlags.NonPublic | BindingFlags.Public); + + if (getGodotPropertyDefaultValuesMethod == null) + return; + + var defaultValues = (Dictionary<StringName, object>?) + getGodotPropertyDefaultValuesMethod.Invoke(null, null); + + if (defaultValues == null || defaultValues.Count <= 0) + return; + + int length = defaultValues.Count; + + // There's no recursion here, so it's ok to go with a big enough number for most cases + // stackMaxSize = stackMaxLength * sizeof(godotsharp_property_def_val_pair) + const int stackMaxLength = 32; + bool useStack = length < stackMaxLength; + + godotsharp_property_def_val_pair* interopDefaultValues; + + if (useStack) + { + // Weird limitation, hence the need for aux: + // "In the case of pointer types, you can use a stackalloc expression only in a local variable declaration to initialize the variable." + var aux = stackalloc godotsharp_property_def_val_pair[length]; + interopDefaultValues = aux; + } + else + { + interopDefaultValues = ((godotsharp_property_def_val_pair*)NativeMemory.Alloc((nuint)length))!; + } + + try + { + int i = 0; + foreach (var defaultValuePair in defaultValues) + { + godotsharp_property_def_val_pair interopProperty = new() + { + Name = (godot_string_name)defaultValuePair.Key.NativeValue, // Not owned + Value = Marshaling.ConvertManagedObjectToVariant(defaultValuePair.Value) + }; + + interopDefaultValues[i] = interopProperty; + + i++; + } + + addDefValFunc(scriptPtr, interopDefaultValues, length); + + // We're borrowing the StringName's without making an owning copy, so the + // managed collection needs to be kept alive until `addDefValFunc` returns. + GC.KeepAlive(defaultValues); + } + finally + { + for (int i = 0; i < length; i++) + interopDefaultValues[i].Dispose(); + + if (!useStack) + NativeMemory.Free(interopDefaultValues); + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool SwapGCHandleForType(IntPtr oldGCHandlePtr, IntPtr* outNewGCHandlePtr, + godot_bool createWeak) + { + try + { + var oldGCHandle = GCHandle.FromIntPtr(oldGCHandlePtr); + + object? target = oldGCHandle.Target; + + if (target == null) + { + CustomGCHandle.Free(oldGCHandle); + *outNewGCHandlePtr = IntPtr.Zero; + return godot_bool.False; // Called after the managed side was collected, so nothing to do here + } + + // Release the current weak handle and replace it with a strong handle. + var newGCHandle = createWeak.ToBool() ? + CustomGCHandle.AllocWeak(target) : + CustomGCHandle.AllocStrong(target); + + CustomGCHandle.Free(oldGCHandle); + *outNewGCHandlePtr = GCHandle.ToIntPtr(newGCHandle); + return godot_bool.True; + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outNewGCHandlePtr = IntPtr.Zero; + return godot_bool.False; + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs new file mode 100644 index 0000000000..a58f6849ad --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.types.cs @@ -0,0 +1,92 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Godot.Bridge; + +#nullable enable + +public static partial class ScriptManagerBridge +{ + private class ScriptTypeBiMap + { + public readonly object ReadWriteLock = new(); + private System.Collections.Generic.Dictionary<IntPtr, Type> _scriptTypeMap = new(); + private System.Collections.Generic.Dictionary<Type, IntPtr> _typeScriptMap = new(); + + public void Add(IntPtr scriptPtr, Type scriptType) + { + // TODO: What if this is called while unloading a load context, but after we already did cleanup in preparation for unloading? + + _scriptTypeMap.Add(scriptPtr, scriptType); + _typeScriptMap.Add(scriptType, scriptPtr); + + if (AlcReloadCfg.IsAlcReloadingEnabled) + { + AddTypeForAlcReloading(scriptType); + } + } + + public void Remove(IntPtr scriptPtr) + { + if (_scriptTypeMap.Remove(scriptPtr, out Type? scriptType)) + _ = _typeScriptMap.Remove(scriptType); + } + + public bool RemoveByScriptType(Type scriptType, out IntPtr scriptPtr) + { + if (_typeScriptMap.Remove(scriptType, out scriptPtr)) + return _scriptTypeMap.Remove(scriptPtr); + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Type GetScriptType(IntPtr scriptPtr) => _scriptTypeMap[scriptPtr]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetScriptType(IntPtr scriptPtr, [MaybeNullWhen(false)] out Type scriptType) => + _scriptTypeMap.TryGetValue(scriptPtr, out scriptType); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetScriptPtr(Type scriptType, out IntPtr scriptPtr) => + _typeScriptMap.TryGetValue(scriptType, out scriptPtr); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsScriptRegistered(IntPtr scriptPtr) => _scriptTypeMap.ContainsKey(scriptPtr); + } + + private class PathScriptTypeBiMap + { + private System.Collections.Generic.Dictionary<string, Type> _pathTypeMap = new(); + private System.Collections.Generic.Dictionary<Type, string> _typePathMap = new(); + + public void Add(string scriptPath, Type scriptType) + { + _pathTypeMap.Add(scriptPath, scriptType); + + // Due to partial classes, more than one file can point to the same type, so + // there could be duplicate keys in this case. We only add a type as key once. + _typePathMap.TryAdd(scriptType, scriptPath); + } + + public void RemoveByScriptType(Type scriptType) + { + foreach (var pair in _pathTypeMap + .Where(p => p.Value == scriptType).ToArray()) + { + _pathTypeMap.Remove(pair.Key); + } + + _typePathMap.Remove(scriptType); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetScriptType(string scriptPath, [MaybeNullWhen(false)] out Type scriptType) => + _pathTypeMap.TryGetValue(scriptPath, out scriptType); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGetScriptPath(Type scriptType, [MaybeNullWhen(false)] out string scriptPath) => + _typePathMap.TryGetValue(scriptType, out scriptPath); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs index 2722b64e6d..8d0e77d171 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Callable.cs @@ -1,5 +1,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { @@ -24,7 +26,7 @@ namespace Godot /// } /// </code> /// </example> - public struct Callable + public readonly struct Callable { private readonly Object _target; private readonly StringName _method; @@ -34,10 +36,12 @@ namespace Godot /// Object that contains the method. /// </summary> public Object Target => _target; + /// <summary> /// Name of the method that will be called. /// </summary> public StringName Method => _method; + /// <summary> /// Delegate of the method that will be called. /// </summary> @@ -73,15 +77,43 @@ namespace Godot _delegate = @delegate; } + private const int VarArgsSpanThreshold = 5; + /// <summary> /// Calls the method represented by this <see cref="Callable"/>. /// Arguments can be passed and should match the method's signature. /// </summary> /// <param name="args">Arguments that will be passed to the method call.</param> /// <returns>The value returned by the method.</returns> - public object Call(params object[] args) + public unsafe Variant Call(params Variant[] args) { - return godot_icall_Callable_Call(ref this, args); + using godot_callable callable = Marshaling.ConvertCallableToNative(this); + + int argc = args.Length; + + Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? + stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + new godot_variant.movable[argc]; + + Span<IntPtr> argsSpan = argc <= 10 ? + stackalloc IntPtr[argc] : + new IntPtr[argc]; + + using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); + + fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) + fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) + { + for (int i = 0; i < argc; i++) + { + varargs[i] = (godot_variant)args[i].NativeVar; + argsPtr[i] = new IntPtr(&varargs[i]); + } + + godot_variant ret = NativeFuncs.godotsharp_callable_call(callable, + (godot_variant**)argsPtr, argc, out _); + return Variant.CreateTakingOwnershipOfDisposableValue(ret); + } } /// <summary> @@ -89,9 +121,33 @@ namespace Godot /// Arguments can be passed and should match the method's signature. /// </summary> /// <param name="args">Arguments that will be passed to the method call.</param> - public void CallDeferred(params object[] args) + public unsafe void CallDeferred(params Variant[] args) { - godot_icall_Callable_CallDeferred(ref this, args); + using godot_callable callable = Marshaling.ConvertCallableToNative(this); + + int argc = args.Length; + + Span<godot_variant.movable> argsStoreSpan = argc <= VarArgsSpanThreshold ? + stackalloc godot_variant.movable[VarArgsSpanThreshold].Cleared() : + new godot_variant.movable[argc]; + + Span<IntPtr> argsSpan = argc <= 10 ? + stackalloc IntPtr[argc] : + new IntPtr[argc]; + + using var variantSpanDisposer = new VariantSpanDisposer(argsStoreSpan); + + fixed (godot_variant* varargs = &MemoryMarshal.GetReference(argsStoreSpan).DangerousSelfRef) + fixed (IntPtr* argsPtr = &MemoryMarshal.GetReference(argsSpan)) + { + for (int i = 0; i < argc; i++) + { + varargs[i] = (godot_variant)args[i].NativeVar; + argsPtr[i] = new IntPtr(&varargs[i]); + } + + NativeFuncs.godotsharp_callable_call_deferred(callable, (godot_variant**)argsPtr, argc); + } } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs index a6324504fc..ed0e1efd35 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Color.cs @@ -210,7 +210,7 @@ namespace Godot case 3: return a; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -230,7 +230,7 @@ namespace Godot a = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } @@ -841,7 +841,7 @@ namespace Godot return ParseCol4(str, ofs) * 16 + ParseCol4(str, ofs + 1); } - private string ToHex32(float val) + private static string ToHex32(float val) { byte b = (byte)Mathf.RoundToInt(Mathf.Clamp(val * 255, 0, 255)); return b.HexEncode(); @@ -849,7 +849,7 @@ namespace Godot internal static bool HtmlIsValid(string color) { - if (color.Length == 0) + if (string.IsNullOrEmpty(color)) { return false; } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs new file mode 100644 index 0000000000..42f19ace1a --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/CustomGCHandle.cs @@ -0,0 +1,98 @@ +#nullable enable + +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using Godot.Bridge; + +namespace Godot; + +/// <summary> +/// Provides a GCHandle that becomes weak when unloading the assembly load context, without having +/// to manually replace the GCHandle. This hides all the complexity of releasing strong GC handles +/// to allow the assembly load context to unload properly. +/// +/// Internally, a strong CustomGCHandle actually contains a weak GCHandle, while the actual strong +/// reference is stored in a static table. +/// </summary> +public static class CustomGCHandle +{ + // ConditionalWeakTable uses DependentHandle, so it stores weak references. + // Having the assembly load context as key won't prevent it from unloading. + private static ConditionalWeakTable<AssemblyLoadContext, object?> _alcsBeingUnloaded = new(); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool IsAlcBeingUnloaded(AssemblyLoadContext alc) => _alcsBeingUnloaded.TryGetValue(alc, out _); + + // ReSharper disable once RedundantNameQualifier + private static ConcurrentDictionary< + AssemblyLoadContext, + ConcurrentDictionary<GCHandle, object> + > _strongReferencesByAlc = new(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void OnAlcUnloading(AssemblyLoadContext alc) + { + _alcsBeingUnloaded.Add(alc, null); + + if (_strongReferencesByAlc.TryRemove(alc, out var strongReferences)) + { + strongReferences.Clear(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static GCHandle AllocStrong(object value) + => AllocStrong(value, value.GetType()); + + public static GCHandle AllocStrong(object value, Type valueType) + { + if (AlcReloadCfg.IsAlcReloadingEnabled) + { + var alc = AssemblyLoadContext.GetLoadContext(valueType.Assembly); + + if (alc != null) + { + var weakHandle = GCHandle.Alloc(value, GCHandleType.Weak); + + if (!IsAlcBeingUnloaded(alc)) + { + var strongReferences = _strongReferencesByAlc.GetOrAdd(alc, + static alc => + { + alc.Unloading += OnAlcUnloading; + return new(); + }); + strongReferences.TryAdd(weakHandle, value); + } + + return weakHandle; + } + } + + return GCHandle.Alloc(value, GCHandleType.Normal); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static GCHandle AllocWeak(object value) => GCHandle.Alloc(value, GCHandleType.Weak); + + public static void Free(GCHandle handle) + { + if (AlcReloadCfg.IsAlcReloadingEnabled) + { + var target = handle.Target; + + if (target != null) + { + var alc = AssemblyLoadContext.GetLoadContext(target.GetType().Assembly); + + if (alc != null && _strongReferencesByAlc.TryGetValue(alc, out var strongReferences)) + _ = strongReferences.TryRemove(handle, out _); + } + } + + handle.Free(); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs index edfe3464ec..6fbc04702a 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DebuggingUtils.cs @@ -1,13 +1,18 @@ using System; using System.Diagnostics; using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; +using Godot.NativeInterop; + +#nullable enable namespace Godot { internal static class DebuggingUtils { - internal static void AppendTypeName(this StringBuilder sb, Type type) + private static void AppendTypeName(this StringBuilder sb, Type type) { if (type.IsPrimitive) sb.Append(type.Name); @@ -16,21 +21,97 @@ namespace Godot else sb.Append(type); - sb.Append(" "); + sb.Append(' '); } - public static void InstallTraceListener() + internal static void InstallTraceListener() { Trace.Listeners.Clear(); Trace.Listeners.Add(new GodotTraceListener()); } - public static void GetStackFrameInfo(StackFrame frame, out string fileName, out int fileLineNumber, out string methodDecl) + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal ref struct godot_stack_info + { + public godot_string File; + public godot_string Func; + public int Line; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal ref struct godot_stack_info_vector { - fileName = frame.GetFileName(); - fileLineNumber = frame.GetFileLineNumber(); + private IntPtr _writeProxy; + private unsafe godot_stack_info* _ptr; + + public readonly unsafe godot_stack_info* Elements + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public void Resize(int size) + { + if (size < 0) + throw new ArgumentOutOfRangeException(nameof(size)); + var err = NativeFuncs.godotsharp_stack_info_vector_resize(ref this, size); + if (err != Error.Ok) + throw new InvalidOperationException("Failed to resize vector. Error code is: " + err.ToString()); + } + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_stack_info_vector_destroy(ref this); + _ptr = null; + } + } - MethodBase methodBase = frame.GetMethod(); + [UnmanagedCallersOnly] + internal static unsafe void GetCurrentStackInfo(void* destVector) + { + try + { + var vector = (godot_stack_info_vector*)destVector; + var stackTrace = new StackTrace(skipFrames: 1, fNeedFileInfo: true); + int frameCount = stackTrace.FrameCount; + + if (frameCount == 0) + return; + + vector->Resize(frameCount); + + int i = 0; + foreach (StackFrame frame in stackTrace.GetFrames()) + { + string? fileName = frame.GetFileName(); + int fileLineNumber = frame.GetFileLineNumber(); + + GetStackFrameMethodDecl(frame, out string methodDecl); + + godot_stack_info* stackInfo = &vector->Elements[i]; + + // Assign directly to element in Vector. This way we don't need to worry + // about disposal if an exception is thrown. The Vector takes care of it. + stackInfo->File = Marshaling.ConvertStringToNative(fileName); + stackInfo->Func = Marshaling.ConvertStringToNative(methodDecl); + stackInfo->Line = fileLineNumber; + + i++; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + internal static void GetStackFrameMethodDecl(StackFrame frame, out string methodDecl) + { + MethodBase? methodBase = frame.GetMethod(); if (methodBase == null) { @@ -43,15 +124,15 @@ namespace Godot if (methodBase is MethodInfo) sb.AppendTypeName(((MethodInfo)methodBase).ReturnType); - sb.Append(methodBase.DeclaringType.FullName); - sb.Append("."); + sb.Append(methodBase.DeclaringType?.FullName ?? "<unknown>"); + sb.Append('.'); sb.Append(methodBase.Name); if (methodBase.IsGenericMethod) { Type[] genericParams = methodBase.GetGenericArguments(); - sb.Append("<"); + sb.Append('<'); for (int j = 0; j < genericParams.Length; j++) { @@ -61,10 +142,10 @@ namespace Godot sb.AppendTypeName(genericParams[j]); } - sb.Append(">"); + sb.Append('>'); } - sb.Append("("); + sb.Append('('); bool varArgs = (methodBase.CallingConvention & CallingConventions.VarArgs) != 0; @@ -81,7 +162,7 @@ namespace Godot sb.AppendTypeName(parameter[i].ParameterType); } - sb.Append(")"); + sb.Append(')'); methodDecl = sb.ToString(); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs index 1dca9e6ea7..266038a0af 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DelegateUtils.cs @@ -1,14 +1,72 @@ +#nullable enable + using System; using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { internal static class DelegateUtils { + [UnmanagedCallersOnly] + internal static godot_bool DelegateEquals(IntPtr delegateGCHandleA, IntPtr delegateGCHandleB) + { + try + { + var @delegateA = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleA).Target; + var @delegateB = (Delegate?)GCHandle.FromIntPtr(delegateGCHandleB).Target; + return (@delegateA! == @delegateB!).ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; + } + } + + [UnmanagedCallersOnly] + internal static unsafe void InvokeWithVariantArgs(IntPtr delegateGCHandle, godot_variant** args, uint argc, + godot_variant* outRet) + { + try + { + // TODO: Optimize + var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!; + var managedArgs = new object?[argc]; + + var parameterInfos = @delegate.Method.GetParameters(); + var paramsLength = parameterInfos.Length; + + if (argc != paramsLength) + { + throw new InvalidOperationException( + $"The delegate expects {paramsLength} arguments, but received {argc}."); + } + + for (uint i = 0; i < argc; i++) + { + managedArgs[i] = Marshaling.ConvertVariantToManagedObjectOfType( + *args[i], parameterInfos[i].ParameterType); + } + + object? invokeRet = @delegate.DynamicInvoke(managedArgs); + + *outRet = Marshaling.ConvertManagedObjectToVariant(invokeRet); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outRet = default; + } + } + + // TODO: Check if we should be using BindingFlags.DeclaredOnly (would give better reflection performance). + private enum TargetKind : uint { Static, @@ -39,20 +97,20 @@ namespace Godot } } - if (TrySerializeSingleDelegate(@delegate, out byte[] buffer)) + if (TrySerializeSingleDelegate(@delegate, out byte[]? buffer)) { - serializedData.Add(buffer); + serializedData.Add((Span<byte>)buffer); return true; } return false; } - private static bool TrySerializeSingleDelegate(Delegate @delegate, out byte[] buffer) + private static bool TrySerializeSingleDelegate(Delegate @delegate, [MaybeNullWhen(false)] out byte[] buffer) { buffer = null; - object target = @delegate.Target; + object? target = @delegate.Target; switch (target) { @@ -72,12 +130,14 @@ namespace Godot return true; } } + // ReSharper disable once RedundantNameQualifier case Godot.Object godotObject: { using (var stream = new MemoryStream()) using (var writer = new BinaryWriter(stream)) { writer.Write((ulong)TargetKind.GodotObject); + // ReSharper disable once RedundantCast writer.Write((ulong)godotObject.GetInstanceId()); SerializeType(writer, @delegate.GetType()); @@ -93,7 +153,7 @@ namespace Godot { Type targetType = target.GetType(); - if (targetType.GetCustomAttribute(typeof(CompilerGeneratedAttribute), true) != null) + if (targetType.IsDefined(typeof(CompilerGeneratedAttribute), true)) { // Compiler generated. Probably a closure. Try to serialize it. @@ -121,8 +181,18 @@ namespace Godot if (variantType == Variant.Type.Nil) return false; + static byte[] Var2Bytes(in godot_variant var) + { + NativeFuncs.godotsharp_var2bytes(var, false.ToGodotBool(), out var varBytes); + using (varBytes) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(varBytes); + } + writer.Write(field.Name); - byte[] valueBuffer = GD.Var2Bytes(field.GetValue(target)); + + var fieldValue = field.GetValue(target); + using var fieldValueVariant = Marshaling.ConvertManagedObjectToVariant(fieldValue); + byte[] valueBuffer = Var2Bytes(fieldValueVariant); writer.Write(valueBuffer.Length); writer.Write(valueBuffer); } @@ -139,9 +209,6 @@ namespace Godot private static bool TrySerializeMethodInfo(BinaryWriter writer, MethodInfo methodInfo) { - if (methodInfo == null) - return false; - SerializeType(writer, methodInfo.DeclaringType); writer.Write(methodInfo.Name); @@ -180,7 +247,7 @@ namespace Godot return true; } - private static void SerializeType(BinaryWriter writer, Type type) + private static void SerializeType(BinaryWriter writer, Type? type) { if (type == null) { @@ -195,9 +262,8 @@ namespace Godot int genericArgumentsCount = genericArgs.Length; writer.Write(genericArgumentsCount); - string assemblyQualifiedName = genericTypeDef.AssemblyQualifiedName; - Debug.Assert(assemblyQualifiedName != null); - writer.Write(assemblyQualifiedName); + writer.Write(genericTypeDef.Assembly.GetName().Name ?? ""); + writer.Write(genericTypeDef.FullName ?? genericTypeDef.ToString()); for (int i = 0; i < genericArgs.Length; i++) SerializeType(writer, genericArgs[i]); @@ -207,17 +273,71 @@ namespace Godot int genericArgumentsCount = 0; writer.Write(genericArgumentsCount); - string assemblyQualifiedName = type.AssemblyQualifiedName; - Debug.Assert(assemblyQualifiedName != null); - writer.Write(assemblyQualifiedName); + writer.Write(type.Assembly.GetName().Name ?? ""); + writer.Write(type.FullName ?? type.ToString()); + } + } + + [UnmanagedCallersOnly] + internal static unsafe godot_bool TrySerializeDelegateWithGCHandle(IntPtr delegateGCHandle, + godot_array* nSerializedData) + { + try + { + var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(*nSerializedData)); + + var @delegate = (Delegate)GCHandle.FromIntPtr(delegateGCHandle).Target!; + + return TrySerializeDelegate(@delegate, serializedData) + .ToGodotBool(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + return godot_bool.False; } } - private static bool TryDeserializeDelegate(Collections.Array serializedData, out Delegate @delegate) + [UnmanagedCallersOnly] + internal static unsafe godot_bool TryDeserializeDelegateWithGCHandle(godot_array* nSerializedData, + IntPtr* delegateGCHandle) { + try + { + var serializedData = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(*nSerializedData)); + + if (TryDeserializeDelegate(serializedData, out Delegate? @delegate)) + { + *delegateGCHandle = GCHandle.ToIntPtr(CustomGCHandle.AllocStrong(@delegate)); + return godot_bool.True; + } + else + { + *delegateGCHandle = IntPtr.Zero; + return godot_bool.False; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *delegateGCHandle = default; + return godot_bool.False; + } + } + + internal static bool TryDeserializeDelegate(Collections.Array serializedData, + [MaybeNullWhen(false)] out Delegate @delegate) + { + @delegate = null; + if (serializedData.Count == 1) { - object elem = serializedData[0]; + var elem = serializedData[0].Obj; + + if (elem == null) + return false; if (elem is Collections.Array multiCastData) return TryDeserializeDelegate(multiCastData, out @delegate); @@ -225,20 +345,23 @@ namespace Godot return TryDeserializeSingleDelegate((byte[])elem, out @delegate); } - @delegate = null; - var delegates = new List<Delegate>(serializedData.Count); - foreach (object elem in serializedData) + foreach (Variant variantElem in serializedData) { + var elem = variantElem.Obj; + + if (elem == null) + continue; + if (elem is Collections.Array multiCastData) { - if (TryDeserializeDelegate(multiCastData, out Delegate oneDelegate)) + if (TryDeserializeDelegate(multiCastData, out Delegate? oneDelegate)) delegates.Add(oneDelegate); } else { - if (TryDeserializeSingleDelegate((byte[])elem, out Delegate oneDelegate)) + if (TryDeserializeSingleDelegate((byte[])elem, out Delegate? oneDelegate)) delegates.Add(oneDelegate); } } @@ -246,11 +369,11 @@ namespace Godot if (delegates.Count <= 0) return false; - @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray()); + @delegate = delegates.Count == 1 ? delegates[0] : Delegate.Combine(delegates.ToArray())!; return true; } - private static bool TryDeserializeSingleDelegate(byte[] buffer, out Delegate @delegate) + private static bool TryDeserializeSingleDelegate(byte[] buffer, [MaybeNullWhen(false)] out Delegate @delegate) { @delegate = null; @@ -263,49 +386,59 @@ namespace Godot { case TargetKind.Static: { - Type delegateType = DeserializeType(reader); + Type? delegateType = DeserializeType(reader); if (delegateType == null) return false; - if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo)) + if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo)) + return false; + + @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo, throwOnBindFailure: false); + + if (@delegate == null) return false; - @delegate = Delegate.CreateDelegate(delegateType, null, methodInfo); return true; } case TargetKind.GodotObject: { ulong objectId = reader.ReadUInt64(); + // ReSharper disable once RedundantNameQualifier Godot.Object godotObject = GD.InstanceFromId(objectId); if (godotObject == null) return false; - Type delegateType = DeserializeType(reader); + Type? delegateType = DeserializeType(reader); if (delegateType == null) return false; - if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo)) + if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo)) + return false; + + @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo, + throwOnBindFailure: false); + + if (@delegate == null) return false; - @delegate = Delegate.CreateDelegate(delegateType, godotObject, methodInfo); return true; } case TargetKind.CompilerGenerated: { - Type targetType = DeserializeType(reader); + Type? targetType = DeserializeType(reader); if (targetType == null) return false; - Type delegateType = DeserializeType(reader); + Type? delegateType = DeserializeType(reader); if (delegateType == null) return false; - if (!TryDeserializeMethodInfo(reader, out MethodInfo methodInfo)) + if (!TryDeserializeMethodInfo(reader, out MethodInfo? methodInfo)) return false; int fieldCount = reader.ReadInt32(); - object recreatedTarget = Activator.CreateInstance(targetType); + object recreatedTarget = Activator.CreateInstance(targetType)!; for (int i = 0; i < fieldCount; i++) { @@ -313,11 +446,17 @@ namespace Godot int valueBufferLength = reader.ReadInt32(); byte[] valueBuffer = reader.ReadBytes(valueBufferLength); - FieldInfo fieldInfo = targetType.GetField(name, BindingFlags.Instance | BindingFlags.Public); + FieldInfo? fieldInfo = targetType.GetField(name, + BindingFlags.Instance | BindingFlags.Public); fieldInfo?.SetValue(recreatedTarget, GD.Bytes2Var(valueBuffer)); } - @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo); + @delegate = Delegate.CreateDelegate(delegateType, recreatedTarget, methodInfo, + throwOnBindFailure: false); + + if (@delegate == null) + return false; + return true; } default: @@ -326,18 +465,22 @@ namespace Godot } } - private static bool TryDeserializeMethodInfo(BinaryReader reader, out MethodInfo methodInfo) + private static bool TryDeserializeMethodInfo(BinaryReader reader, + [MaybeNullWhen(false)] out MethodInfo methodInfo) { methodInfo = null; - Type declaringType = DeserializeType(reader); + Type? declaringType = DeserializeType(reader); + + if (declaringType == null) + return false; string methodName = reader.ReadString(); int flags = reader.ReadInt32(); bool hasReturn = reader.ReadBoolean(); - Type returnType = hasReturn ? DeserializeType(reader) : typeof(void); + Type? returnType = hasReturn ? DeserializeType(reader) : typeof(void); int parametersCount = reader.ReadInt32(); @@ -347,7 +490,7 @@ namespace Godot for (int i = 0; i < parametersCount; i++) { - Type parameterType = DeserializeType(reader); + Type? parameterType = DeserializeType(reader); if (parameterType == null) return false; parameterTypes[i] = parameterType; @@ -361,15 +504,23 @@ namespace Godot return methodInfo != null && methodInfo.ReturnType == returnType; } - private static Type DeserializeType(BinaryReader reader) + private static Type? DeserializeType(BinaryReader reader) { int genericArgumentsCount = reader.ReadInt32(); if (genericArgumentsCount == -1) return null; - string assemblyQualifiedName = reader.ReadString(); - var type = Type.GetType(assemblyQualifiedName); + string assemblyName = reader.ReadString(); + + if (assemblyName.Length == 0) + { + GD.PushError($"Missing assembly name of type when attempting to deserialize delegate"); + return null; + } + + string typeFullName = reader.ReadString(); + var type = ReflectionUtils.FindTypeInLoadedAssemblies(assemblyName, typeFullName); if (type == null) return null; // Type not found @@ -380,7 +531,7 @@ namespace Godot for (int i = 0; i < genericArgumentsCount; i++) { - Type genericArgumentType = DeserializeType(reader); + Type? genericArgumentType = DeserializeType(reader); if (genericArgumentType == null) return null; genericArgumentTypes[i] = genericArgumentType; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs index e80b6af68f..fa8c94ed18 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dictionary.cs @@ -1,79 +1,50 @@ using System; using System.Collections.Generic; using System.Collections; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot.Collections { - internal class DictionarySafeHandle : SafeHandle - { - public DictionarySafeHandle(IntPtr handle) : base(IntPtr.Zero, true) - { - this.handle = handle; - } - - public override bool IsInvalid - { - get { return handle == IntPtr.Zero; } - } - - protected override bool ReleaseHandle() - { - Dictionary.godot_icall_Dictionary_Dtor(handle); - return true; - } - } - /// <summary> /// Wrapper around Godot's Dictionary class, a dictionary of Variant /// typed elements allocated in the engine in C++. Useful when /// interfacing with the engine. /// </summary> - public class Dictionary : IDictionary, IDisposable + public sealed class Dictionary : + IDictionary<Variant, Variant>, + IReadOnlyDictionary<Variant, Variant>, + IDisposable { - private DictionarySafeHandle _safeHandle; - private bool _disposed = false; + internal godot_dictionary.movable NativeValue; + + private WeakReference<IDisposable> _weakReferenceToSelf; /// <summary> /// Constructs a new empty <see cref="Dictionary"/>. /// </summary> public Dictionary() { - _safeHandle = new DictionarySafeHandle(godot_icall_Dictionary_Ctor()); + NativeValue = (godot_dictionary.movable)NativeFuncs.godotsharp_dictionary_new(); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } - /// <summary> - /// Constructs a new <see cref="Dictionary"/> from the given dictionary's elements. - /// </summary> - /// <param name="dictionary">The dictionary to construct from.</param> - /// <returns>A new Godot Dictionary.</returns> - public Dictionary(IDictionary dictionary) : this() - { - if (dictionary == null) - throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'"); - - foreach (DictionaryEntry entry in dictionary) - Add(entry.Key, entry.Value); - } - - internal Dictionary(DictionarySafeHandle handle) + private Dictionary(godot_dictionary nativeValueToOwn) { - _safeHandle = handle; + NativeValue = (godot_dictionary.movable)(nativeValueToOwn.IsAllocated ? + nativeValueToOwn : + NativeFuncs.godotsharp_dictionary_new()); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } - internal Dictionary(IntPtr handle) - { - _safeHandle = new DictionarySafeHandle(handle); - } + // Explicit name to make it very clear + internal static Dictionary CreateTakingOwnershipOfDisposableValue(godot_dictionary nativeValueToOwn) + => new Dictionary(nativeValueToOwn); - internal IntPtr GetPtr() + ~Dictionary() { - if (_disposed) - throw new ObjectDisposedException(GetType().FullName); - - return _safeHandle.DangerousGetHandle(); + Dispose(false); } /// <summary> @@ -81,16 +52,19 @@ namespace Godot.Collections /// </summary> public void Dispose() { - if (_disposed) - return; + Dispose(true); + GC.SuppressFinalize(this); + } - if (_safeHandle != null) + public void Dispose(bool disposing) + { + // Always dispose `NativeValue` even if disposing is true + NativeValue.DangerousSelfRef.Dispose(); + + if (_weakReferenceToSelf != null) { - _safeHandle.Dispose(); - _safeHandle = null; + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); } - - _disposed = true; } /// <summary> @@ -100,7 +74,10 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary Duplicate(bool deep = false) { - return new Dictionary(godot_icall_Dictionary_Duplicate(GetPtr(), deep)); + godot_dictionary newDictionary; + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_duplicate(ref self, deep.ToGodotBool(), out newDictionary); + return CreateTakingOwnershipOfDisposableValue(newDictionary); } // IDictionary @@ -108,176 +85,250 @@ namespace Godot.Collections /// <summary> /// Gets the collection of keys in this <see cref="Dictionary"/>. /// </summary> - public ICollection Keys + public ICollection<Variant> Keys { get { - IntPtr handle = godot_icall_Dictionary_Keys(GetPtr()); - return new Array(new ArraySafeHandle(handle)); + godot_array keysArray; + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_keys(ref self, out keysArray); + return Array.CreateTakingOwnershipOfDisposableValue(keysArray); } } /// <summary> /// Gets the collection of elements in this <see cref="Dictionary"/>. /// </summary> - public ICollection Values + public ICollection<Variant> Values { get { - IntPtr handle = godot_icall_Dictionary_Values(GetPtr()); - return new Array(new ArraySafeHandle(handle)); + godot_array valuesArray; + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray); + return Array.CreateTakingOwnershipOfDisposableValue(valuesArray); } } + IEnumerable<Variant> IReadOnlyDictionary<Variant, Variant>.Keys => Keys; + + IEnumerable<Variant> IReadOnlyDictionary<Variant, Variant>.Values => Values; + private (Array keys, Array values, int count) GetKeyValuePairs() { - int count = godot_icall_Dictionary_KeyValuePairs(GetPtr(), out IntPtr keysHandle, out IntPtr valuesHandle); - Array keys = new Array(new ArraySafeHandle(keysHandle)); - Array values = new Array(new ArraySafeHandle(valuesHandle)); - return (keys, values, count); - } + var self = (godot_dictionary)NativeValue; - bool IDictionary.IsFixedSize => false; + godot_array keysArray; + NativeFuncs.godotsharp_dictionary_keys(ref self, out keysArray); + var keys = Array.CreateTakingOwnershipOfDisposableValue(keysArray); - bool IDictionary.IsReadOnly => false; + godot_array valuesArray; + NativeFuncs.godotsharp_dictionary_keys(ref self, out valuesArray); + var values = Array.CreateTakingOwnershipOfDisposableValue(valuesArray); + + int count = NativeFuncs.godotsharp_dictionary_count(ref self); + + return (keys, values, count); + } /// <summary> - /// Returns the object at the given <paramref name="key"/>. + /// Returns the value at the given <paramref name="key"/>. /// </summary> - /// <value>The object at the given <paramref name="key"/>.</value> - public object this[object key] + /// <value>The value at the given <paramref name="key"/>.</value> + public Variant this[Variant key] { - get => godot_icall_Dictionary_GetValue(GetPtr(), key); - set => godot_icall_Dictionary_SetValue(GetPtr(), key, value); + get + { + var self = (godot_dictionary)NativeValue; + + if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + (godot_variant)key.NativeVar, out godot_variant value).ToBool()) + { + return Variant.CreateTakingOwnershipOfDisposableValue(value); + } + else + { + throw new KeyNotFoundException(); + } + } + set + { + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_set_value(ref self, + (godot_variant)key.NativeVar, (godot_variant)value.NativeVar); + } } /// <summary> - /// Adds an object <paramref name="value"/> at key <paramref name="key"/> + /// Adds an value <paramref name="value"/> at key <paramref name="key"/> /// to this <see cref="Dictionary"/>. /// </summary> - /// <param name="key">The key at which to add the object.</param> - /// <param name="value">The object to add.</param> - public void Add(object key, object value) => godot_icall_Dictionary_Add(GetPtr(), key, value); + /// <param name="key">The key at which to add the value.</param> + /// <param name="value">The value to add.</param> + public void Add(Variant key, Variant value) + { + var variantKey = (godot_variant)key.NativeVar; + var self = (godot_dictionary)NativeValue; + + if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool()) + throw new ArgumentException("An element with the same key already exists", nameof(key)); + + godot_variant variantValue = (godot_variant)value.NativeVar; + NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue); + } + + void ICollection<KeyValuePair<Variant, Variant>>.Add(KeyValuePair<Variant, Variant> item) + => Add(item.Key, item.Value); /// <summary> /// Erases all items from this <see cref="Dictionary"/>. /// </summary> - public void Clear() => godot_icall_Dictionary_Clear(GetPtr()); + public void Clear() + { + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_clear(ref self); + } /// <summary> /// Checks if this <see cref="Dictionary"/> contains the given key. /// </summary> /// <param name="key">The key to look for.</param> /// <returns>Whether or not this dictionary contains the given key.</returns> - public bool Contains(object key) => godot_icall_Dictionary_ContainsKey(GetPtr(), key); + public bool ContainsKey(Variant key) + { + var self = (godot_dictionary)NativeValue; + return NativeFuncs.godotsharp_dictionary_contains_key(ref self, (godot_variant)key.NativeVar).ToBool(); + } - /// <summary> - /// Gets an enumerator for this <see cref="Dictionary"/>. - /// </summary> - /// <returns>An enumerator.</returns> - public IDictionaryEnumerator GetEnumerator() => new DictionaryEnumerator(this); + public bool Contains(KeyValuePair<Variant, Variant> item) + { + godot_variant variantKey = (godot_variant)item.Key.NativeVar; + var self = (godot_dictionary)NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + { + if (!found) + return false; + + godot_variant variantValue = (godot_variant)item.Value.NativeVar; + return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool(); + } + } /// <summary> /// Removes an element from this <see cref="Dictionary"/> by key. /// </summary> /// <param name="key">The key of the element to remove.</param> - public void Remove(object key) => godot_icall_Dictionary_RemoveKey(GetPtr(), key); + public bool Remove(Variant key) + { + var self = (godot_dictionary)NativeValue; + return NativeFuncs.godotsharp_dictionary_remove_key(ref self, (godot_variant)key.NativeVar).ToBool(); + } - // ICollection + public bool Remove(KeyValuePair<Variant, Variant> item) + { + godot_variant variantKey = (godot_variant)item.Key.NativeVar; + var self = (godot_dictionary)NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + { + if (!found) + return false; - object ICollection.SyncRoot => this; + godot_variant variantValue = (godot_variant)item.Value.NativeVar; + if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool()) + { + return NativeFuncs.godotsharp_dictionary_remove_key( + ref self, variantKey).ToBool(); + } - bool ICollection.IsSynchronized => false; + return false; + } + } /// <summary> /// Returns the number of elements in this <see cref="Dictionary"/>. /// This is also known as the size or length of the dictionary. /// </summary> /// <returns>The number of elements.</returns> - public int Count => godot_icall_Dictionary_Count(GetPtr()); + public int Count + { + get + { + var self = (godot_dictionary)NativeValue; + return NativeFuncs.godotsharp_dictionary_count(ref self); + } + } + + bool ICollection<KeyValuePair<Variant, Variant>>.IsReadOnly => false; + + public bool TryGetValue(Variant key, out Variant value) + { + var self = (godot_dictionary)NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + (godot_variant)key.NativeVar, out godot_variant retValue).ToBool(); + + value = found ? Variant.CreateTakingOwnershipOfDisposableValue(retValue) : default; + + return found; + } /// <summary> - /// Copies the elements of this <see cref="Dictionary"/> to the given - /// untyped C# array, starting at the given index. + /// Copies the elements of this <see cref="Dictionary"/> to the given untyped + /// <see cref="KeyValuePair{TKey, TValue}"/> array, starting at the given index. /// </summary> /// <param name="array">The array to copy to.</param> - /// <param name="index">The index to start at.</param> - public void CopyTo(System.Array array, int index) + /// <param name="arrayIndex">The index to start at.</param> + public void CopyTo(KeyValuePair<Variant, Variant>[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException(nameof(array), "Value cannot be null."); - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index), "Number was less than the array's lower bound in the first dimension."); + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); var (keys, values, count) = GetKeyValuePairs(); - if (array.Length < (index + count)) - throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + if (array.Length < (arrayIndex + count)) + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); for (int i = 0; i < count; i++) { - array.SetValue(new DictionaryEntry(keys[i], values[i]), index); - index++; + array[arrayIndex] = new(keys[i], values[i]); + arrayIndex++; } } // IEnumerable - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private class DictionaryEnumerator : IDictionaryEnumerator + /// <summary> + /// Gets an enumerator for this <see cref="Dictionary"/>. + /// </summary> + /// <returns>An enumerator.</returns> + public IEnumerator<KeyValuePair<Variant, Variant>> GetEnumerator() { - private readonly Dictionary _dictionary; - private readonly int _count; - private int _index = -1; - private bool _dirty = true; - - private DictionaryEntry _entry; - - public DictionaryEnumerator(Dictionary dictionary) - { - _dictionary = dictionary; - _count = dictionary.Count; - } - - public object Current => Entry; - - public DictionaryEntry Entry - { - get - { - if (_dirty) - { - UpdateEntry(); - } - return _entry; - } - } - - private void UpdateEntry() + for (int i = 0; i < Count; i++) { - _dirty = false; - godot_icall_Dictionary_KeyValuePairAt(_dictionary.GetPtr(), _index, out object key, out object value); - _entry = new DictionaryEntry(key, value); + yield return GetKeyValuePair(i); } + } - public object Key => Entry.Key; - - public object Value => Entry.Value; - - public bool MoveNext() - { - _index++; - _dirty = true; - return _index < _count; - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public void Reset() - { - _index = -1; - _dirty = true; - } + private KeyValuePair<Variant, Variant> GetKeyValuePair(int index) + { + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index, + out godot_variant key, + out godot_variant value); + return new KeyValuePair<Variant, Variant>(Variant.CreateTakingOwnershipOfDisposableValue(key), + Variant.CreateTakingOwnershipOfDisposableValue(value)); } /// <summary> @@ -286,74 +337,11 @@ namespace Godot.Collections /// <returns>A string representation of this dictionary.</returns> public override string ToString() { - return godot_icall_Dictionary_ToString(GetPtr()); + var self = (godot_dictionary)NativeValue; + NativeFuncs.godotsharp_dictionary_to_string(ref self, out godot_string str); + using (str) + return Marshaling.ConvertStringToManaged(str); } - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Dictionary_Ctor(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_Dtor(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_Dictionary_GetValue(IntPtr ptr, object key); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_Dictionary_GetValue_Generic(IntPtr ptr, object key, int valTypeEncoding, IntPtr valTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_SetValue(IntPtr ptr, object key, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Dictionary_Keys(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Dictionary_Values(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Dictionary_Count(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_Dictionary_KeyValuePairs(IntPtr ptr, out IntPtr keys, out IntPtr values); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_KeyValuePairAt(IntPtr ptr, int index, out object key, out object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_KeyValuePairAt_Generic(IntPtr ptr, int index, out object key, out object value, int valueTypeEncoding, IntPtr valueTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_Add(IntPtr ptr, object key, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_Clear(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_Contains(IntPtr ptr, object key, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_ContainsKey(IntPtr ptr, object key); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Dictionary_Duplicate(IntPtr ptr, bool deep); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_RemoveKey(IntPtr ptr, object key); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_Remove(IntPtr ptr, object key, object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_TryGetValue(IntPtr ptr, object key, out object value); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_Dictionary_TryGetValue_Generic(IntPtr ptr, object key, out object value, int valTypeEncoding, IntPtr valTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Dictionary_Generic_GetValueTypeInfo(Type valueType, out int valTypeEncoding, out IntPtr valTypeClass); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_Dictionary_ToString(IntPtr ptr); } /// <summary> @@ -364,16 +352,51 @@ namespace Godot.Collections /// </summary> /// <typeparam name="TKey">The type of the dictionary's keys.</typeparam> /// <typeparam name="TValue">The type of the dictionary's values.</typeparam> - public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue> + public class Dictionary<TKey, TValue> : + IDictionary<TKey, TValue>, + IReadOnlyDictionary<TKey, TValue> { - private readonly Dictionary _objectDict; + // ReSharper disable StaticMemberInGenericType + // Warning is about unique static fields being created for each generic type combination: + // https://www.jetbrains.com/help/resharper/StaticMemberInGenericType.html + // In our case this is exactly what we want. + + private static unsafe delegate* managed<in TKey, godot_variant> _convertKeyToVariantCallback; + private static unsafe delegate* managed<in godot_variant, TKey> _convertKeyToManagedCallback; + private static unsafe delegate* managed<in TValue, godot_variant> _convertValueToVariantCallback; + private static unsafe delegate* managed<in godot_variant, TValue> _convertValueToManagedCallback; + + // ReSharper restore StaticMemberInGenericType + + static unsafe Dictionary() + { + _convertKeyToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TKey>(); + _convertKeyToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TKey>(); + _convertValueToVariantCallback = VariantConversionCallbacks.GetToVariantCallback<TValue>(); + _convertValueToManagedCallback = VariantConversionCallbacks.GetToManagedCallback<TValue>(); + } + + private static unsafe void ValidateVariantConversionCallbacks() + { + if (_convertKeyToVariantCallback == null || _convertKeyToManagedCallback == null) + { + throw new InvalidOperationException( + $"The dictionary key type is not supported for conversion to Variant: '{typeof(TKey).FullName}'"); + } + + if (_convertValueToVariantCallback == null || _convertValueToManagedCallback == null) + { + throw new InvalidOperationException( + $"The dictionary value type is not supported for conversion to Variant: '{typeof(TValue).FullName}'"); + } + } - internal static int valTypeEncoding; - internal static IntPtr valTypeClass; + private readonly Dictionary _underlyingDict; - static Dictionary() + internal ref godot_dictionary.movable NativeValue { - Dictionary.godot_icall_Dictionary_Generic_GetValueTypeInfo(typeof(TValue), out valTypeEncoding, out valTypeClass); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref _underlyingDict.NativeValue; } /// <summary> @@ -381,7 +404,9 @@ namespace Godot.Collections /// </summary> public Dictionary() { - _objectDict = new Dictionary(); + ValidateVariantConversionCallbacks(); + + _underlyingDict = new Dictionary(); } /// <summary> @@ -391,19 +416,15 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(IDictionary<TKey, TValue> dictionary) { - _objectDict = new Dictionary(); + ValidateVariantConversionCallbacks(); if (dictionary == null) - throw new NullReferenceException($"Parameter '{nameof(dictionary)} cannot be null.'"); - - // TODO: Can be optimized + throw new ArgumentNullException(nameof(dictionary)); - IntPtr godotDictionaryPtr = GetPtr(); + _underlyingDict = new Dictionary(); foreach (KeyValuePair<TKey, TValue> entry in dictionary) - { - Dictionary.godot_icall_Dictionary_Add(godotDictionaryPtr, entry.Key, entry.Value); - } + Add(entry.Key, entry.Value); } /// <summary> @@ -413,18 +434,15 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary(Dictionary dictionary) { - _objectDict = dictionary; - } + ValidateVariantConversionCallbacks(); - internal Dictionary(IntPtr handle) - { - _objectDict = new Dictionary(handle); + _underlyingDict = dictionary; } - internal Dictionary(DictionarySafeHandle handle) - { - _objectDict = new Dictionary(handle); - } + // Explicit name to make it very clear + internal static Dictionary<TKey, TValue> CreateTakingOwnershipOfDisposableValue( + godot_dictionary nativeValueToOwn) + => new Dictionary<TKey, TValue>(Dictionary.CreateTakingOwnershipOfDisposableValue(nativeValueToOwn)); /// <summary> /// Converts this typed <see cref="Dictionary{TKey, TValue}"/> to an untyped <see cref="Dictionary"/>. @@ -432,12 +450,7 @@ namespace Godot.Collections /// <param name="from">The typed dictionary to convert.</param> public static explicit operator Dictionary(Dictionary<TKey, TValue> from) { - return from._objectDict; - } - - internal IntPtr GetPtr() - { - return _objectDict.GetPtr(); + return from?._underlyingDict; } /// <summary> @@ -447,7 +460,7 @@ namespace Godot.Collections /// <returns>A new Godot Dictionary.</returns> public Dictionary<TKey, TValue> Duplicate(bool deep = false) { - return new Dictionary<TKey, TValue>(_objectDict.Duplicate(deep)); + return new Dictionary<TKey, TValue>(_underlyingDict.Duplicate(deep)); } // IDictionary<TKey, TValue> @@ -456,10 +469,32 @@ namespace Godot.Collections /// Returns the value at the given <paramref name="key"/>. /// </summary> /// <value>The value at the given <paramref name="key"/>.</value> - public TValue this[TKey key] + public unsafe TValue this[TKey key] { - get { return (TValue)Dictionary.godot_icall_Dictionary_GetValue_Generic(_objectDict.GetPtr(), key, valTypeEncoding, valTypeClass); } - set { _objectDict[key] = value; } + get + { + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + + if (NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant value).ToBool()) + { + using (value) + return _convertValueToManagedCallback(value); + } + else + { + throw new KeyNotFoundException(); + } + } + set + { + using var variantKey = _convertKeyToVariantCallback(key); + using var variantValue = _convertValueToVariantCallback(value); + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_set_value(ref self, + variantKey, variantValue); + } } /// <summary> @@ -469,8 +504,10 @@ namespace Godot.Collections { get { - IntPtr handle = Dictionary.godot_icall_Dictionary_Keys(_objectDict.GetPtr()); - return new Array<TKey>(new ArraySafeHandle(handle)); + godot_array keyArray; + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_keys(ref self, out keyArray); + return Array<TKey>.CreateTakingOwnershipOfDisposableValue(keyArray); } } @@ -481,15 +518,30 @@ namespace Godot.Collections { get { - IntPtr handle = Dictionary.godot_icall_Dictionary_Values(_objectDict.GetPtr()); - return new Array<TValue>(new ArraySafeHandle(handle)); + godot_array valuesArray; + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_values(ref self, out valuesArray); + return Array<TValue>.CreateTakingOwnershipOfDisposableValue(valuesArray); } } - private KeyValuePair<TKey, TValue> GetKeyValuePair(int index) + IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Keys; + + IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Values; + + private unsafe KeyValuePair<TKey, TValue> GetKeyValuePair(int index) { - Dictionary.godot_icall_Dictionary_KeyValuePairAt_Generic(GetPtr(), index, out object key, out object value, valTypeEncoding, valTypeClass); - return new KeyValuePair<TKey, TValue>((TKey)key, (TValue)value); + var self = (godot_dictionary)_underlyingDict.NativeValue; + NativeFuncs.godotsharp_dictionary_key_value_pair_at(ref self, index, + out godot_variant key, + out godot_variant value); + using (key) + using (value) + { + return new KeyValuePair<TKey, TValue>( + _convertKeyToManagedCallback(key), + _convertValueToManagedCallback(value)); + } } /// <summary> @@ -498,9 +550,16 @@ namespace Godot.Collections /// </summary> /// <param name="key">The key at which to add the object.</param> /// <param name="value">The object to add.</param> - public void Add(TKey key, TValue value) + public unsafe void Add(TKey key, TValue value) { - _objectDict.Add(key, value); + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + + if (NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool()) + throw new ArgumentException("An element with the same key already exists", nameof(key)); + + using var variantValue = _convertValueToVariantCallback(value); + NativeFuncs.godotsharp_dictionary_add(ref self, variantKey, variantValue); } /// <summary> @@ -508,18 +567,22 @@ namespace Godot.Collections /// </summary> /// <param name="key">The key to look for.</param> /// <returns>Whether or not this dictionary contains the given key.</returns> - public bool ContainsKey(TKey key) + public unsafe bool ContainsKey(TKey key) { - return _objectDict.Contains(key); + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + return NativeFuncs.godotsharp_dictionary_contains_key(ref self, variantKey).ToBool(); } /// <summary> /// Removes an element from this <see cref="Dictionary{TKey, TValue}"/> by key. /// </summary> /// <param name="key">The key of the element to remove.</param> - public bool Remove(TKey key) + public unsafe bool Remove(TKey key) { - return Dictionary.godot_icall_Dictionary_RemoveKey(GetPtr(), key); + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + return NativeFuncs.godotsharp_dictionary_remove_key(ref self, variantKey).ToBool(); } /// <summary> @@ -528,10 +591,16 @@ namespace Godot.Collections /// <param name="key">The key of the element to get.</param> /// <param name="value">The value at the given <paramref name="key"/>.</param> /// <returns>If an object was found for the given <paramref name="key"/>.</returns> - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) + public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { - bool found = Dictionary.godot_icall_Dictionary_TryGetValue_Generic(GetPtr(), key, out object retValue, valTypeEncoding, valTypeClass); - value = found ? (TValue)retValue : default; + using var variantKey = _convertKeyToVariantCallback(key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + value = found ? _convertValueToManagedCallback(retValue) : default; + return found; } @@ -542,29 +611,33 @@ namespace Godot.Collections /// This is also known as the size or length of the dictionary. /// </summary> /// <returns>The number of elements.</returns> - public int Count - { - get { return _objectDict.Count; } - } + public int Count => _underlyingDict.Count; bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => false; void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) - { - _objectDict.Add(item.Key, item.Value); - } + => Add(item.Key, item.Value); /// <summary> /// Erases all the items from this <see cref="Dictionary{TKey, TValue}"/>. /// </summary> - public void Clear() - { - _objectDict.Clear(); - } + public void Clear() => _underlyingDict.Clear(); - bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) + unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) { - return _objectDict.Contains(new KeyValuePair<object, object>(item.Key, item.Value)); + using var variantKey = _convertKeyToVariantCallback(item.Key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + { + if (!found) + return false; + + using var variantValue = _convertValueToVariantCallback(item.Value); + return NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool(); + } } /// <summary> @@ -579,12 +652,14 @@ namespace Godot.Collections throw new ArgumentNullException(nameof(array), "Value cannot be null."); if (arrayIndex < 0) - throw new ArgumentOutOfRangeException(nameof(arrayIndex), "Number was less than the array's lower bound in the first dimension."); + throw new ArgumentOutOfRangeException(nameof(arrayIndex), + "Number was less than the array's lower bound in the first dimension."); int count = Count; if (array.Length < (arrayIndex + count)) - throw new ArgumentException("Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); + throw new ArgumentException( + "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); for (int i = 0; i < count; i++) { @@ -593,10 +668,27 @@ namespace Godot.Collections } } - bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) + unsafe bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { - return Dictionary.godot_icall_Dictionary_Remove(GetPtr(), item.Key, item.Value); - ; + using var variantKey = _convertKeyToVariantCallback(item.Key); + var self = (godot_dictionary)_underlyingDict.NativeValue; + bool found = NativeFuncs.godotsharp_dictionary_try_get_value(ref self, + variantKey, out godot_variant retValue).ToBool(); + + using (retValue) + { + if (!found) + return false; + + using var variantValue = _convertValueToVariantCallback(item.Value); + if (NativeFuncs.godotsharp_variant_equals(variantValue, retValue).ToBool()) + { + return NativeFuncs.godotsharp_dictionary_remove_key( + ref self, variantKey).ToBool(); + } + + return false; + } } // IEnumerable<KeyValuePair<TKey, TValue>> @@ -613,15 +705,18 @@ namespace Godot.Collections } } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// <summary> /// Converts this <see cref="Dictionary{TKey, TValue}"/> to a string. /// </summary> /// <returns>A string representation of this dictionary.</returns> - public override string ToString() => _objectDict.ToString(); + public override string ToString() => _underlyingDict.ToString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Dictionary<TKey, TValue> from) => Variant.CreateFrom(from); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Dictionary<TKey, TValue>(Variant from) => from.AsGodotDictionary<TKey, TValue>(); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs index 6475237002..e6cb4171a7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Dispatcher.cs @@ -1,20 +1,16 @@ -using System.Runtime.CompilerServices; +using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { public static class Dispatcher { - /// <summary> - /// Implements an external instance of GodotTaskScheduler. - /// </summary> - /// <returns>A GodotTaskScheduler instance.</returns> - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern GodotTaskScheduler godot_icall_DefaultGodotTaskScheduler(); + internal static GodotTaskScheduler DefaultGodotTaskScheduler; - /// <summary> - /// Initializes the synchronization context as the context of the GodotTaskScheduler. - /// </summary> - public static GodotSynchronizationContext SynchronizationContext => - godot_icall_DefaultGodotTaskScheduler().Context; + internal static void InitializeDefaultGodotTaskScheduler() + => DefaultGodotTaskScheduler = new GodotTaskScheduler(); + + public static GodotSynchronizationContext SynchronizationContext => DefaultGodotTaskScheduler.Context; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs new file mode 100644 index 0000000000..75793ea446 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/DisposablesTracker.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using Godot.NativeInterop; + +#nullable enable + +namespace Godot +{ + internal static class DisposablesTracker + { + [UnmanagedCallersOnly] + internal static void OnGodotShuttingDown() + { + try + { + OnGodotShuttingDownImpl(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } + } + + private static void OnGodotShuttingDownImpl() + { + bool isStdoutVerbose; + + try + { + isStdoutVerbose = OS.IsStdoutVerbose(); + } + catch (ObjectDisposedException) + { + // OS singleton already disposed. Maybe OnUnloading was called twice. + isStdoutVerbose = false; + } + + if (isStdoutVerbose) + GD.Print("Unloading: Disposing tracked instances..."); + + // Dispose Godot Objects first, and only then dispose other disposables + // like StringName, NodePath, Godot.Collections.Array/Dictionary, etc. + // The Godot Object Dispose() method may need any of the later instances. + + foreach (WeakReference<Object> item in GodotObjectInstances.Keys) + { + if (item.TryGetTarget(out Object? self)) + self.Dispose(); + } + + foreach (WeakReference<IDisposable> item in OtherInstances.Keys) + { + if (item.TryGetTarget(out IDisposable? self)) + self.Dispose(); + } + + if (isStdoutVerbose) + GD.Print("Unloading: Finished disposing tracked instances."); + } + + // ReSharper disable once RedundantNameQualifier + private static ConcurrentDictionary<WeakReference<Godot.Object>, byte> GodotObjectInstances { get; } = + new(); + + private static ConcurrentDictionary<WeakReference<IDisposable>, byte> OtherInstances { get; } = + new(); + + public static WeakReference<Object> RegisterGodotObject(Object godotObject) + { + var weakReferenceToSelf = new WeakReference<Object>(godotObject); + GodotObjectInstances.TryAdd(weakReferenceToSelf, 0); + return weakReferenceToSelf; + } + + public static WeakReference<IDisposable> RegisterDisposable(IDisposable disposable) + { + var weakReferenceToSelf = new WeakReference<IDisposable>(disposable); + OtherInstances.TryAdd(weakReferenceToSelf, 0); + return weakReferenceToSelf; + } + + public static void UnregisterGodotObject(Object godotObject, WeakReference<Object> weakReferenceToSelf) + { + if (!GodotObjectInstances.TryRemove(weakReferenceToSelf, out _)) + throw new ArgumentException("Godot Object not registered", nameof(weakReferenceToSelf)); + } + + public static void UnregisterDisposable(WeakReference<IDisposable> weakReference) + { + if (!OtherInstances.TryRemove(weakReference, out _)) + throw new ArgumentException("Disposable not registered", nameof(weakReference)); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs deleted file mode 100644 index 26d5f9c796..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/DynamicObject.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Dynamic; -using System.Linq.Expressions; -using System.Runtime.CompilerServices; - -namespace Godot -{ - /// <summary> - /// Represents an <see cref="Object"/> whose members can be dynamically accessed at runtime through the Variant API. - /// </summary> - /// <remarks> - /// <para> - /// The <see cref="DynamicGodotObject"/> class enables access to the Variant - /// members of a <see cref="Object"/> instance at runtime. - /// </para> - /// <para> - /// This allows accessing the class members using their original names in the engine as well as the members from the - /// script attached to the <see cref="Object"/>, regardless of the scripting language it was written in. - /// </para> - /// </remarks> - /// <example> - /// This sample shows how to use <see cref="DynamicGodotObject"/> to dynamically access the engine members of a <see cref="Object"/>. - /// <code> - /// dynamic sprite = GetNode("Sprite2D").DynamicGodotObject; - /// sprite.add_child(this); - /// - /// if ((sprite.hframes * sprite.vframes) > 0) - /// sprite.frame = 0; - /// </code> - /// </example> - /// <example> - /// This sample shows how to use <see cref="DynamicGodotObject"/> to dynamically access the members of the script attached to a <see cref="Object"/>. - /// <code> - /// dynamic childNode = GetNode("ChildNode").DynamicGodotObject; - /// - /// if (childNode.print_allowed) - /// { - /// childNode.message = "Hello from C#"; - /// childNode.print_message(3); - /// } - /// </code> - /// The <c>ChildNode</c> node has the following GDScript script attached: - /// <code> - /// // # ChildNode.gd - /// // var print_allowed = true - /// // var message = "" - /// // - /// // func print_message(times): - /// // for i in times: - /// // print(message) - /// </code> - /// </example> - public class DynamicGodotObject : DynamicObject - { - /// <summary> - /// Gets the <see cref="Object"/> associated with this <see cref="DynamicGodotObject"/>. - /// </summary> - public Object Value { get; } - - /// <summary> - /// Initializes a new instance of the <see cref="DynamicGodotObject"/> class. - /// </summary> - /// <param name="godotObject"> - /// The <see cref="Object"/> that will be associated with this <see cref="DynamicGodotObject"/>. - /// </param> - /// <exception cref="ArgumentNullException"> - /// Thrown when the <paramref name="godotObject"/> parameter is <see langword="null"/>. - /// </exception> - public DynamicGodotObject(Object godotObject) - { - if (godotObject == null) - throw new ArgumentNullException(nameof(godotObject)); - - Value = godotObject; - } - - /// <inheritdoc/> - public override IEnumerable<string> GetDynamicMemberNames() - { - return godot_icall_DynamicGodotObject_SetMemberList(Object.GetPtr(Value)); - } - - /// <inheritdoc/> - public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result) - { - switch (binder.Operation) - { - case ExpressionType.Equal: - case ExpressionType.NotEqual: - if (binder.ReturnType == typeof(bool) || binder.ReturnType.IsAssignableFrom(typeof(bool))) - { - if (arg == null) - { - bool boolResult = Object.IsInstanceValid(Value); - - if (binder.Operation == ExpressionType.Equal) - boolResult = !boolResult; - - result = boolResult; - return true; - } - - if (arg is Object other) - { - bool boolResult = (Value == other); - - if (binder.Operation == ExpressionType.NotEqual) - boolResult = !boolResult; - - result = boolResult; - return true; - } - } - - break; - default: - // We're not implementing operators <, <=, >, and >= (LessThan, LessThanOrEqual, GreaterThan, GreaterThanOrEqual). - // These are used on the actual pointers in variant_op.cpp. It's better to let the user do that explicitly. - break; - } - - return base.TryBinaryOperation(binder, arg, out result); - } - - /// <inheritdoc/> - public override bool TryConvert(ConvertBinder binder, out object result) - { - if (binder.Type == typeof(Object)) - { - result = Value; - return true; - } - - if (typeof(Object).IsAssignableFrom(binder.Type)) - { - // Throws InvalidCastException when the cast fails - result = Convert.ChangeType(Value, binder.Type); - return true; - } - - return base.TryConvert(binder, out result); - } - - /// <inheritdoc/> - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - if (indexes.Length == 1) - { - if (indexes[0] is string name) - { - return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), name, out result); - } - } - - return base.TryGetIndex(binder, indexes, out result); - } - - /// <inheritdoc/> - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - return godot_icall_DynamicGodotObject_GetMember(Object.GetPtr(Value), binder.Name, out result); - } - - /// <inheritdoc/> - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) - { - return godot_icall_DynamicGodotObject_InvokeMember(Object.GetPtr(Value), binder.Name, args, out result); - } - - /// <inheritdoc/> - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) - { - if (indexes.Length == 1) - { - if (indexes[0] is string name) - { - return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), name, value); - } - } - - return base.TrySetIndex(binder, indexes, value); - } - - /// <inheritdoc/> - public override bool TrySetMember(SetMemberBinder binder, object value) - { - return godot_icall_DynamicGodotObject_SetMember(Object.GetPtr(Value), binder.Name, value); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string[] godot_icall_DynamicGodotObject_SetMemberList(IntPtr godotObject); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_DynamicGodotObject_InvokeMember(IntPtr godotObject, string name, object[] args, out object result); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_DynamicGodotObject_GetMember(IntPtr godotObject, string name, out object result); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_DynamicGodotObject_SetMember(IntPtr godotObject, string name, object value); - - #region We don't override these methods - - // Looks like this is not usable from C# - //public override bool TryCreateInstance(CreateInstanceBinder binder, object[] args, out object result); - - // Object members cannot be deleted - //public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes); - //public override bool TryDeleteMember(DeleteMemberBinder binder); - - // Invocation on the object itself, e.g.: obj(param) - //public override bool TryInvoke(InvokeBinder binder, object[] args, out object result); - - // No unnary operations to handle - //public override bool TryUnaryOperation(UnaryOperationBinder binder, out object result); - - #endregion - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs index 691fd85964..4094ceeb22 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/ObjectExtensions.cs @@ -1,5 +1,5 @@ using System; -using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot { @@ -32,10 +32,17 @@ namespace Godot /// </returns> public static WeakRef WeakRef(Object obj) { - return godot_icall_Object_weakref(GetPtr(obj)); - } + if (!IsInstanceValid(obj)) + return null; + + NativeFuncs.godotsharp_weakref(GetPtr(obj), out godot_ref weakRef); + using (weakRef) + { + if (weakRef.IsNull) + return null; - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern WeakRef godot_icall_Object_weakref(IntPtr obj); + return (WeakRef)InteropUtils.UnmanagedGetManaged(weakRef.Reference); + } + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs deleted file mode 100644 index df130a5c77..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Extensions/SceneTreeExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using Godot.Collections; - -namespace Godot -{ - public partial class SceneTree - { - /// <summary> - /// Returns a list of all nodes assigned to the given <paramref name="group"/>. - /// </summary> - /// <typeparam name="T">The type to cast to. Should be a descendant of <see cref="Node"/>.</typeparam> - public Array<T> GetNodesInGroup<T>(StringName group) where T : class - { - return new Array<T>(godot_icall_SceneTree_get_nodes_in_group_Generic(GetPtr(this), StringName.GetPtr(group), typeof(T))); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_SceneTree_get_nodes_in_group_Generic(IntPtr obj, IntPtr group, Type elemType); - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 236d0666bc..9348cc1d00 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -1,11 +1,6 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot { @@ -26,9 +21,11 @@ namespace Godot /// <param name="bytes">Byte array that will be decoded to a <c>Variant</c>.</param> /// <param name="allowObjects">If objects should be decoded.</param> /// <returns>The decoded <c>Variant</c>.</returns> - public static object Bytes2Var(byte[] bytes, bool allowObjects = false) + public static Variant Bytes2Var(Span<byte> bytes, bool allowObjects = false) { - return godot_icall_GD_bytes2var(bytes, allowObjects); + using var varBytes = Marshaling.ConvertSystemArrayToNativePackedByteArray(bytes); + NativeFuncs.godotsharp_bytes2var(varBytes, allowObjects.ToGodotBool(), out godot_variant ret); + return Variant.CreateTakingOwnershipOfDisposableValue(ret); } /// <summary> @@ -46,9 +43,10 @@ namespace Godot /// </code> /// </example> /// <returns>The <c>Variant</c> converted to the given <paramref name="type"/>.</returns> - public static object Convert(object what, Variant.Type type) + public static Variant Convert(Variant what, Variant.Type type) { - return godot_icall_GD_convert(what, type); + NativeFuncs.godotsharp_convert((godot_variant)what.NativeVar, (int)type, out godot_variant ret); + return Variant.CreateTakingOwnershipOfDisposableValue(ret); } /// <summary> @@ -62,7 +60,7 @@ namespace Godot return (real_t)Math.Exp(db * 0.11512925464970228420089957273422); } - private static object[] GetPrintParams(object[] parameters) + private static string[] GetPrintParams(object[] parameters) { if (parameters == null) { @@ -82,9 +80,9 @@ namespace Godot /// </example> /// <param name="var">Variable that will be hashed.</param> /// <returns>Hash of the variable passed.</returns> - public static int Hash(object var) + public static int Hash(Variant var) { - return godot_icall_GD_hash(var); + return NativeFuncs.godotsharp_hash((godot_variant)var.NativeVar); } /// <summary> @@ -110,7 +108,7 @@ namespace Godot /// <returns>The <see cref="Object"/> instance.</returns> public static Object InstanceFromId(ulong instanceId) { - return godot_icall_GD_instance_from_id(instanceId); + return InteropUtils.UnmanagedGetManaged(NativeFuncs.godotsharp_instance_from_id(instanceId)); } /// <summary> @@ -202,7 +200,8 @@ namespace Godot /// <param name="message">Error message.</param> public static void PushError(string message) { - godot_icall_GD_pusherror(message); + using var godotStr = Marshaling.ConvertStringToNative(message); + NativeFuncs.godotsharp_pusherror(godotStr); } /// <summary> @@ -214,7 +213,8 @@ namespace Godot /// <param name="message">Warning message.</param> public static void PushWarning(string message) { - godot_icall_GD_pushwarning(message); + using var godotStr = Marshaling.ConvertStringToNative(message); + NativeFuncs.godotsharp_pushwarning(godotStr); } /// <summary> @@ -235,7 +235,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void Print(params object[] what) { - godot_icall_GD_print(GetPrintParams(what)); + string str = string.Concat(GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_print(godotStr); } /// <summary> @@ -264,7 +266,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintRich(params object[] what) { - godot_icall_GD_print_rich(GetPrintParams(what)); + string str = string.Concat(GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_print_rich(godotStr); } /// <summary> @@ -286,7 +290,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintErr(params object[] what) { - godot_icall_GD_printerr(GetPrintParams(what)); + string str = string.Concat(GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_printerr(godotStr); } /// <summary> @@ -306,7 +312,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintRaw(params object[] what) { - godot_icall_GD_printraw(GetPrintParams(what)); + string str = string.Concat(GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_printraw(godotStr); } /// <summary> @@ -320,7 +328,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintS(params object[] what) { - godot_icall_GD_prints(GetPrintParams(what)); + string str = string.Join(' ', GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_prints(godotStr); } /// <summary> @@ -334,7 +344,9 @@ namespace Godot /// <param name="what">Arguments that will be printed.</param> public static void PrintT(params object[] what) { - godot_icall_GD_printt(GetPrintParams(what)); + string str = string.Join('\t', GetPrintParams(what)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_printt(godotStr); } /// <summary> @@ -348,7 +360,7 @@ namespace Godot /// <returns>A random <see langword="float"/> number.</returns> public static float Randf() { - return godot_icall_GD_randf(); + return NativeFuncs.godotsharp_randf(); } /// <summary> @@ -358,7 +370,7 @@ namespace Godot /// <returns>A random normally-distributed <see langword="float"/> number.</returns> public static double Randfn(double mean, double deviation) { - return godot_icall_GD_randfn(mean, deviation); + return NativeFuncs.godotsharp_randfn(mean, deviation); } /// <summary> @@ -376,7 +388,7 @@ namespace Godot /// <returns>A random <see langword="uint"/> number.</returns> public static uint Randi() { - return godot_icall_GD_randi(); + return NativeFuncs.godotsharp_randi(); } /// <summary> @@ -389,7 +401,7 @@ namespace Godot /// </summary> public static void Randomize() { - godot_icall_GD_randomize(); + NativeFuncs.godotsharp_randomize(); } /// <summary> @@ -404,7 +416,7 @@ namespace Godot /// <returns>A random <see langword="double"/> number inside the given range.</returns> public static double RandRange(double from, double to) { - return godot_icall_GD_randf_range(from, to); + return NativeFuncs.godotsharp_randf_range(from, to); } /// <summary> @@ -421,7 +433,7 @@ namespace Godot /// <returns>A random <see langword="int"/> number inside the given range.</returns> public static int RandRange(int from, int to) { - return godot_icall_GD_randi_range(from, to); + return NativeFuncs.godotsharp_randi_range(from, to); } /// <summary> @@ -434,7 +446,7 @@ namespace Godot /// <returns>A random <see langword="uint"/> number.</returns> public static uint RandFromSeed(ref ulong seed) { - return godot_icall_GD_rand_seed(seed, out seed); + return NativeFuncs.godotsharp_rand_from_seed(seed, out seed); } /// <summary> @@ -491,7 +503,7 @@ namespace Godot /// <param name="seed">Seed that will be used.</param> public static void Seed(ulong seed) { - godot_icall_GD_seed(seed); + NativeFuncs.godotsharp_seed(seed); } /// <summary> @@ -499,13 +511,16 @@ namespace Godot /// </summary> /// <param name="what">Arguments that will converted to string.</param> /// <returns>The string formed by the given arguments.</returns> - public static string Str(params object[] what) + public static string Str(params Variant[] what) { - return godot_icall_GD_str(what); + using var whatGodot = new Godot.Collections.Array(what); + NativeFuncs.godotsharp_str((godot_array)whatGodot.NativeValue, out godot_string ret); + using (ret) + return Marshaling.ConvertStringToManaged(ret); } /// <summary> - /// Converts a formatted string that was returned by <see cref="Var2Str(object)"/> to the original value. + /// Converts a formatted string that was returned by <see cref="Var2Str(Variant)"/> to the original value. /// </summary> /// <example> /// <code> @@ -516,32 +531,27 @@ namespace Godot /// </example> /// <param name="str">String that will be converted to Variant.</param> /// <returns>The decoded <c>Variant</c>.</returns> - public static object Str2Var(string str) + public static Variant Str2Var(string str) { - return godot_icall_GD_str2var(str); - } - - /// <summary> - /// Returns whether the given class exists in <see cref="ClassDB"/>. - /// </summary> - /// <returns>If the class exists in <see cref="ClassDB"/>.</returns> - public static bool TypeExists(StringName type) - { - return godot_icall_GD_type_exists(StringName.GetPtr(type)); + using var godotStr = Marshaling.ConvertStringToNative(str); + NativeFuncs.godotsharp_str2var(godotStr, out godot_variant ret); + return Variant.CreateTakingOwnershipOfDisposableValue(ret); } /// <summary> /// Encodes a <c>Variant</c> value to a byte array. /// If <paramref name="fullObjects"/> is <see langword="true"/> encoding objects is allowed /// (and can potentially include code). - /// Deserialization can be done with <see cref="Bytes2Var(byte[], bool)"/>. + /// Deserialization can be done with <see cref="Bytes2Var(Span{byte}, bool)"/>. /// </summary> /// <param name="var">Variant that will be encoded.</param> /// <param name="fullObjects">If objects should be serialized.</param> /// <returns>The <c>Variant</c> encoded as an array of bytes.</returns> - public static byte[] Var2Bytes(object var, bool fullObjects = false) + public static byte[] Var2Bytes(Variant var, bool fullObjects = false) { - return godot_icall_GD_var2bytes(var, fullObjects); + NativeFuncs.godotsharp_var2bytes((godot_variant)var.NativeVar, fullObjects.ToGodotBool(), out var varBytes); + using (varBytes) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(varBytes); } /// <summary> @@ -561,9 +571,11 @@ namespace Godot /// </example> /// <param name="var">Variant that will be converted to string.</param> /// <returns>The <c>Variant</c> encoded as a string.</returns> - public static string Var2Str(object var) + public static string Var2Str(Variant var) { - return godot_icall_GD_var2str(var); + NativeFuncs.godotsharp_var2str((godot_variant)var.NativeVar, out godot_string ret); + using (ret) + return Marshaling.ConvertStringToManaged(ret); } /// <summary> @@ -572,85 +584,7 @@ namespace Godot /// <returns>The <see cref="Variant.Type"/> for the given <paramref name="type"/>.</returns> public static Variant.Type TypeToVariantType(Type type) { - return godot_icall_TypeToVariantType(type); + return Marshaling.ConvertManagedTypeToVariantType(type, out bool _); } - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_GD_bytes2var(byte[] bytes, bool allowObjects); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_GD_convert(object what, Variant.Type type); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_GD_hash(object var); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Object godot_icall_GD_instance_from_id(ulong instanceId); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_print(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_print_rich(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_printerr(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_printraw(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_prints(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_printt(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_randomize(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern uint godot_icall_GD_randi(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern float godot_icall_GD_randf(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_GD_randi_range(int from, int to); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern double godot_icall_GD_randf_range(double from, double to); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern double godot_icall_GD_randfn(double mean, double deviation); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern uint godot_icall_GD_rand_seed(ulong seed, out ulong newSeed); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_seed(ulong seed); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_GD_str(object[] what); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern object godot_icall_GD_str2var(string str); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern bool godot_icall_GD_type_exists(IntPtr type); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern byte[] godot_icall_GD_var2bytes(object what, bool fullObjects); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_GD_var2str(object var); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_pusherror(string type); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_GD_pushwarning(string type); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern Variant.Type godot_icall_TypeToVariantType(Type type); } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs index 9ccac1faaf..78a9d0fe9d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotTraceListener.cs @@ -17,10 +17,7 @@ namespace Godot public override void Fail(string message, string detailMessage) { GD.PrintErr("Assertion failed: ", message); - if (detailMessage != null) - { - GD.PrintErr(" Details: ", detailMessage); - } + GD.PrintErr(" Details: ", detailMessage); try { diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs index 729529d093..a17741ddeb 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotUnhandledExceptionEvent.cs @@ -1,17 +1,33 @@ using System; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { public static partial class GD { - /// <summary> - /// Fires when an unhandled exception occurs, regardless of project settings. - /// </summary> - public static event EventHandler<UnhandledExceptionArgs> UnhandledException; - - private static void OnUnhandledException(Exception e) + [UnmanagedCallersOnly] + internal static void OnCoreApiAssemblyLoaded(godot_bool isDebug) { - UnhandledException?.Invoke(null, new UnhandledExceptionArgs(e)); + try + { + Dispatcher.InitializeDefaultGodotTaskScheduler(); + + if (isDebug.ToBool()) + { + DebuggingUtils.InstallTraceListener(); + + AppDomain.CurrentDomain.UnhandledException += (_, e) => + { + // Exception.ToString() includes the inner exception + ExceptionUtils.LogUnhandledException((Exception)e.ExceptionObject); + }; + } + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + } } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs deleted file mode 100644 index 50ae2eb112..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs +++ /dev/null @@ -1,154 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Godot -{ - internal static class MarshalUtils - { - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Collections.Array{T}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericArray(Type type) => - type.GetGenericTypeDefinition() == typeof(Collections.Array<>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Collections.Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericDictionary(Type type) => - type.GetGenericTypeDefinition() == typeof(Collections.Dictionary<,>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="List{T}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsSystemGenericList(Type type) => - type.GetGenericTypeDefinition() == typeof(List<>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="Dictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsSystemGenericDictionary(Type type) => - type.GetGenericTypeDefinition() == typeof(Dictionary<,>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="IEnumerable{T}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericIEnumerable(Type type) => type.GetGenericTypeDefinition() == typeof(IEnumerable<>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="ICollection{T}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericICollection(Type type) => type.GetGenericTypeDefinition() == typeof(ICollection<>); - - /// <summary> - /// Returns <see langword="true"/> if the generic type definition of <paramref name="type"/> - /// is <see cref="IDictionary{TKey, TValue}"/>; otherwise returns <see langword="false"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static bool TypeIsGenericIDictionary(Type type) => type.GetGenericTypeDefinition() == typeof(IDictionary<,>); - - /// <summary> - /// Returns <see langword="true"/> if the <see cref="FlagsAttribute"/> is applied to the given type. - /// </summary> - private static bool TypeHasFlagsAttribute(Type type) => type.IsDefined(typeof(FlagsAttribute), false); - - /// <summary> - /// Returns the generic type definition of <paramref name="type"/>. - /// </summary> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="type"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static void GetGenericTypeDefinition(Type type, out Type genericTypeDefinition) - { - genericTypeDefinition = type.GetGenericTypeDefinition(); - } - - /// <summary> - /// Gets the element type for the given <paramref name="arrayType"/>. - /// </summary> - /// <param name="arrayType">Type for the generic array.</param> - /// <param name="elementType">Element type for the generic array.</param> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="arrayType"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static void ArrayGetElementType(Type arrayType, out Type elementType) - { - elementType = arrayType.GetGenericArguments()[0]; - } - - /// <summary> - /// Gets the key type and the value type for the given <paramref name="dictionaryType"/>. - /// </summary> - /// <param name="dictionaryType">The type for the generic dictionary.</param> - /// <param name="keyType">Key type for the generic dictionary.</param> - /// <param name="valueType">Value type for the generic dictionary.</param> - /// <exception cref="InvalidOperationException"> - /// Thrown when the given <paramref name="dictionaryType"/> is not a generic type. - /// That is, <see cref="Type.IsGenericType"/> returns <see langword="false"/>. - /// </exception> - private static void DictionaryGetKeyValueTypes(Type dictionaryType, out Type keyType, out Type valueType) - { - var genericArgs = dictionaryType.GetGenericArguments(); - keyType = genericArgs[0]; - valueType = genericArgs[1]; - } - - /// <summary> - /// Constructs a new <see cref="Type"/> from <see cref="Collections.Array{T}"/> - /// where the generic type for the elements is <paramref name="elemType"/>. - /// </summary> - /// <param name="elemType">Element type for the array.</param> - /// <returns>The generic array type with the specified element type.</returns> - private static Type MakeGenericArrayType(Type elemType) - { - return typeof(Collections.Array<>).MakeGenericType(elemType); - } - - /// <summary> - /// Constructs a new <see cref="Type"/> from <see cref="Collections.Dictionary{TKey, TValue}"/> - /// where the generic type for the keys is <paramref name="keyType"/> and - /// for the values is <paramref name="valueType"/>. - /// </summary> - /// <param name="keyType">Key type for the dictionary.</param> - /// <param name="valueType">Key type for the dictionary.</param> - /// <returns>The generic dictionary type with the specified key and value types.</returns> - private static Type MakeGenericDictionaryType(Type keyType, Type valueType) - { - return typeof(Collections.Dictionary<,>).MakeGenericType(keyType, valueType); - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs index 36b7d0f80f..08beff8104 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Mathf.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; namespace Godot diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs index f15d01b34b..ea05c1547c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/MathfEx.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; namespace Godot diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs new file mode 100644 index 0000000000..afef20a7f2 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/CustomUnsafe.cs @@ -0,0 +1,313 @@ +using System.Runtime.CompilerServices; + +namespace Godot.NativeInterop; + +// Ref structs are not allowed as generic type parameters, so we can't use Unsafe.AsPointer<T>/AsRef<T>. +// As a workaround we create our own overloads for our structs with some tricks under the hood. + +public static class CustomUnsafe +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_ref* AsPointer(ref godot_ref value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_ref* ReadOnlyRefAsPointer(in godot_ref value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_ref AsRef(godot_ref* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_ref AsRef(in godot_ref source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_variant_call_error* AsPointer(ref godot_variant_call_error value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_variant_call_error* ReadOnlyRefAsPointer(in godot_variant_call_error value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_variant_call_error AsRef(godot_variant_call_error* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_variant_call_error AsRef(in godot_variant_call_error source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_variant* AsPointer(ref godot_variant value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_variant* ReadOnlyRefAsPointer(in godot_variant value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_variant AsRef(godot_variant* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_variant AsRef(in godot_variant source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_string* AsPointer(ref godot_string value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_string* ReadOnlyRefAsPointer(in godot_string value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_string AsRef(godot_string* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_string AsRef(in godot_string source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_string_name* AsPointer(ref godot_string_name value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_string_name* ReadOnlyRefAsPointer(in godot_string_name value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_string_name AsRef(godot_string_name* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_string_name AsRef(in godot_string_name source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_node_path* AsPointer(ref godot_node_path value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_node_path* ReadOnlyRefAsPointer(in godot_node_path value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_node_path AsRef(godot_node_path* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_node_path AsRef(in godot_node_path source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_signal* AsPointer(ref godot_signal value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_signal* ReadOnlyRefAsPointer(in godot_signal value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_signal AsRef(godot_signal* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_signal AsRef(in godot_signal source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_callable* AsPointer(ref godot_callable value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_callable* ReadOnlyRefAsPointer(in godot_callable value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_callable AsRef(godot_callable* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_callable AsRef(in godot_callable source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_array* AsPointer(ref godot_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_array* ReadOnlyRefAsPointer(in godot_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_array AsRef(godot_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_array AsRef(in godot_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_dictionary* AsPointer(ref godot_dictionary value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_dictionary* ReadOnlyRefAsPointer(in godot_dictionary value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_dictionary AsRef(godot_dictionary* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_dictionary AsRef(in godot_dictionary source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_byte_array* AsPointer(ref godot_packed_byte_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_byte_array* ReadOnlyRefAsPointer(in godot_packed_byte_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_byte_array AsRef(godot_packed_byte_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_byte_array AsRef(in godot_packed_byte_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_int32_array* AsPointer(ref godot_packed_int32_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_int32_array* ReadOnlyRefAsPointer(in godot_packed_int32_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_int32_array AsRef(godot_packed_int32_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_int32_array AsRef(in godot_packed_int32_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_int64_array* AsPointer(ref godot_packed_int64_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_int64_array* ReadOnlyRefAsPointer(in godot_packed_int64_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_int64_array AsRef(godot_packed_int64_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_int64_array AsRef(in godot_packed_int64_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_float32_array* AsPointer(ref godot_packed_float32_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_float32_array* ReadOnlyRefAsPointer(in godot_packed_float32_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_float32_array AsRef(godot_packed_float32_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_float32_array AsRef(in godot_packed_float32_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_float64_array* AsPointer(ref godot_packed_float64_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_float64_array* ReadOnlyRefAsPointer(in godot_packed_float64_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_float64_array AsRef(godot_packed_float64_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_float64_array AsRef(in godot_packed_float64_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_string_array* AsPointer(ref godot_packed_string_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_string_array* ReadOnlyRefAsPointer(in godot_packed_string_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_string_array AsRef(godot_packed_string_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_string_array AsRef(in godot_packed_string_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_vector2_array* AsPointer(ref godot_packed_vector2_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_vector2_array* ReadOnlyRefAsPointer(in godot_packed_vector2_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_vector2_array AsRef(godot_packed_vector2_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_vector2_array AsRef(in godot_packed_vector2_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_vector3_array* AsPointer(ref godot_packed_vector3_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_vector3_array* ReadOnlyRefAsPointer(in godot_packed_vector3_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_vector3_array AsRef(godot_packed_vector3_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_vector3_array AsRef(in godot_packed_vector3_array source) + => ref *ReadOnlyRefAsPointer(in source); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_color_array* AsPointer(ref godot_packed_color_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_packed_color_array* ReadOnlyRefAsPointer(in godot_packed_color_array value) + => value.GetUnsafeAddress(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_color_array AsRef(godot_packed_color_array* source) + => ref *source; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe ref godot_packed_color_array AsRef(in godot_packed_color_array source) + => ref *ReadOnlyRefAsPointer(in source); +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs new file mode 100644 index 0000000000..5a0ea2ba13 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/ExceptionUtils.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +#nullable enable + +namespace Godot.NativeInterop +{ + internal static class ExceptionUtils + { + public static void PushError(string message) + { + GD.PushError(message); + } + + private static void OnExceptionLoggerException(Exception loggerException, Exception exceptionToLog) + { + try + { + // This better not throw + PushError(string.Concat("Exception thrown while trying to log another exception...", + "\n### Exception ###\n", exceptionToLog.ToString(), + "\n### Logger exception ###\n", loggerException.ToString())); + } + catch (Exception) + { + // Well, too bad... + } + } + + private record struct StackInfoTuple(string? File, string Func, int Line); + + private static void CollectExceptionInfo(Exception exception, List<StackInfoTuple> globalFrames, + StringBuilder excMsg) + { + if (excMsg.Length > 0) + excMsg.Append(" ---> "); + excMsg.Append(exception.GetType().FullName); + excMsg.Append(": "); + excMsg.Append(exception.Message); + + var innerExc = exception.InnerException; + + if (innerExc != null) + { + CollectExceptionInfo(innerExc, globalFrames, excMsg); + globalFrames.Add(new("", "--- End of inner exception stack trace ---", 0)); + } + + var stackTrace = new StackTrace(exception, fNeedFileInfo: true); + + foreach (StackFrame frame in stackTrace.GetFrames()) + { + DebuggingUtils.GetStackFrameMethodDecl(frame, out string methodDecl); + globalFrames.Add(new(frame.GetFileName(), methodDecl, frame.GetFileLineNumber())); + } + } + + private static void SendToScriptDebugger(Exception e) + { + var globalFrames = new List<StackInfoTuple>(); + + var excMsg = new StringBuilder(); + + CollectExceptionInfo(e, globalFrames, excMsg); + + string file = globalFrames.Count > 0 ? globalFrames[0].File ?? "" : ""; + string func = globalFrames.Count > 0 ? globalFrames[0].Func : ""; + int line = globalFrames.Count > 0 ? globalFrames[0].Line : 0; + string errorMsg = "Exception"; + + using godot_string nFile = Marshaling.ConvertStringToNative(file); + using godot_string nFunc = Marshaling.ConvertStringToNative(func); + using godot_string nErrorMsg = Marshaling.ConvertStringToNative(errorMsg); + using godot_string nExcMsg = Marshaling.ConvertStringToNative(excMsg.ToString()); + + using DebuggingUtils.godot_stack_info_vector stackInfoVector = default; + + stackInfoVector.Resize(globalFrames.Count); + + unsafe + { + for (int i = 0; i < globalFrames.Count; i++) + { + DebuggingUtils.godot_stack_info* stackInfo = &stackInfoVector.Elements[i]; + + var globalFrame = globalFrames[i]; + + // Assign directly to element in Vector. This way we don't need to worry + // about disposal if an exception is thrown. The Vector takes care of it. + stackInfo->File = Marshaling.ConvertStringToNative(globalFrame.File); + stackInfo->Func = Marshaling.ConvertStringToNative(globalFrame.Func); + stackInfo->Line = globalFrame.Line; + } + + NativeFuncs.godotsharp_internal_script_debugger_send_error(nFunc, nFile, line, + nErrorMsg, nExcMsg, p_warning: godot_bool.False, stackInfoVector); + } + } + + public static void LogException(Exception e) + { + try + { + if (NativeFuncs.godotsharp_internal_script_debugger_is_active()) + { + SendToScriptDebugger(e); + } + else + { + GD.PushError(e.ToString()); + } + } + catch (Exception unexpected) + { + OnExceptionLoggerException(unexpected, e); + } + } + + public static void LogUnhandledException(Exception e) + { + try + { + if (NativeFuncs.godotsharp_internal_script_debugger_is_active()) + { + SendToScriptDebugger(e); + } + + // In this case, print it as well in addition to sending it to the script debugger + GD.PushError("Unhandled exception\n" + e); + } + catch (Exception unexpected) + { + OnExceptionLoggerException(unexpected, e); + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs new file mode 100644 index 0000000000..5579992d2b --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/GodotDllImportResolver.cs @@ -0,0 +1,59 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +#nullable enable + +namespace Godot.NativeInterop +{ + public class GodotDllImportResolver + { + private IntPtr _internalHandle; + + public GodotDllImportResolver(IntPtr internalHandle) + { + _internalHandle = internalHandle; + } + + public IntPtr OnResolveDllImport(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + if (libraryName == "__Internal") + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Win32.GetModuleHandle(IntPtr.Zero); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return _internalHandle; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return MacOS.dlopen(IntPtr.Zero, MacOS.RTLD_LAZY); + } + } + + return IntPtr.Zero; + } + + // ReSharper disable InconsistentNaming + private static class MacOS + { + private const string SystemLibrary = "/usr/lib/libSystem.dylib"; + + public const int RTLD_LAZY = 1; + + [DllImport(SystemLibrary)] + public static extern IntPtr dlopen(IntPtr path, int mode); + } + + private static class Win32 + { + private const string SystemLibrary = "Kernel32.dll"; + + [DllImport(SystemLibrary)] + public static extern IntPtr GetModuleHandle(IntPtr lpModuleName); + } + // ReSharper restore InconsistentNaming + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs new file mode 100644 index 0000000000..44806e8ecf --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -0,0 +1,1091 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Godot.NativeInterop +{ + // NOTES: + // ref structs cannot implement interfaces, but they still work in `using` directives if they declare Dispose() + + public static class GodotBoolExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe godot_bool ToGodotBool(this bool @bool) + { + return *(godot_bool*)&@bool; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe bool ToBool(this godot_bool godotBool) + { + return *(bool*)&godotBool; + } + } + + // Apparently a struct with a byte is not blittable? It crashes when calling a UnmanagedCallersOnly function ptr. + // ReSharper disable once InconsistentNaming + public enum godot_bool : byte + { + True = 1, + False = 0 + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_ref + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_ref* GetUnsafeAddress() + => (godot_ref*)Unsafe.AsPointer(ref Unsafe.AsRef(in _reference)); + + private IntPtr _reference; + + public void Dispose() + { + if (_reference == IntPtr.Zero) + return; + NativeFuncs.godotsharp_ref_destroy(ref this); + _reference = IntPtr.Zero; + } + + public readonly IntPtr Reference + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _reference; + } + + public readonly bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _reference == IntPtr.Zero; + } + } + + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum godot_variant_call_error_error + { + GODOT_CALL_ERROR_CALL_OK = 0, + GODOT_CALL_ERROR_CALL_ERROR_INVALID_METHOD, + GODOT_CALL_ERROR_CALL_ERROR_INVALID_ARGUMENT, + GODOT_CALL_ERROR_CALL_ERROR_TOO_MANY_ARGUMENTS, + GODOT_CALL_ERROR_CALL_ERROR_TOO_FEW_ARGUMENTS, + GODOT_CALL_ERROR_CALL_ERROR_INSTANCE_IS_NULL, + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_variant_call_error + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_variant_call_error* GetUnsafeAddress() + => (godot_variant_call_error*)Unsafe.AsPointer(ref Unsafe.AsRef(in error)); + + private godot_variant_call_error_error error; + private int argument; + private int expected; + + public godot_variant_call_error_error Error + { + readonly get => error; + set => error = value; + } + + public int Argument + { + readonly get => argument; + set => argument = value; + } + + public Godot.Variant.Type Expected + { + readonly get => (Godot.Variant.Type)expected; + set => expected = (int)value; + } + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + public ref struct godot_variant + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_variant* GetUnsafeAddress() + => (godot_variant*)Unsafe.AsPointer(ref Unsafe.AsRef(in _typeField)); + + // Variant.Type is generated as an enum of type long, so we can't use for the field as it must only take 32-bits. + [FieldOffset(0)] private int _typeField; + + // There's padding here + + [FieldOffset(8)] private godot_variant_data _data; + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + private unsafe ref struct godot_variant_data + { + [FieldOffset(0)] public godot_bool _bool; + [FieldOffset(0)] public long _int; + [FieldOffset(0)] public double _float; + [FieldOffset(0)] public Transform2D* _transform2D; + [FieldOffset(0)] public AABB* _aabb; + [FieldOffset(0)] public Basis* _basis; + [FieldOffset(0)] public Transform3D* _transform3D; + [FieldOffset(0)] public Vector4* _vector4; + [FieldOffset(0)] public Vector4i* _vector4i; + [FieldOffset(0)] public Projection* _projection; + [FieldOffset(0)] private godot_variant_data_mem _mem; + + // The following fields are not in the C++ union, but this is how they're stored in _mem. + [FieldOffset(0)] public godot_string_name _m_string_name; + [FieldOffset(0)] public godot_string _m_string; + [FieldOffset(0)] public Vector3 _m_vector3; + [FieldOffset(0)] public Vector3i _m_vector3i; + [FieldOffset(0)] public Vector2 _m_vector2; + [FieldOffset(0)] public Vector2i _m_vector2i; + [FieldOffset(0)] public Rect2 _m_rect2; + [FieldOffset(0)] public Rect2i _m_rect2i; + [FieldOffset(0)] public Plane _m_plane; + [FieldOffset(0)] public Quaternion _m_quaternion; + [FieldOffset(0)] public Color _m_color; + [FieldOffset(0)] public godot_node_path _m_node_path; + [FieldOffset(0)] public RID _m_rid; + [FieldOffset(0)] public godot_variant_obj_data _m_obj_data; + [FieldOffset(0)] public godot_callable _m_callable; + [FieldOffset(0)] public godot_signal _m_signal; + [FieldOffset(0)] public godot_dictionary _m_dictionary; + [FieldOffset(0)] public godot_array _m_array; + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public struct godot_variant_obj_data + { + public ulong id; + public IntPtr obj; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public struct godot_variant_data_mem + { +#pragma warning disable 169 + private real_t _mem0; + private real_t _mem1; + private real_t _mem2; + private real_t _mem3; +#pragma warning restore 169 + } + } + + public Variant.Type Type + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => (Variant.Type)_typeField; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _typeField = (int)value; + } + + public godot_bool Bool + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._bool; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._bool = value; + } + + public long Int + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._int; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._int = value; + } + + public double Float + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._float; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._float = value; + } + + public readonly unsafe Transform2D* Transform2D + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._transform2D; + } + + public readonly unsafe AABB* AABB + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._aabb; + } + + public readonly unsafe Basis* Basis + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._basis; + } + + public readonly unsafe Transform3D* Transform3D + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._transform3D; + } + + public readonly unsafe Vector4* Vector4 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._vector4; + } + + public readonly unsafe Vector4i* Vector4i + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._vector4i; + } + + public readonly unsafe Projection* Projection + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._projection; + } + + public godot_string_name StringName + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_string_name; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_string_name = value; + } + + public godot_string String + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_string; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_string = value; + } + + public Vector3 Vector3 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_vector3; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_vector3 = value; + } + + public Vector3i Vector3i + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_vector3i; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_vector3i = value; + } + + public Vector2 Vector2 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_vector2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_vector2 = value; + } + + public Vector2i Vector2i + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_vector2i; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_vector2i = value; + } + + public Rect2 Rect2 + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_rect2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_rect2 = value; + } + + public Rect2i Rect2i + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_rect2i; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_rect2i = value; + } + + public Plane Plane + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_plane; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_plane = value; + } + + public Quaternion Quaternion + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_quaternion; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_quaternion = value; + } + + public Color Color + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_color; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_color = value; + } + + public godot_node_path NodePath + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_node_path; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_node_path = value; + } + + public RID RID + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_rid; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_rid = value; + } + + public godot_callable Callable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_callable; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_callable = value; + } + + public godot_signal Signal + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_signal; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_signal = value; + } + + public godot_dictionary Dictionary + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_dictionary; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_dictionary = value; + } + + public godot_array Array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + readonly get => _data._m_array; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _data._m_array = value; + } + + public readonly IntPtr Object + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data._m_obj_data.obj; + } + + public void Dispose() + { + switch (Type) + { + case Variant.Type.Nil: + case Variant.Type.Bool: + case Variant.Type.Int: + case Variant.Type.Float: + case Variant.Type.Vector2: + case Variant.Type.Vector2i: + case Variant.Type.Rect2: + case Variant.Type.Rect2i: + case Variant.Type.Vector3: + case Variant.Type.Vector3i: + case Variant.Type.Plane: + case Variant.Type.Quaternion: + case Variant.Type.Color: + case Variant.Type.Rid: + return; + } + + NativeFuncs.godotsharp_variant_destroy(ref this); + Type = Variant.Type.Nil; + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + // Variant.Type is generated as an enum of type long, so we can't use for the field as it must only take 32-bits. + [FieldOffset(0)] private int _typeField; + + // There's padding here + + [FieldOffset(8)] private godot_variant_data.godot_variant_data_mem _data; + + public static unsafe explicit operator movable(in godot_variant value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_variant(movable value) + => *(godot_variant*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_variant DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_variant*)Unsafe.AsPointer(ref this)); + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_string + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_string* GetUnsafeAddress() + => (godot_string*)Unsafe.AsPointer(ref Unsafe.AsRef(in _ptr)); + + private IntPtr _ptr; + + public void Dispose() + { + if (_ptr == IntPtr.Zero) + return; + NativeFuncs.godotsharp_string_destroy(ref this); + _ptr = IntPtr.Zero; + } + + public readonly IntPtr Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + // Size including the null termination character + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != IntPtr.Zero ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_string_name + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_string_name* GetUnsafeAddress() + => (godot_string_name*)Unsafe.AsPointer(ref Unsafe.AsRef(in _data)); + + private IntPtr _data; + + public void Dispose() + { + if (_data == IntPtr.Zero) + return; + NativeFuncs.godotsharp_string_name_destroy(ref this); + _data = IntPtr.Zero; + } + + public readonly bool IsAllocated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data != IntPtr.Zero; + } + + public readonly bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // This is all that's needed to check if it's empty. Equivalent to `== StringName()` in C++. + get => _data == IntPtr.Zero; + } + + public static bool operator ==(godot_string_name left, godot_string_name right) + { + return left._data == right._data; + } + + public static bool operator !=(godot_string_name left, godot_string_name right) + { + return !(left == right); + } + + public bool Equals(godot_string_name other) + { + return _data == other._data; + } + + public override bool Equals(object obj) + { + return obj is StringName s && s.Equals(this); + } + + public override int GetHashCode() + { + return _data.GetHashCode(); + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + private IntPtr _data; + + public static unsafe explicit operator movable(in godot_string_name value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_string_name(movable value) + => *(godot_string_name*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_string_name DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_string_name*)Unsafe.AsPointer(ref this)); + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_node_path + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_node_path* GetUnsafeAddress() + => (godot_node_path*)Unsafe.AsPointer(ref Unsafe.AsRef(in _data)); + + private IntPtr _data; + + public void Dispose() + { + if (_data == IntPtr.Zero) + return; + NativeFuncs.godotsharp_node_path_destroy(ref this); + _data = IntPtr.Zero; + } + + public readonly bool IsAllocated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _data != IntPtr.Zero; + } + + public readonly bool IsEmpty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // This is all that's needed to check if it's empty. It's what the `is_empty()` C++ method does. + get => _data == IntPtr.Zero; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + private IntPtr _data; + + public static unsafe explicit operator movable(in godot_node_path value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_node_path(movable value) + => *(godot_node_path*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_node_path DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_node_path*)Unsafe.AsPointer(ref this)); + } + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + public ref struct godot_signal + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_signal* GetUnsafeAddress() + => (godot_signal*)Unsafe.AsPointer(ref Unsafe.AsRef(in _getUnsafeAddressHelper)); + + [FieldOffset(0)] private byte _getUnsafeAddressHelper; + + [FieldOffset(0)] private godot_string_name _name; + + // There's padding here on 32-bit + + [FieldOffset(8)] private ulong _objectId; + + public godot_signal(godot_string_name name, ulong objectId) : this() + { + _name = name; + _objectId = objectId; + } + + public godot_string_name Name + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _name; + } + + public ulong ObjectId + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _objectId; + } + + public void Dispose() + { + if (!_name.IsAllocated) + return; + NativeFuncs.godotsharp_signal_destroy(ref this); + _name = default; + } + } + + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + public ref struct godot_callable + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_callable* GetUnsafeAddress() + => (godot_callable*)Unsafe.AsPointer(ref Unsafe.AsRef(in _getUnsafeAddressHelper)); + + [FieldOffset(0)] private byte _getUnsafeAddressHelper; + + [FieldOffset(0)] private godot_string_name _method; + + // There's padding here on 32-bit + + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable + [FieldOffset(8)] private ulong _objectId; + [FieldOffset(8)] private IntPtr _custom; + + public godot_callable(godot_string_name method, ulong objectId) : this() + { + _method = method; + _objectId = objectId; + } + + public void Dispose() + { + // _custom needs freeing as well + if (!_method.IsAllocated && _custom == IntPtr.Zero) + return; + NativeFuncs.godotsharp_callable_destroy(ref this); + _method = default; + _custom = IntPtr.Zero; + } + } + + // A correctly constructed value needs to call the native default constructor to allocate `_p`. + // Don't pass a C# default constructed `godot_array` to native code, unless it's going to + // be re-assigned a new value (the copy constructor checks if `_p` is null so that's fine). + [StructLayout(LayoutKind.Explicit)] + // ReSharper disable once InconsistentNaming + public ref struct godot_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_array* GetUnsafeAddress() + => (godot_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _getUnsafeAddressHelper)); + + [FieldOffset(0)] private byte _getUnsafeAddressHelper; + + [FieldOffset(0)] private unsafe ArrayPrivate* _p; + + [StructLayout(LayoutKind.Sequential)] + private struct ArrayPrivate + { + private uint _safeRefCount; + + public VariantVector _arrayVector; + // There are more fields here, but we don't care as we never store this in C# + + public readonly int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _arrayVector.Size; + } + } + + [StructLayout(LayoutKind.Sequential)] + private struct VariantVector + { + private IntPtr _writeProxy; + public unsafe godot_variant* _ptr; + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + public readonly unsafe godot_variant* Elements + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p->_arrayVector._ptr; + } + + public readonly unsafe bool IsAllocated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p != null; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p != null ? _p->Size : 0; + } + + public unsafe void Dispose() + { + if (_p == null) + return; + NativeFuncs.godotsharp_array_destroy(ref this); + _p = null; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + private unsafe ArrayPrivate* _p; + + public static unsafe explicit operator movable(in godot_array value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_array(movable value) + => *(godot_array*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_array DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_array*)Unsafe.AsPointer(ref this)); + } + } + + // IMPORTANT: + // A correctly constructed value needs to call the native default constructor to allocate `_p`. + // Don't pass a C# default constructed `godot_dictionary` to native code, unless it's going to + // be re-assigned a new value (the copy constructor checks if `_p` is null so that's fine). + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_dictionary + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_dictionary* GetUnsafeAddress() + => (godot_dictionary*)Unsafe.AsPointer(ref Unsafe.AsRef(in _p)); + + private IntPtr _p; + + public readonly bool IsAllocated + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _p != IntPtr.Zero; + } + + public void Dispose() + { + if (_p == IntPtr.Zero) + return; + NativeFuncs.godotsharp_dictionary_destroy(ref this); + _p = IntPtr.Zero; + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + internal struct movable + { + private IntPtr _p; + + public static unsafe explicit operator movable(in godot_dictionary value) + => *(movable*)CustomUnsafe.AsPointer(ref CustomUnsafe.AsRef(value)); + + public static unsafe explicit operator godot_dictionary(movable value) + => *(godot_dictionary*)Unsafe.AsPointer(ref value); + + public unsafe ref godot_dictionary DangerousSelfRef => + ref CustomUnsafe.AsRef((godot_dictionary*)Unsafe.AsPointer(ref this)); + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_byte_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_byte_array* GetUnsafeAddress() + => (godot_packed_byte_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe byte* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_byte_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe byte* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_int32_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_int32_array* GetUnsafeAddress() + => (godot_packed_int32_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe int* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_int32_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe int* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *(_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_int64_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_int64_array* GetUnsafeAddress() + => (godot_packed_int64_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe long* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_int64_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe long* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_float32_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_float32_array* GetUnsafeAddress() + => (godot_packed_float32_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe float* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_float32_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe float* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_float64_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_float64_array* GetUnsafeAddress() + => (godot_packed_float64_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe double* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_float64_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe double* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_string_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_string_array* GetUnsafeAddress() + => (godot_packed_string_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe godot_string* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_string_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe godot_string* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_vector2_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_vector2_array* GetUnsafeAddress() + => (godot_packed_vector2_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe Vector2* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_vector2_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe Vector2* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_vector3_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_vector3_array* GetUnsafeAddress() + => (godot_packed_vector3_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe Vector3* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_vector3_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe Vector3* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } + + [StructLayout(LayoutKind.Sequential)] + // ReSharper disable once InconsistentNaming + public ref struct godot_packed_color_array + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal readonly unsafe godot_packed_color_array* GetUnsafeAddress() + => (godot_packed_color_array*)Unsafe.AsPointer(ref Unsafe.AsRef(in _writeProxy)); + + private IntPtr _writeProxy; + private unsafe Color* _ptr; + + public unsafe void Dispose() + { + if (_ptr == null) + return; + NativeFuncs.godotsharp_packed_color_array_destroy(ref this); + _ptr = null; + } + + public readonly unsafe Color* Buffer + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr; + } + + public readonly unsafe int Size + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _ptr != null ? *((int*)_ptr - 1) : 0; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs new file mode 100644 index 0000000000..82f1c04d40 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropUtils.cs @@ -0,0 +1,96 @@ +using System; +using System.Runtime.InteropServices; +using Godot.Bridge; + +// ReSharper disable InconsistentNaming + +namespace Godot.NativeInterop +{ + internal static class InteropUtils + { + public static Object UnmanagedGetManaged(IntPtr unmanaged) + { + // The native pointer may be null + if (unmanaged == IntPtr.Zero) + return null; + + IntPtr gcHandlePtr; + godot_bool hasCsScriptInstance; + + // First try to get the tied managed instance from a CSharpInstance script instance + + gcHandlePtr = NativeFuncs.godotsharp_internal_unmanaged_get_script_instance_managed( + unmanaged, out hasCsScriptInstance); + + if (gcHandlePtr != IntPtr.Zero) + return (Object)GCHandle.FromIntPtr(gcHandlePtr).Target; + + // Otherwise, if the object has a CSharpInstance script instance, return null + + if (hasCsScriptInstance.ToBool()) + return null; + + // If it doesn't have a CSharpInstance script instance, try with native instance bindings + + gcHandlePtr = NativeFuncs.godotsharp_internal_unmanaged_get_instance_binding_managed(unmanaged); + + object target = gcHandlePtr != IntPtr.Zero ? GCHandle.FromIntPtr(gcHandlePtr).Target : null; + + if (target != null) + return (Object)target; + + // If the native instance binding GC handle target was collected, create a new one + + gcHandlePtr = NativeFuncs.godotsharp_internal_unmanaged_instance_binding_create_managed( + unmanaged, gcHandlePtr); + + return gcHandlePtr != IntPtr.Zero ? (Object)GCHandle.FromIntPtr(gcHandlePtr).Target : null; + } + + public static void TieManagedToUnmanaged(Object managed, IntPtr unmanaged, + StringName nativeName, bool refCounted, Type type, Type nativeType) + { + var gcHandle = refCounted ? + CustomGCHandle.AllocWeak(managed) : + CustomGCHandle.AllocStrong(managed, type); + + if (type == nativeType) + { + var nativeNameSelf = (godot_string_name)nativeName.NativeValue; + NativeFuncs.godotsharp_internal_tie_native_managed_to_unmanaged( + GCHandle.ToIntPtr(gcHandle), unmanaged, nativeNameSelf, refCounted.ToGodotBool()); + } + else + { + unsafe + { + // We don't dispose `script` ourselves here. + // `tie_user_managed_to_unmanaged` does it for us to avoid another P/Invoke call. + godot_ref script; + ScriptManagerBridge.GetOrLoadOrCreateScriptForType(type, &script); + + // IMPORTANT: This must be called after GetOrCreateScriptBridgeForType + NativeFuncs.godotsharp_internal_tie_user_managed_to_unmanaged( + GCHandle.ToIntPtr(gcHandle), unmanaged, &script, refCounted.ToGodotBool()); + } + } + } + + public static void TieManagedToUnmanagedWithPreSetup(Object managed, IntPtr unmanaged, + Type type, Type nativeType) + { + if (type == nativeType) + return; + + var strongGCHandle = CustomGCHandle.AllocStrong(managed); + NativeFuncs.godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup( + GCHandle.ToIntPtr(strongGCHandle), unmanaged); + } + + public static Object EngineGetSingleton(string name) + { + using godot_string src = Marshaling.ConvertStringToNative(name); + return UnmanagedGetManaged(NativeFuncs.godotsharp_engine_get_singleton(src)); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs new file mode 100644 index 0000000000..eee19aea46 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/Marshaling.cs @@ -0,0 +1,1092 @@ +using System; +using System.Runtime.InteropServices; + +// ReSharper disable InconsistentNaming + +// We want to use full name qualifiers here even if redundant for clarity +// ReSharper disable RedundantNameQualifier + +#nullable enable + +namespace Godot.NativeInterop +{ + public static class Marshaling + { + internal static Variant.Type ConvertManagedTypeToVariantType(Type type, out bool r_nil_is_variant) + { + r_nil_is_variant = false; + + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + return Variant.Type.Bool; + case TypeCode.Char: + return Variant.Type.Int; + case TypeCode.SByte: + return Variant.Type.Int; + case TypeCode.Int16: + return Variant.Type.Int; + case TypeCode.Int32: + return Variant.Type.Int; + case TypeCode.Int64: + return Variant.Type.Int; + case TypeCode.Byte: + return Variant.Type.Int; + case TypeCode.UInt16: + return Variant.Type.Int; + case TypeCode.UInt32: + return Variant.Type.Int; + case TypeCode.UInt64: + return Variant.Type.Int; + case TypeCode.Single: + return Variant.Type.Float; + case TypeCode.Double: + return Variant.Type.Float; + case TypeCode.String: + return Variant.Type.String; + default: + { + if (type == typeof(Vector2)) + return Variant.Type.Vector2; + + if (type == typeof(Vector2i)) + return Variant.Type.Vector2i; + + if (type == typeof(Rect2)) + return Variant.Type.Rect2; + + if (type == typeof(Rect2i)) + return Variant.Type.Rect2i; + + if (type == typeof(Transform2D)) + return Variant.Type.Transform2d; + + if (type == typeof(Vector3)) + return Variant.Type.Vector3; + + if (type == typeof(Vector3i)) + return Variant.Type.Vector3i; + + if (type == typeof(Vector4)) + return Variant.Type.Vector4; + + if (type == typeof(Vector4i)) + return Variant.Type.Vector4i; + + if (type == typeof(Basis)) + return Variant.Type.Basis; + + if (type == typeof(Quaternion)) + return Variant.Type.Quaternion; + + if (type == typeof(Transform3D)) + return Variant.Type.Transform3d; + + if (type == typeof(Projection)) + return Variant.Type.Projection; + + if (type == typeof(AABB)) + return Variant.Type.Aabb; + + if (type == typeof(Color)) + return Variant.Type.Color; + + if (type == typeof(Plane)) + return Variant.Type.Plane; + + if (type == typeof(Callable)) + return Variant.Type.Callable; + + if (type == typeof(SignalInfo)) + return Variant.Type.Signal; + + if (type.IsEnum) + return Variant.Type.Int; + + if (type.IsArray || type.IsSZArray) + { + if (type == typeof(byte[])) + return Variant.Type.PackedByteArray; + + if (type == typeof(int[])) + return Variant.Type.PackedInt32Array; + + if (type == typeof(long[])) + return Variant.Type.PackedInt64Array; + + if (type == typeof(float[])) + return Variant.Type.PackedFloat32Array; + + if (type == typeof(double[])) + return Variant.Type.PackedFloat64Array; + + if (type == typeof(string[])) + return Variant.Type.PackedStringArray; + + if (type == typeof(Vector2[])) + return Variant.Type.PackedVector2Array; + + if (type == typeof(Vector3[])) + return Variant.Type.PackedVector3Array; + + if (type == typeof(Color[])) + return Variant.Type.PackedColorArray; + + if (type == typeof(StringName[])) + return Variant.Type.Array; + + if (type == typeof(NodePath[])) + return Variant.Type.Array; + + if (type == typeof(RID[])) + return Variant.Type.Array; + + if (typeof(Godot.Object[]).IsAssignableFrom(type)) + return Variant.Type.Array; + } + else if (type.IsGenericType) + { + if (typeof(Godot.Object).IsAssignableFrom(type)) + return Variant.Type.Object; + } + else if (type == typeof(Variant)) + { + r_nil_is_variant = true; + return Variant.Type.Nil; + } + else + { + if (typeof(Godot.Object).IsAssignableFrom(type)) + return Variant.Type.Object; + + if (typeof(StringName) == type) + return Variant.Type.StringName; + + if (typeof(NodePath) == type) + return Variant.Type.NodePath; + + if (typeof(RID) == type) + return Variant.Type.Rid; + + if (typeof(Collections.Dictionary) == type) + return Variant.Type.Dictionary; + + if (typeof(Collections.Array) == type) + return Variant.Type.Array; + } + + break; + } + } + + // Unknown + return Variant.Type.Nil; + } + + /* TODO: Reflection and type checking each time is slow. This will be replaced with source generators. */ + public static godot_variant ConvertManagedObjectToVariant(object? p_obj) + { + if (p_obj == null) + return new godot_variant(); + + switch (p_obj) + { + case bool @bool: + return VariantUtils.CreateFromBool(@bool); + case char @char: + return VariantUtils.CreateFromInt(@char); + case sbyte @int8: + return VariantUtils.CreateFromInt(@int8); + case short @int16: + return VariantUtils.CreateFromInt(@int16); + case int @int32: + return VariantUtils.CreateFromInt(@int32); + case long @int64: + return VariantUtils.CreateFromInt(@int64); + case byte @uint8: + return VariantUtils.CreateFromInt(@uint8); + case ushort @uint16: + return VariantUtils.CreateFromInt(@uint16); + case uint @uint32: + return VariantUtils.CreateFromInt(@uint32); + case ulong @uint64: + return VariantUtils.CreateFromInt(@uint64); + case float @float: + return VariantUtils.CreateFromFloat(@float); + case double @double: + return VariantUtils.CreateFromFloat(@double); + case Vector2 @vector2: + return VariantUtils.CreateFromVector2(@vector2); + case Vector2i @vector2i: + return VariantUtils.CreateFromVector2i(@vector2i); + case Rect2 @rect2: + return VariantUtils.CreateFromRect2(@rect2); + case Rect2i @rect2i: + return VariantUtils.CreateFromRect2i(@rect2i); + case Transform2D @transform2D: + return VariantUtils.CreateFromTransform2D(@transform2D); + case Vector3 @vector3: + return VariantUtils.CreateFromVector3(@vector3); + case Vector3i @vector3i: + return VariantUtils.CreateFromVector3i(@vector3i); + case Vector4 @vector4: + return VariantUtils.CreateFromVector4(@vector4); + case Vector4i @vector4i: + return VariantUtils.CreateFromVector4i(@vector4i); + case Basis @basis: + return VariantUtils.CreateFromBasis(@basis); + case Quaternion @quaternion: + return VariantUtils.CreateFromQuaternion(@quaternion); + case Transform3D @transform3d: + return VariantUtils.CreateFromTransform3D(@transform3d); + case Projection @projection: + return VariantUtils.CreateFromProjection(@projection); + case AABB @aabb: + return VariantUtils.CreateFromAABB(@aabb); + case Color @color: + return VariantUtils.CreateFromColor(@color); + case Plane @plane: + return VariantUtils.CreateFromPlane(@plane); + case Callable @callable: + return VariantUtils.CreateFromCallable(@callable); + case SignalInfo @signalInfo: + return VariantUtils.CreateFromSignalInfo(@signalInfo); + case Enum @enum: + return VariantUtils.CreateFromInt(Convert.ToInt64(@enum)); + case string @string: + return VariantUtils.CreateFromString(@string); + case byte[] byteArray: + return VariantUtils.CreateFromPackedByteArray(byteArray); + case int[] int32Array: + return VariantUtils.CreateFromPackedInt32Array(int32Array); + case long[] int64Array: + return VariantUtils.CreateFromPackedInt64Array(int64Array); + case float[] floatArray: + return VariantUtils.CreateFromPackedFloat32Array(floatArray); + case double[] doubleArray: + return VariantUtils.CreateFromPackedFloat64Array(doubleArray); + case string[] stringArray: + return VariantUtils.CreateFromPackedStringArray(stringArray); + case Vector2[] vector2Array: + return VariantUtils.CreateFromPackedVector2Array(vector2Array); + case Vector3[] vector3Array: + return VariantUtils.CreateFromPackedVector3Array(vector3Array); + case Color[] colorArray: + return VariantUtils.CreateFromPackedColorArray(colorArray); + case StringName[] stringNameArray: + return VariantUtils.CreateFromSystemArrayOfStringName(stringNameArray); + case NodePath[] nodePathArray: + return VariantUtils.CreateFromSystemArrayOfNodePath(nodePathArray); + case RID[] ridArray: + return VariantUtils.CreateFromSystemArrayOfRID(ridArray); + case Godot.Object[] godotObjectArray: + return VariantUtils.CreateFromSystemArrayOfGodotObject(godotObjectArray); + case Godot.Object godotObject: + return VariantUtils.CreateFromGodotObject(godotObject); + case StringName stringName: + return VariantUtils.CreateFromStringName(stringName); + case NodePath nodePath: + return VariantUtils.CreateFromNodePath(nodePath); + case RID rid: + return VariantUtils.CreateFromRID(rid); + case Collections.Dictionary godotDictionary: + return VariantUtils.CreateFromDictionary(godotDictionary); + case Collections.Array godotArray: + return VariantUtils.CreateFromArray(godotArray); + case Variant variant: + return NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); + } + + GD.PushError("Attempted to convert an unmarshallable managed type to Variant. Name: '" + + p_obj.GetType().FullName + "."); + return new godot_variant(); + } + + public static object? ConvertVariantToManagedObjectOfType(in godot_variant p_var, Type type) + { + // This function is only needed to set the value of properties. Fields have their own implementation, set_value_from_variant. + switch (Type.GetTypeCode(type)) + { + case TypeCode.Boolean: + return VariantUtils.ConvertToBool(p_var); + case TypeCode.Char: + return VariantUtils.ConvertToChar(p_var); + case TypeCode.SByte: + return VariantUtils.ConvertToInt8(p_var); + case TypeCode.Int16: + return VariantUtils.ConvertToInt16(p_var); + case TypeCode.Int32: + return VariantUtils.ConvertToInt32(p_var); + case TypeCode.Int64: + return VariantUtils.ConvertToInt64(p_var); + case TypeCode.Byte: + return VariantUtils.ConvertToUInt8(p_var); + case TypeCode.UInt16: + return VariantUtils.ConvertToUInt16(p_var); + case TypeCode.UInt32: + return VariantUtils.ConvertToUInt32(p_var); + case TypeCode.UInt64: + return VariantUtils.ConvertToUInt64(p_var); + case TypeCode.Single: + return VariantUtils.ConvertToFloat32(p_var); + case TypeCode.Double: + return VariantUtils.ConvertToFloat64(p_var); + case TypeCode.String: + return VariantUtils.ConvertToStringObject(p_var); + default: + { + if (type == typeof(Vector2)) + return VariantUtils.ConvertToVector2(p_var); + + if (type == typeof(Vector2i)) + return VariantUtils.ConvertToVector2i(p_var); + + if (type == typeof(Rect2)) + return VariantUtils.ConvertToRect2(p_var); + + if (type == typeof(Rect2i)) + return VariantUtils.ConvertToRect2i(p_var); + + if (type == typeof(Transform2D)) + return VariantUtils.ConvertToTransform2D(p_var); + + if (type == typeof(Vector3)) + return VariantUtils.ConvertToVector3(p_var); + + if (type == typeof(Vector3i)) + return VariantUtils.ConvertToVector3i(p_var); + + if (type == typeof(Vector4)) + return VariantUtils.ConvertToVector4(p_var); + + if (type == typeof(Vector4i)) + return VariantUtils.ConvertToVector4i(p_var); + + if (type == typeof(Basis)) + return VariantUtils.ConvertToBasis(p_var); + + if (type == typeof(Quaternion)) + return VariantUtils.ConvertToQuaternion(p_var); + + if (type == typeof(Transform3D)) + return VariantUtils.ConvertToTransform3D(p_var); + + if (type == typeof(Projection)) + return VariantUtils.ConvertToProjection(p_var); + + if (type == typeof(AABB)) + return VariantUtils.ConvertToAABB(p_var); + + if (type == typeof(Color)) + return VariantUtils.ConvertToColor(p_var); + + if (type == typeof(Plane)) + return VariantUtils.ConvertToPlane(p_var); + + if (type == typeof(Callable)) + return VariantUtils.ConvertToCallableManaged(p_var); + + if (type == typeof(SignalInfo)) + return VariantUtils.ConvertToSignalInfo(p_var); + + if (type.IsEnum) + { + var enumUnderlyingType = type.GetEnumUnderlyingType(); + switch (Type.GetTypeCode(enumUnderlyingType)) + { + case TypeCode.SByte: + return VariantUtils.ConvertToInt8(p_var); + case TypeCode.Int16: + return VariantUtils.ConvertToInt16(p_var); + case TypeCode.Int32: + return VariantUtils.ConvertToInt32(p_var); + case TypeCode.Int64: + return VariantUtils.ConvertToInt64(p_var); + case TypeCode.Byte: + return VariantUtils.ConvertToUInt8(p_var); + case TypeCode.UInt16: + return VariantUtils.ConvertToUInt16(p_var); + case TypeCode.UInt32: + return VariantUtils.ConvertToUInt32(p_var); + case TypeCode.UInt64: + return VariantUtils.ConvertToUInt64(p_var); + default: + { + GD.PushError( + "Attempted to convert Variant to enum value of unsupported underlying type. Name: " + + type.FullName + " : " + enumUnderlyingType.FullName + "."); + return null; + } + } + } + + if (type.IsArray || type.IsSZArray) + { + return ConvertVariantToSystemArrayOfType(p_var, type); + } + else if (type.IsGenericType) + { + if (typeof(Godot.Object).IsAssignableFrom(type)) + { + var godotObject = VariantUtils.ConvertToGodotObject(p_var); + + if (!type.IsInstanceOfType(godotObject)) + { + GD.PushError("Invalid cast when marshaling Godot.Object type." + + $" `{godotObject.GetType()}` is not assignable to `{type.FullName}`."); + return null; + } + + return godotObject; + } + + return null; + } + else if (type == typeof(Variant)) + { + return Variant.CreateCopyingBorrowed(p_var); + } + + if (ConvertVariantToManagedObjectOfClass(p_var, type, out object? res)) + return res; + + break; + } + } + + GD.PushError("Attempted to convert Variant to unsupported type. Name: " + + type.FullName + "."); + return null; + } + + private static object? ConvertVariantToSystemArrayOfType(in godot_variant p_var, Type type) + { + if (type == typeof(byte[])) + return VariantUtils.ConvertAsPackedByteArrayToSystemArray(p_var); + + if (type == typeof(int[])) + return VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(p_var); + + if (type == typeof(long[])) + return VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(p_var); + + if (type == typeof(float[])) + return VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(p_var); + + if (type == typeof(double[])) + return VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(p_var); + + if (type == typeof(string[])) + return VariantUtils.ConvertAsPackedStringArrayToSystemArray(p_var); + + if (type == typeof(Vector2[])) + return VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(p_var); + + if (type == typeof(Vector3[])) + return VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(p_var); + + if (type == typeof(Color[])) + return VariantUtils.ConvertAsPackedColorArrayToSystemArray(p_var); + + if (type == typeof(StringName[])) + return VariantUtils.ConvertToSystemArrayOfStringName(p_var); + + if (type == typeof(NodePath[])) + return VariantUtils.ConvertToSystemArrayOfNodePath(p_var); + + if (type == typeof(RID[])) + return VariantUtils.ConvertToSystemArrayOfRID(p_var); + + if (typeof(Godot.Object[]).IsAssignableFrom(type)) + return VariantUtils.ConvertToSystemArrayOfGodotObject(p_var, type); + + GD.PushError("Attempted to convert Variant to array of unsupported element type. Name: " + + type.GetElementType()!.FullName + "."); + return null; + } + + private static bool ConvertVariantToManagedObjectOfClass(in godot_variant p_var, Type type, + out object? res) + { + if (typeof(Godot.Object).IsAssignableFrom(type)) + { + if (p_var.Type == Variant.Type.Nil) + { + res = null; + return true; + } + + if (p_var.Type != Variant.Type.Object) + { + GD.PushError("Invalid cast when marshaling Godot.Object type." + + $" Variant type is `{p_var.Type}`; expected `{p_var.Object}`."); + res = null; + return true; + } + + var godotObjectPtr = VariantUtils.ConvertToGodotObjectPtr(p_var); + + if (godotObjectPtr == IntPtr.Zero) + { + res = null; + return true; + } + + var godotObject = InteropUtils.UnmanagedGetManaged(godotObjectPtr); + + if (!type.IsInstanceOfType(godotObject)) + { + GD.PushError("Invalid cast when marshaling Godot.Object type." + + $" `{godotObject.GetType()}` is not assignable to `{type.FullName}`."); + res = null; + return false; + } + + res = godotObject; + return true; + } + + if (typeof(StringName) == type) + { + res = VariantUtils.ConvertToStringNameObject(p_var); + return true; + } + + if (typeof(NodePath) == type) + { + res = VariantUtils.ConvertToNodePathObject(p_var); + return true; + } + + if (typeof(RID) == type) + { + res = VariantUtils.ConvertToRID(p_var); + return true; + } + + if (typeof(Collections.Dictionary) == type) + { + res = VariantUtils.ConvertToDictionaryObject(p_var); + return true; + } + + if (typeof(Collections.Array) == type) + { + res = VariantUtils.ConvertToArrayObject(p_var); + return true; + } + + res = null; + return false; + } + + public static unsafe object? ConvertVariantToManagedObject(in godot_variant p_var) + { + switch (p_var.Type) + { + case Variant.Type.Bool: + return p_var.Bool.ToBool(); + case Variant.Type.Int: + return p_var.Int; + case Variant.Type.Float: + { +#if REAL_T_IS_DOUBLE + return p_var.Float; +#else + return (float)p_var.Float; +#endif + } + case Variant.Type.String: + return ConvertStringToManaged(p_var.String); + case Variant.Type.Vector2: + return p_var.Vector2; + case Variant.Type.Vector2i: + return p_var.Vector2i; + case Variant.Type.Rect2: + return p_var.Rect2; + case Variant.Type.Rect2i: + return p_var.Rect2i; + case Variant.Type.Vector3: + return p_var.Vector3; + case Variant.Type.Vector3i: + return p_var.Vector3i; + case Variant.Type.Transform2d: + return *p_var.Transform2D; + case Variant.Type.Vector4: + return *p_var.Vector4; + case Variant.Type.Vector4i: + return *p_var.Vector4i; + case Variant.Type.Plane: + return p_var.Plane; + case Variant.Type.Quaternion: + return p_var.Quaternion; + case Variant.Type.Aabb: + return *p_var.AABB; + case Variant.Type.Basis: + return *p_var.Basis; + case Variant.Type.Transform3d: + return *p_var.Transform3D; + case Variant.Type.Projection: + return *p_var.Projection; + case Variant.Type.Color: + return p_var.Color; + case Variant.Type.StringName: + { + // The Variant owns the value, so we need to make a copy + return StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(p_var.StringName)); + } + case Variant.Type.NodePath: + { + // The Variant owns the value, so we need to make a copy + return NodePath.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_node_path_new_copy(p_var.NodePath)); + } + case Variant.Type.Rid: + return p_var.RID; + case Variant.Type.Object: + return InteropUtils.UnmanagedGetManaged(p_var.Object); + case Variant.Type.Callable: + return ConvertCallableToManaged(p_var.Callable); + case Variant.Type.Signal: + return ConvertSignalToManaged(p_var.Signal); + case Variant.Type.Dictionary: + { + // The Variant owns the value, so we need to make a copy + return Collections.Dictionary.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_dictionary_new_copy(p_var.Dictionary)); + } + case Variant.Type.Array: + { + // The Variant owns the value, so we need to make a copy + return Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_var.Array)); + } + case Variant.Type.PackedByteArray: + return VariantUtils.ConvertAsPackedByteArrayToSystemArray(p_var); + case Variant.Type.PackedInt32Array: + return VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(p_var); + case Variant.Type.PackedInt64Array: + return VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(p_var); + case Variant.Type.PackedFloat32Array: + return VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(p_var); + case Variant.Type.PackedFloat64Array: + return VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(p_var); + case Variant.Type.PackedStringArray: + return VariantUtils.ConvertAsPackedStringArrayToSystemArray(p_var); + case Variant.Type.PackedVector2Array: + return VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(p_var); + case Variant.Type.PackedVector3Array: + return VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(p_var); + case Variant.Type.PackedColorArray: + return VariantUtils.ConvertAsPackedColorArrayToSystemArray(p_var); + default: + return null; + } + } + + // String + + public static unsafe godot_string ConvertStringToNative(string? p_mono_string) + { + if (p_mono_string == null) + return new godot_string(); + + fixed (char* methodChars = p_mono_string) + { + NativeFuncs.godotsharp_string_new_with_utf16_chars(out godot_string dest, methodChars); + return dest; + } + } + + public static unsafe string ConvertStringToManaged(in godot_string p_string) + { + if (p_string.Buffer == IntPtr.Zero) + return string.Empty; + + const int sizeOfChar32 = 4; + byte* bytes = (byte*)p_string.Buffer; + int size = p_string.Size; + if (size == 0) + return string.Empty; + size -= 1; // zero at the end + int sizeInBytes = size * sizeOfChar32; + return System.Text.Encoding.UTF32.GetString(bytes, sizeInBytes); + } + + // Callable + + public static godot_callable ConvertCallableToNative(in Callable p_managed_callable) + { + if (p_managed_callable.Delegate != null) + { + var gcHandle = CustomGCHandle.AllocStrong(p_managed_callable.Delegate); + NativeFuncs.godotsharp_callable_new_with_delegate( + GCHandle.ToIntPtr(gcHandle), out godot_callable callable); + return callable; + } + else + { + godot_string_name method; + + if (p_managed_callable.Method != null && !p_managed_callable.Method.IsEmpty) + { + var src = (godot_string_name)p_managed_callable.Method.NativeValue; + method = NativeFuncs.godotsharp_string_name_new_copy(src); + } + else + { + method = default; + } + + return new godot_callable(method /* Takes ownership of disposable */, + p_managed_callable.Target.GetInstanceId()); + } + } + + public static Callable ConvertCallableToManaged(in godot_callable p_callable) + { + if (NativeFuncs.godotsharp_callable_get_data_for_marshalling(p_callable, + out IntPtr delegateGCHandle, out IntPtr godotObject, + out godot_string_name name).ToBool()) + { + if (delegateGCHandle != IntPtr.Zero) + { + return new Callable((Delegate?)GCHandle.FromIntPtr(delegateGCHandle).Target); + } + else + { + return new Callable( + InteropUtils.UnmanagedGetManaged(godotObject), + StringName.CreateTakingOwnershipOfDisposableValue(name)); + } + } + + // Some other unsupported callable + return new Callable(); + } + + // SignalInfo + + public static godot_signal ConvertSignalToNative(in SignalInfo p_managed_signal) + { + ulong ownerId = p_managed_signal.Owner.GetInstanceId(); + godot_string_name name; + + if (p_managed_signal.Name != null && !p_managed_signal.Name.IsEmpty) + { + var src = (godot_string_name)p_managed_signal.Name.NativeValue; + name = NativeFuncs.godotsharp_string_name_new_copy(src); + } + else + { + name = default; + } + + return new godot_signal(name, ownerId); + } + + public static SignalInfo ConvertSignalToManaged(in godot_signal p_signal) + { + var owner = GD.InstanceFromId(p_signal.ObjectId); + var name = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(p_signal.Name)); + return new SignalInfo(owner, name); + } + + // Array + + internal static T[] ConvertNativeGodotArrayToSystemArrayOfGodotObjectType<T>(in godot_array p_array) + where T : Godot.Object + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = new T[length]; + + for (int i = 0; i < length; i++) + ret[i] = (T)array[i].AsGodotObject(); + + return ret; + } + + // TODO: This needs reflection. Look for an alternative. + internal static Godot.Object[] ConvertNativeGodotArrayToSystemArrayOfGodotObjectType(in godot_array p_array, + Type type) + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = (Godot.Object[])Activator.CreateInstance(type, length)!; + + for (int i = 0; i < length; i++) + ret[i] = array[i].AsGodotObject(); + + return ret; + } + + internal static StringName[] ConvertNativeGodotArrayToSystemArrayOfStringName(in godot_array p_array) + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = new StringName[length]; + + for (int i = 0; i < length; i++) + ret[i] = array[i].AsStringName(); + + return ret; + } + + internal static NodePath[] ConvertNativeGodotArrayToSystemArrayOfNodePath(in godot_array p_array) + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = new NodePath[length]; + + for (int i = 0; i < length; i++) + ret[i] = array[i].AsNodePath(); + + return ret; + } + + internal static RID[] ConvertNativeGodotArrayToSystemArrayOfRID(in godot_array p_array) + { + var array = Collections.Array.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_array_new_copy(p_array)); + + int length = array.Count; + var ret = new RID[length]; + + for (int i = 0; i < length; i++) + ret[i] = array[i].AsRID(); + + return ret; + } + + // PackedByteArray + + public static unsafe byte[] ConvertNativePackedByteArrayToSystemArray(in godot_packed_byte_array p_array) + { + byte* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<byte>(); + var array = new byte[size]; + fixed (byte* dest = array) + Buffer.MemoryCopy(buffer, dest, size, size); + return array; + } + + public static unsafe godot_packed_byte_array ConvertSystemArrayToNativePackedByteArray(Span<byte> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_byte_array(); + fixed (byte* src = p_array) + return NativeFuncs.godotsharp_packed_byte_array_new_mem_copy(src, p_array.Length); + } + + // PackedInt32Array + + public static unsafe int[] ConvertNativePackedInt32ArrayToSystemArray(godot_packed_int32_array p_array) + { + int* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<int>(); + int sizeInBytes = size * sizeof(int); + var array = new int[size]; + fixed (int* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_int32_array ConvertSystemArrayToNativePackedInt32Array(Span<int> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_int32_array(); + fixed (int* src = p_array) + return NativeFuncs.godotsharp_packed_int32_array_new_mem_copy(src, p_array.Length); + } + + // PackedInt64Array + + public static unsafe long[] ConvertNativePackedInt64ArrayToSystemArray(godot_packed_int64_array p_array) + { + long* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<long>(); + int sizeInBytes = size * sizeof(long); + var array = new long[size]; + fixed (long* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_int64_array ConvertSystemArrayToNativePackedInt64Array(Span<long> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_int64_array(); + fixed (long* src = p_array) + return NativeFuncs.godotsharp_packed_int64_array_new_mem_copy(src, p_array.Length); + } + + // PackedFloat32Array + + public static unsafe float[] ConvertNativePackedFloat32ArrayToSystemArray(godot_packed_float32_array p_array) + { + float* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<float>(); + int sizeInBytes = size * sizeof(float); + var array = new float[size]; + fixed (float* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_float32_array ConvertSystemArrayToNativePackedFloat32Array( + Span<float> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_float32_array(); + fixed (float* src = p_array) + return NativeFuncs.godotsharp_packed_float32_array_new_mem_copy(src, p_array.Length); + } + + // PackedFloat64Array + + public static unsafe double[] ConvertNativePackedFloat64ArrayToSystemArray(godot_packed_float64_array p_array) + { + double* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<double>(); + int sizeInBytes = size * sizeof(double); + var array = new double[size]; + fixed (double* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_float64_array ConvertSystemArrayToNativePackedFloat64Array( + Span<double> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_float64_array(); + fixed (double* src = p_array) + return NativeFuncs.godotsharp_packed_float64_array_new_mem_copy(src, p_array.Length); + } + + // PackedStringArray + + public static unsafe string[] ConvertNativePackedStringArrayToSystemArray(godot_packed_string_array p_array) + { + godot_string* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<string>(); + var array = new string[size]; + for (int i = 0; i < size; i++) + array[i] = ConvertStringToManaged(buffer[i]); + return array; + } + + public static godot_packed_string_array ConvertSystemArrayToNativePackedStringArray(Span<string> p_array) + { + godot_packed_string_array dest = new godot_packed_string_array(); + + if (p_array.IsEmpty) + return dest; + + /* TODO: Replace godotsharp_packed_string_array_add with a single internal call to + get the write address. We can't use `dest._ptr` directly for writing due to COW. */ + + for (int i = 0; i < p_array.Length; i++) + { + using godot_string godotStrElem = ConvertStringToNative(p_array[i]); + NativeFuncs.godotsharp_packed_string_array_add(ref dest, godotStrElem); + } + + return dest; + } + + // PackedVector2Array + + public static unsafe Vector2[] ConvertNativePackedVector2ArrayToSystemArray(godot_packed_vector2_array p_array) + { + Vector2* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<Vector2>(); + int sizeInBytes = size * sizeof(Vector2); + var array = new Vector2[size]; + fixed (Vector2* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_vector2_array ConvertSystemArrayToNativePackedVector2Array( + Span<Vector2> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_vector2_array(); + fixed (Vector2* src = p_array) + return NativeFuncs.godotsharp_packed_vector2_array_new_mem_copy(src, p_array.Length); + } + + // PackedVector3Array + + public static unsafe Vector3[] ConvertNativePackedVector3ArrayToSystemArray(godot_packed_vector3_array p_array) + { + Vector3* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<Vector3>(); + int sizeInBytes = size * sizeof(Vector3); + var array = new Vector3[size]; + fixed (Vector3* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_vector3_array ConvertSystemArrayToNativePackedVector3Array( + Span<Vector3> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_vector3_array(); + fixed (Vector3* src = p_array) + return NativeFuncs.godotsharp_packed_vector3_array_new_mem_copy(src, p_array.Length); + } + + // PackedColorArray + + public static unsafe Color[] ConvertNativePackedColorArrayToSystemArray(godot_packed_color_array p_array) + { + Color* buffer = p_array.Buffer; + int size = p_array.Size; + if (size == 0) + return Array.Empty<Color>(); + int sizeInBytes = size * sizeof(Color); + var array = new Color[size]; + fixed (Color* dest = array) + Buffer.MemoryCopy(buffer, dest, sizeInBytes, sizeInBytes); + return array; + } + + public static unsafe godot_packed_color_array ConvertSystemArrayToNativePackedColorArray(Span<Color> p_array) + { + if (p_array.IsEmpty) + return new godot_packed_color_array(); + fixed (Color* src = p_array) + return NativeFuncs.godotsharp_packed_color_array_new_mem_copy(src, p_array.Length); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs new file mode 100644 index 0000000000..6d2534e6f7 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -0,0 +1,522 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Godot.SourceGenerators.Internal; + +// ReSharper disable InconsistentNaming + +namespace Godot.NativeInterop +{ + /* + * IMPORTANT: + * The order of the methods defined in NativeFuncs must match the order + * in the array defined at the bottom of 'glue/runtime_interop.cpp'. + */ + + [GenerateUnmanagedCallbacks(typeof(UnmanagedCallbacks))] + public static unsafe partial class NativeFuncs + { + private static bool initialized = false; + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Global + public static void Initialize(IntPtr unmanagedCallbacks, int unmanagedCallbacksSize) + { + if (initialized) + throw new InvalidOperationException("Already initialized"); + initialized = true; + + if (unmanagedCallbacksSize != sizeof(UnmanagedCallbacks)) + throw new ArgumentException("Unmanaged callbacks size mismatch"); + + _unmanagedCallbacks = Unsafe.AsRef<UnmanagedCallbacks>((void*)unmanagedCallbacks); + } + + private partial struct UnmanagedCallbacks + { + } + + // Custom functions + + public static partial IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname, + in godot_string_name p_methodname); + + public static partial delegate* unmanaged<IntPtr> godotsharp_get_class_constructor( + in godot_string_name p_classname); + + public static partial IntPtr godotsharp_engine_get_singleton(in godot_string p_name); + + + internal static partial Error godotsharp_stack_info_vector_resize( + ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector, int p_size); + + internal static partial void godotsharp_stack_info_vector_destroy( + ref DebuggingUtils.godot_stack_info_vector p_stack_info_vector); + + internal static partial void godotsharp_internal_script_debugger_send_error(in godot_string p_func, + in godot_string p_file, int p_line, in godot_string p_err, in godot_string p_descr, + godot_bool p_warning, in DebuggingUtils.godot_stack_info_vector p_stack_info_vector); + + internal static partial bool godotsharp_internal_script_debugger_is_active(); + + internal static partial IntPtr godotsharp_internal_object_get_associated_gchandle(IntPtr ptr); + + internal static partial void godotsharp_internal_object_disposed(IntPtr ptr, IntPtr gcHandleToFree); + + internal static partial void godotsharp_internal_refcounted_disposed(IntPtr ptr, IntPtr gcHandleToFree, + godot_bool isFinalizer); + + internal static partial Error godotsharp_internal_signal_awaiter_connect(IntPtr source, + in godot_string_name signal, + IntPtr target, IntPtr awaiterHandlePtr); + + internal static partial void godotsharp_internal_tie_native_managed_to_unmanaged(IntPtr gcHandleIntPtr, + IntPtr unmanaged, in godot_string_name nativeName, godot_bool refCounted); + + internal static partial void godotsharp_internal_tie_user_managed_to_unmanaged(IntPtr gcHandleIntPtr, + IntPtr unmanaged, godot_ref* scriptPtr, godot_bool refCounted); + + internal static partial void godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup( + IntPtr gcHandleIntPtr, IntPtr unmanaged); + + internal static partial IntPtr godotsharp_internal_unmanaged_get_script_instance_managed(IntPtr p_unmanaged, + out godot_bool r_has_cs_script_instance); + + internal static partial IntPtr godotsharp_internal_unmanaged_get_instance_binding_managed(IntPtr p_unmanaged); + + internal static partial IntPtr godotsharp_internal_unmanaged_instance_binding_create_managed(IntPtr p_unmanaged, + IntPtr oldGCHandlePtr); + + internal static partial void godotsharp_internal_new_csharp_script(godot_ref* r_dest); + + internal static partial godot_bool godotsharp_internal_script_load(in godot_string p_path, godot_ref* r_dest); + + internal static partial void godotsharp_internal_reload_registered_script(IntPtr scriptPtr); + + internal static partial void godotsharp_array_filter_godot_objects_by_native(in godot_string_name p_native_name, + in godot_array p_input, out godot_array r_output); + + internal static partial void godotsharp_array_filter_godot_objects_by_non_native(in godot_array p_input, + out godot_array r_output); + + public static partial void godotsharp_ref_new_from_ref_counted_ptr(out godot_ref r_dest, + IntPtr p_ref_counted_ptr); + + public static partial void godotsharp_ref_destroy(ref godot_ref p_instance); + + public static partial void godotsharp_string_name_new_from_string(out godot_string_name r_dest, + in godot_string p_name); + + public static partial void godotsharp_node_path_new_from_string(out godot_node_path r_dest, + in godot_string p_name); + + public static partial void + godotsharp_string_name_as_string(out godot_string r_dest, in godot_string_name p_name); + + public static partial void godotsharp_node_path_as_string(out godot_string r_dest, in godot_node_path p_np); + + public static partial godot_packed_byte_array godotsharp_packed_byte_array_new_mem_copy(byte* p_src, + int p_length); + + public static partial godot_packed_int32_array godotsharp_packed_int32_array_new_mem_copy(int* p_src, + int p_length); + + public static partial godot_packed_int64_array godotsharp_packed_int64_array_new_mem_copy(long* p_src, + int p_length); + + public static partial godot_packed_float32_array godotsharp_packed_float32_array_new_mem_copy(float* p_src, + int p_length); + + public static partial godot_packed_float64_array godotsharp_packed_float64_array_new_mem_copy(double* p_src, + int p_length); + + public static partial godot_packed_vector2_array godotsharp_packed_vector2_array_new_mem_copy(Vector2* p_src, + int p_length); + + public static partial godot_packed_vector3_array godotsharp_packed_vector3_array_new_mem_copy(Vector3* p_src, + int p_length); + + public static partial godot_packed_color_array godotsharp_packed_color_array_new_mem_copy(Color* p_src, + int p_length); + + public static partial void godotsharp_packed_string_array_add(ref godot_packed_string_array r_dest, + in godot_string p_element); + + public static partial void godotsharp_callable_new_with_delegate(IntPtr p_delegate_handle, + out godot_callable r_callable); + + internal static partial godot_bool godotsharp_callable_get_data_for_marshalling(in godot_callable p_callable, + out IntPtr r_delegate_handle, out IntPtr r_object, out godot_string_name r_name); + + internal static partial godot_variant godotsharp_callable_call(in godot_callable p_callable, + godot_variant** p_args, int p_arg_count, out godot_variant_call_error p_call_error); + + internal static partial void godotsharp_callable_call_deferred(in godot_callable p_callable, + godot_variant** p_args, int p_arg_count); + + // GDNative functions + + // gdnative.h + + public static partial void godotsharp_method_bind_ptrcall(IntPtr p_method_bind, IntPtr p_instance, void** p_args, + void* p_ret); + + public static partial godot_variant godotsharp_method_bind_call(IntPtr p_method_bind, IntPtr p_instance, + godot_variant** p_args, int p_arg_count, out godot_variant_call_error p_call_error); + + // variant.h + + public static partial void + godotsharp_variant_new_string_name(out godot_variant r_dest, in godot_string_name p_s); + + public static partial void godotsharp_variant_new_copy(out godot_variant r_dest, in godot_variant p_src); + + public static partial void godotsharp_variant_new_node_path(out godot_variant r_dest, in godot_node_path p_np); + + public static partial void godotsharp_variant_new_object(out godot_variant r_dest, IntPtr p_obj); + + public static partial void godotsharp_variant_new_transform2d(out godot_variant r_dest, in Transform2D p_t2d); + + public static partial void godotsharp_variant_new_vector4(out godot_variant r_dest, in Vector4 p_vec4); + + public static partial void godotsharp_variant_new_vector4i(out godot_variant r_dest, in Vector4i p_vec4i); + + public static partial void godotsharp_variant_new_basis(out godot_variant r_dest, in Basis p_basis); + + public static partial void godotsharp_variant_new_transform3d(out godot_variant r_dest, in Transform3D p_trans); + + public static partial void godotsharp_variant_new_projection(out godot_variant r_dest, in Projection p_proj); + + public static partial void godotsharp_variant_new_aabb(out godot_variant r_dest, in AABB p_aabb); + + public static partial void godotsharp_variant_new_dictionary(out godot_variant r_dest, + in godot_dictionary p_dict); + + public static partial void godotsharp_variant_new_array(out godot_variant r_dest, in godot_array p_arr); + + public static partial void godotsharp_variant_new_packed_byte_array(out godot_variant r_dest, + in godot_packed_byte_array p_pba); + + public static partial void godotsharp_variant_new_packed_int32_array(out godot_variant r_dest, + in godot_packed_int32_array p_pia); + + public static partial void godotsharp_variant_new_packed_int64_array(out godot_variant r_dest, + in godot_packed_int64_array p_pia); + + public static partial void godotsharp_variant_new_packed_float32_array(out godot_variant r_dest, + in godot_packed_float32_array p_pra); + + public static partial void godotsharp_variant_new_packed_float64_array(out godot_variant r_dest, + in godot_packed_float64_array p_pra); + + public static partial void godotsharp_variant_new_packed_string_array(out godot_variant r_dest, + in godot_packed_string_array p_psa); + + public static partial void godotsharp_variant_new_packed_vector2_array(out godot_variant r_dest, + in godot_packed_vector2_array p_pv2a); + + public static partial void godotsharp_variant_new_packed_vector3_array(out godot_variant r_dest, + in godot_packed_vector3_array p_pv3a); + + public static partial void godotsharp_variant_new_packed_color_array(out godot_variant r_dest, + in godot_packed_color_array p_pca); + + public static partial godot_bool godotsharp_variant_as_bool(in godot_variant p_self); + + public static partial Int64 godotsharp_variant_as_int(in godot_variant p_self); + + public static partial double godotsharp_variant_as_float(in godot_variant p_self); + + public static partial godot_string godotsharp_variant_as_string(in godot_variant p_self); + + public static partial Vector2 godotsharp_variant_as_vector2(in godot_variant p_self); + + public static partial Vector2i godotsharp_variant_as_vector2i(in godot_variant p_self); + + public static partial Rect2 godotsharp_variant_as_rect2(in godot_variant p_self); + + public static partial Rect2i godotsharp_variant_as_rect2i(in godot_variant p_self); + + public static partial Vector3 godotsharp_variant_as_vector3(in godot_variant p_self); + + public static partial Vector3i godotsharp_variant_as_vector3i(in godot_variant p_self); + + public static partial Transform2D godotsharp_variant_as_transform2d(in godot_variant p_self); + + public static partial Vector4 godotsharp_variant_as_vector4(in godot_variant p_self); + + public static partial Vector4i godotsharp_variant_as_vector4i(in godot_variant p_self); + + public static partial Plane godotsharp_variant_as_plane(in godot_variant p_self); + + public static partial Quaternion godotsharp_variant_as_quaternion(in godot_variant p_self); + + public static partial AABB godotsharp_variant_as_aabb(in godot_variant p_self); + + public static partial Basis godotsharp_variant_as_basis(in godot_variant p_self); + + public static partial Transform3D godotsharp_variant_as_transform3d(in godot_variant p_self); + + public static partial Projection godotsharp_variant_as_projection(in godot_variant p_self); + + public static partial Color godotsharp_variant_as_color(in godot_variant p_self); + + public static partial godot_string_name godotsharp_variant_as_string_name(in godot_variant p_self); + + public static partial godot_node_path godotsharp_variant_as_node_path(in godot_variant p_self); + + public static partial RID godotsharp_variant_as_rid(in godot_variant p_self); + + public static partial godot_callable godotsharp_variant_as_callable(in godot_variant p_self); + + public static partial godot_signal godotsharp_variant_as_signal(in godot_variant p_self); + + public static partial godot_dictionary godotsharp_variant_as_dictionary(in godot_variant p_self); + + public static partial godot_array godotsharp_variant_as_array(in godot_variant p_self); + + public static partial godot_packed_byte_array godotsharp_variant_as_packed_byte_array(in godot_variant p_self); + + public static partial godot_packed_int32_array godotsharp_variant_as_packed_int32_array(in godot_variant p_self); + + public static partial godot_packed_int64_array godotsharp_variant_as_packed_int64_array(in godot_variant p_self); + + public static partial godot_packed_float32_array godotsharp_variant_as_packed_float32_array( + in godot_variant p_self); + + public static partial godot_packed_float64_array godotsharp_variant_as_packed_float64_array( + in godot_variant p_self); + + public static partial godot_packed_string_array godotsharp_variant_as_packed_string_array( + in godot_variant p_self); + + public static partial godot_packed_vector2_array godotsharp_variant_as_packed_vector2_array( + in godot_variant p_self); + + public static partial godot_packed_vector3_array godotsharp_variant_as_packed_vector3_array( + in godot_variant p_self); + + public static partial godot_packed_color_array godotsharp_variant_as_packed_color_array(in godot_variant p_self); + + public static partial godot_bool godotsharp_variant_equals(in godot_variant p_a, in godot_variant p_b); + + // string.h + + public static partial void godotsharp_string_new_with_utf16_chars(out godot_string r_dest, char* p_contents); + + // string_name.h + + public static partial void godotsharp_string_name_new_copy(out godot_string_name r_dest, + in godot_string_name p_src); + + // node_path.h + + public static partial void godotsharp_node_path_new_copy(out godot_node_path r_dest, in godot_node_path p_src); + + // array.h + + public static partial void godotsharp_array_new(out godot_array r_dest); + + public static partial void godotsharp_array_new_copy(out godot_array r_dest, in godot_array p_src); + + public static partial godot_variant* godotsharp_array_ptrw(ref godot_array p_self); + + // dictionary.h + + public static partial void godotsharp_dictionary_new(out godot_dictionary r_dest); + + public static partial void godotsharp_dictionary_new_copy(out godot_dictionary r_dest, + in godot_dictionary p_src); + + // destroy functions + + public static partial void godotsharp_packed_byte_array_destroy(ref godot_packed_byte_array p_self); + + public static partial void godotsharp_packed_int32_array_destroy(ref godot_packed_int32_array p_self); + + public static partial void godotsharp_packed_int64_array_destroy(ref godot_packed_int64_array p_self); + + public static partial void godotsharp_packed_float32_array_destroy(ref godot_packed_float32_array p_self); + + public static partial void godotsharp_packed_float64_array_destroy(ref godot_packed_float64_array p_self); + + public static partial void godotsharp_packed_string_array_destroy(ref godot_packed_string_array p_self); + + public static partial void godotsharp_packed_vector2_array_destroy(ref godot_packed_vector2_array p_self); + + public static partial void godotsharp_packed_vector3_array_destroy(ref godot_packed_vector3_array p_self); + + public static partial void godotsharp_packed_color_array_destroy(ref godot_packed_color_array p_self); + + public static partial void godotsharp_variant_destroy(ref godot_variant p_self); + + public static partial void godotsharp_string_destroy(ref godot_string p_self); + + public static partial void godotsharp_string_name_destroy(ref godot_string_name p_self); + + public static partial void godotsharp_node_path_destroy(ref godot_node_path p_self); + + public static partial void godotsharp_signal_destroy(ref godot_signal p_self); + + public static partial void godotsharp_callable_destroy(ref godot_callable p_self); + + public static partial void godotsharp_array_destroy(ref godot_array p_self); + + public static partial void godotsharp_dictionary_destroy(ref godot_dictionary p_self); + + // Array + + public static partial int godotsharp_array_add(ref godot_array p_self, in godot_variant p_item); + + public static partial void + godotsharp_array_duplicate(ref godot_array p_self, godot_bool p_deep, out godot_array r_dest); + + public static partial int godotsharp_array_index_of(ref godot_array p_self, in godot_variant p_item); + + public static partial void godotsharp_array_insert(ref godot_array p_self, int p_index, in godot_variant p_item); + + public static partial void godotsharp_array_remove_at(ref godot_array p_self, int p_index); + + public static partial Error godotsharp_array_resize(ref godot_array p_self, int p_new_size); + + public static partial Error godotsharp_array_shuffle(ref godot_array p_self); + + public static partial void godotsharp_array_to_string(ref godot_array p_self, out godot_string r_str); + + // Dictionary + + public static partial godot_bool godotsharp_dictionary_try_get_value(ref godot_dictionary p_self, + in godot_variant p_key, + out godot_variant r_value); + + public static partial void godotsharp_dictionary_set_value(ref godot_dictionary p_self, in godot_variant p_key, + in godot_variant p_value); + + public static partial void godotsharp_dictionary_keys(ref godot_dictionary p_self, out godot_array r_dest); + + public static partial void godotsharp_dictionary_values(ref godot_dictionary p_self, out godot_array r_dest); + + public static partial int godotsharp_dictionary_count(ref godot_dictionary p_self); + + public static partial void godotsharp_dictionary_key_value_pair_at(ref godot_dictionary p_self, int p_index, + out godot_variant r_key, out godot_variant r_value); + + public static partial void godotsharp_dictionary_add(ref godot_dictionary p_self, in godot_variant p_key, + in godot_variant p_value); + + public static partial void godotsharp_dictionary_clear(ref godot_dictionary p_self); + + public static partial godot_bool godotsharp_dictionary_contains_key(ref godot_dictionary p_self, + in godot_variant p_key); + + public static partial void godotsharp_dictionary_duplicate(ref godot_dictionary p_self, godot_bool p_deep, + out godot_dictionary r_dest); + + public static partial godot_bool godotsharp_dictionary_remove_key(ref godot_dictionary p_self, + in godot_variant p_key); + + public static partial void godotsharp_dictionary_to_string(ref godot_dictionary p_self, out godot_string r_str); + + // StringExtensions + + public static partial void godotsharp_string_md5_buffer(in godot_string p_self, + out godot_packed_byte_array r_md5_buffer); + + public static partial void godotsharp_string_md5_text(in godot_string p_self, out godot_string r_md5_text); + + public static partial int godotsharp_string_rfind(in godot_string p_self, in godot_string p_what, int p_from); + + public static partial int godotsharp_string_rfindn(in godot_string p_self, in godot_string p_what, int p_from); + + public static partial void godotsharp_string_sha256_buffer(in godot_string p_self, + out godot_packed_byte_array r_sha256_buffer); + + public static partial void godotsharp_string_sha256_text(in godot_string p_self, + out godot_string r_sha256_text); + + public static partial void godotsharp_string_simplify_path(in godot_string p_self, + out godot_string r_simplified_path); + + // NodePath + + public static partial void godotsharp_node_path_get_as_property_path(in godot_node_path p_self, + ref godot_node_path r_dest); + + public static partial void godotsharp_node_path_get_concatenated_names(in godot_node_path p_self, + out godot_string r_names); + + public static partial void godotsharp_node_path_get_concatenated_subnames(in godot_node_path p_self, + out godot_string r_subnames); + + public static partial void godotsharp_node_path_get_name(in godot_node_path p_self, int p_idx, + out godot_string r_name); + + public static partial int godotsharp_node_path_get_name_count(in godot_node_path p_self); + + public static partial void godotsharp_node_path_get_subname(in godot_node_path p_self, int p_idx, + out godot_string r_subname); + + public static partial int godotsharp_node_path_get_subname_count(in godot_node_path p_self); + + public static partial godot_bool godotsharp_node_path_is_absolute(in godot_node_path p_self); + + // GD, etc + + internal static partial void godotsharp_bytes2var(in godot_packed_byte_array p_bytes, + godot_bool p_allow_objects, + out godot_variant r_ret); + + internal static partial void godotsharp_convert(in godot_variant p_what, int p_type, + out godot_variant r_ret); + + internal static partial int godotsharp_hash(in godot_variant p_var); + + internal static partial IntPtr godotsharp_instance_from_id(ulong p_instance_id); + + internal static partial void godotsharp_print(in godot_string p_what); + + public static partial void godotsharp_print_rich(in godot_string p_what); + + internal static partial void godotsharp_printerr(in godot_string p_what); + + internal static partial void godotsharp_printraw(in godot_string p_what); + + internal static partial void godotsharp_prints(in godot_string p_what); + + internal static partial void godotsharp_printt(in godot_string p_what); + + internal static partial float godotsharp_randf(); + + internal static partial uint godotsharp_randi(); + + internal static partial void godotsharp_randomize(); + + internal static partial double godotsharp_randf_range(double from, double to); + + internal static partial double godotsharp_randfn(double mean, double deviation); + + internal static partial int godotsharp_randi_range(int from, int to); + + internal static partial uint godotsharp_rand_from_seed(ulong seed, out ulong newSeed); + + internal static partial void godotsharp_seed(ulong seed); + + internal static partial void godotsharp_weakref(IntPtr p_obj, out godot_ref r_weak_ref); + + internal static partial void godotsharp_str(in godot_array p_what, out godot_string r_ret); + + internal static partial void godotsharp_str2var(in godot_string p_str, out godot_variant r_ret); + + internal static partial void godotsharp_var2bytes(in godot_variant p_what, godot_bool p_full_objects, + out godot_packed_byte_array r_bytes); + + internal static partial void godotsharp_var2str(in godot_variant p_var, out godot_string r_ret); + + internal static partial void godotsharp_pusherror(in godot_string p_str); + + internal static partial void godotsharp_pushwarning(in godot_string p_str); + + // Object + + public static partial void godotsharp_object_to_string(IntPtr ptr, out godot_string r_str); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs new file mode 100644 index 0000000000..2ea3c18d26 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs @@ -0,0 +1,99 @@ +// ReSharper disable InconsistentNaming + +namespace Godot.NativeInterop +{ + public static partial class NativeFuncs + { + public static godot_variant godotsharp_variant_new_copy(in godot_variant src) + { + switch (src.Type) + { + case Variant.Type.Nil: + return default; + case Variant.Type.Bool: + return new godot_variant() { Bool = src.Bool }; + case Variant.Type.Int: + return new godot_variant() { Int = src.Int }; + case Variant.Type.Float: + return new godot_variant() { Float = src.Float }; + case Variant.Type.Vector2: + return new godot_variant() { Vector2 = src.Vector2 }; + case Variant.Type.Vector2i: + return new godot_variant() { Vector2i = src.Vector2i }; + case Variant.Type.Rect2: + return new godot_variant() { Rect2 = src.Rect2 }; + case Variant.Type.Rect2i: + return new godot_variant() { Rect2i = src.Rect2i }; + case Variant.Type.Vector3: + return new godot_variant() { Vector3 = src.Vector3 }; + case Variant.Type.Vector3i: + return new godot_variant() { Vector3i = src.Vector3i }; + case Variant.Type.Plane: + return new godot_variant() { Plane = src.Plane }; + case Variant.Type.Quaternion: + return new godot_variant() { Quaternion = src.Quaternion }; + case Variant.Type.Color: + return new godot_variant() { Color = src.Color }; + case Variant.Type.Rid: + return new godot_variant() { RID = src.RID }; + } + + godotsharp_variant_new_copy(out godot_variant ret, src); + return ret; + } + + public static godot_string_name godotsharp_string_name_new_copy(in godot_string_name src) + { + if (src.IsEmpty) + return default; + godotsharp_string_name_new_copy(out godot_string_name ret, src); + return ret; + } + + public static godot_node_path godotsharp_node_path_new_copy(in godot_node_path src) + { + if (src.IsEmpty) + return default; + godotsharp_node_path_new_copy(out godot_node_path ret, src); + return ret; + } + + public static godot_array godotsharp_array_new() + { + godotsharp_array_new(out godot_array ret); + return ret; + } + + public static godot_array godotsharp_array_new_copy(in godot_array src) + { + godotsharp_array_new_copy(out godot_array ret, src); + return ret; + } + + public static godot_dictionary godotsharp_dictionary_new() + { + godotsharp_dictionary_new(out godot_dictionary ret); + return ret; + } + + public static godot_dictionary godotsharp_dictionary_new_copy(in godot_dictionary src) + { + godotsharp_dictionary_new_copy(out godot_dictionary ret, src); + return ret; + } + + public static godot_string_name godotsharp_string_name_new_from_string(string name) + { + using godot_string src = Marshaling.ConvertStringToNative(name); + godotsharp_string_name_new_from_string(out godot_string_name ret, src); + return ret; + } + + public static godot_node_path godotsharp_node_path_new_from_string(string name) + { + using godot_string src = Marshaling.ConvertStringToNative(name); + godotsharp_node_path_new_from_string(out godot_node_path ret, src); + return ret; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs new file mode 100644 index 0000000000..422df74c23 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeVariantPtrArgs.cs @@ -0,0 +1,20 @@ +using System.Runtime.CompilerServices; + +namespace Godot.NativeInterop +{ + // Our source generators will add trampolines methods that access variant arguments. + // This struct makes that possible without having to enable `AllowUnsafeBlocks` in game projects. + + public unsafe ref struct NativeVariantPtrArgs + { + private godot_variant** _args; + + internal NativeVariantPtrArgs(godot_variant** args) => _args = args; + + public ref godot_variant this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref *_args[index]; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs new file mode 100644 index 0000000000..2b5bf2e142 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantConversionCallbacks.cs @@ -0,0 +1,976 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Godot.NativeInterop; + +internal static unsafe class VariantConversionCallbacks +{ + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + internal static delegate* <in T, godot_variant> GetToVariantCallback<T>() + { + static godot_variant FromBool(in bool @bool) => + VariantUtils.CreateFromBool(@bool); + + static godot_variant FromChar(in char @char) => + VariantUtils.CreateFromInt(@char); + + static godot_variant FromInt8(in sbyte @int8) => + VariantUtils.CreateFromInt(@int8); + + static godot_variant FromInt16(in short @int16) => + VariantUtils.CreateFromInt(@int16); + + static godot_variant FromInt32(in int @int32) => + VariantUtils.CreateFromInt(@int32); + + static godot_variant FromInt64(in long @int64) => + VariantUtils.CreateFromInt(@int64); + + static godot_variant FromUInt8(in byte @uint8) => + VariantUtils.CreateFromInt(@uint8); + + static godot_variant FromUInt16(in ushort @uint16) => + VariantUtils.CreateFromInt(@uint16); + + static godot_variant FromUInt32(in uint @uint32) => + VariantUtils.CreateFromInt(@uint32); + + static godot_variant FromUInt64(in ulong @uint64) => + VariantUtils.CreateFromInt(@uint64); + + static godot_variant FromFloat(in float @float) => + VariantUtils.CreateFromFloat(@float); + + static godot_variant FromDouble(in double @double) => + VariantUtils.CreateFromFloat(@double); + + static godot_variant FromVector2(in Vector2 @vector2) => + VariantUtils.CreateFromVector2(@vector2); + + static godot_variant FromVector2I(in Vector2i vector2I) => + VariantUtils.CreateFromVector2i(vector2I); + + static godot_variant FromRect2(in Rect2 @rect2) => + VariantUtils.CreateFromRect2(@rect2); + + static godot_variant FromRect2I(in Rect2i rect2I) => + VariantUtils.CreateFromRect2i(rect2I); + + static godot_variant FromTransform2D(in Transform2D @transform2D) => + VariantUtils.CreateFromTransform2D(@transform2D); + + static godot_variant FromVector3(in Vector3 @vector3) => + VariantUtils.CreateFromVector3(@vector3); + + static godot_variant FromVector3I(in Vector3i vector3I) => + VariantUtils.CreateFromVector3i(vector3I); + + static godot_variant FromBasis(in Basis @basis) => + VariantUtils.CreateFromBasis(@basis); + + static godot_variant FromQuaternion(in Quaternion @quaternion) => + VariantUtils.CreateFromQuaternion(@quaternion); + + static godot_variant FromTransform3D(in Transform3D @transform3d) => + VariantUtils.CreateFromTransform3D(@transform3d); + + static godot_variant FromAabb(in AABB @aabb) => + VariantUtils.CreateFromAABB(@aabb); + + static godot_variant FromColor(in Color @color) => + VariantUtils.CreateFromColor(@color); + + static godot_variant FromPlane(in Plane @plane) => + VariantUtils.CreateFromPlane(@plane); + + static godot_variant FromCallable(in Callable @callable) => + VariantUtils.CreateFromCallable(@callable); + + static godot_variant FromSignalInfo(in SignalInfo @signalInfo) => + VariantUtils.CreateFromSignalInfo(@signalInfo); + + static godot_variant FromString(in string @string) => + VariantUtils.CreateFromString(@string); + + static godot_variant FromByteArray(in byte[] byteArray) => + VariantUtils.CreateFromPackedByteArray(byteArray); + + static godot_variant FromInt32Array(in int[] int32Array) => + VariantUtils.CreateFromPackedInt32Array(int32Array); + + static godot_variant FromInt64Array(in long[] int64Array) => + VariantUtils.CreateFromPackedInt64Array(int64Array); + + static godot_variant FromFloatArray(in float[] floatArray) => + VariantUtils.CreateFromPackedFloat32Array(floatArray); + + static godot_variant FromDoubleArray(in double[] doubleArray) => + VariantUtils.CreateFromPackedFloat64Array(doubleArray); + + static godot_variant FromStringArray(in string[] stringArray) => + VariantUtils.CreateFromPackedStringArray(stringArray); + + static godot_variant FromVector2Array(in Vector2[] vector2Array) => + VariantUtils.CreateFromPackedVector2Array(vector2Array); + + static godot_variant FromVector3Array(in Vector3[] vector3Array) => + VariantUtils.CreateFromPackedVector3Array(vector3Array); + + static godot_variant FromColorArray(in Color[] colorArray) => + VariantUtils.CreateFromPackedColorArray(colorArray); + + static godot_variant FromStringNameArray(in StringName[] stringNameArray) => + VariantUtils.CreateFromSystemArrayOfStringName(stringNameArray); + + static godot_variant FromNodePathArray(in NodePath[] nodePathArray) => + VariantUtils.CreateFromSystemArrayOfNodePath(nodePathArray); + + static godot_variant FromRidArray(in RID[] ridArray) => + VariantUtils.CreateFromSystemArrayOfRID(ridArray); + + static godot_variant FromGodotObject(in Godot.Object godotObject) => + VariantUtils.CreateFromGodotObject(godotObject); + + static godot_variant FromStringName(in StringName stringName) => + VariantUtils.CreateFromStringName(stringName); + + static godot_variant FromNodePath(in NodePath nodePath) => + VariantUtils.CreateFromNodePath(nodePath); + + static godot_variant FromRid(in RID rid) => + VariantUtils.CreateFromRID(rid); + + static godot_variant FromGodotDictionary(in Collections.Dictionary godotDictionary) => + VariantUtils.CreateFromDictionary(godotDictionary); + + static godot_variant FromGodotArray(in Collections.Array godotArray) => + VariantUtils.CreateFromArray(godotArray); + + static godot_variant FromVariant(in Variant variant) => + NativeFuncs.godotsharp_variant_new_copy((godot_variant)variant.NativeVar); + + var typeOfT = typeof(T); + + if (typeOfT == typeof(bool)) + { + return (delegate* <in T, godot_variant>)(delegate* <in bool, godot_variant>) + &FromBool; + } + + if (typeOfT == typeof(char)) + { + return (delegate* <in T, godot_variant>)(delegate* <in char, godot_variant>) + &FromChar; + } + + if (typeOfT == typeof(sbyte)) + { + return (delegate* <in T, godot_variant>)(delegate* <in sbyte, godot_variant>) + &FromInt8; + } + + if (typeOfT == typeof(short)) + { + return (delegate* <in T, godot_variant>)(delegate* <in short, godot_variant>) + &FromInt16; + } + + if (typeOfT == typeof(int)) + { + return (delegate* <in T, godot_variant>)(delegate* <in int, godot_variant>) + &FromInt32; + } + + if (typeOfT == typeof(long)) + { + return (delegate* <in T, godot_variant>)(delegate* <in long, godot_variant>) + &FromInt64; + } + + if (typeOfT == typeof(byte)) + { + return (delegate* <in T, godot_variant>)(delegate* <in byte, godot_variant>) + &FromUInt8; + } + + if (typeOfT == typeof(ushort)) + { + return (delegate* <in T, godot_variant>)(delegate* <in ushort, godot_variant>) + &FromUInt16; + } + + if (typeOfT == typeof(uint)) + { + return (delegate* <in T, godot_variant>)(delegate* <in uint, godot_variant>) + &FromUInt32; + } + + if (typeOfT == typeof(ulong)) + { + return (delegate* <in T, godot_variant>)(delegate* <in ulong, godot_variant>) + &FromUInt64; + } + + if (typeOfT == typeof(float)) + { + return (delegate* <in T, godot_variant>)(delegate* <in float, godot_variant>) + &FromFloat; + } + + if (typeOfT == typeof(double)) + { + return (delegate* <in T, godot_variant>)(delegate* <in double, godot_variant>) + &FromDouble; + } + + if (typeOfT == typeof(Vector2)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector2, godot_variant>) + &FromVector2; + } + + if (typeOfT == typeof(Vector2i)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector2i, godot_variant>) + &FromVector2I; + } + + if (typeOfT == typeof(Rect2)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Rect2, godot_variant>) + &FromRect2; + } + + if (typeOfT == typeof(Rect2i)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Rect2i, godot_variant>) + &FromRect2I; + } + + if (typeOfT == typeof(Transform2D)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Transform2D, godot_variant>) + &FromTransform2D; + } + + if (typeOfT == typeof(Vector3)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector3, godot_variant>) + &FromVector3; + } + + if (typeOfT == typeof(Vector3i)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector3i, godot_variant>) + &FromVector3I; + } + + if (typeOfT == typeof(Basis)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Basis, godot_variant>) + &FromBasis; + } + + if (typeOfT == typeof(Quaternion)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Quaternion, godot_variant>) + &FromQuaternion; + } + + if (typeOfT == typeof(Transform3D)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Transform3D, godot_variant>) + &FromTransform3D; + } + + if (typeOfT == typeof(AABB)) + { + return (delegate* <in T, godot_variant>)(delegate* <in AABB, godot_variant>) + &FromAabb; + } + + if (typeOfT == typeof(Color)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Color, godot_variant>) + &FromColor; + } + + if (typeOfT == typeof(Plane)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Plane, godot_variant>) + &FromPlane; + } + + if (typeOfT == typeof(Callable)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Callable, godot_variant>) + &FromCallable; + } + + if (typeOfT == typeof(SignalInfo)) + { + return (delegate* <in T, godot_variant>)(delegate* <in SignalInfo, godot_variant>) + &FromSignalInfo; + } + + if (typeOfT.IsEnum) + { + var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); + + switch (Type.GetTypeCode(enumUnderlyingType)) + { + case TypeCode.SByte: + { + return (delegate* <in T, godot_variant>)(delegate* <in sbyte, godot_variant>) + &FromInt8; + } + case TypeCode.Int16: + { + return (delegate* <in T, godot_variant>)(delegate* <in short, godot_variant>) + &FromInt16; + } + case TypeCode.Int32: + { + return (delegate* <in T, godot_variant>)(delegate* <in int, godot_variant>) + &FromInt32; + } + case TypeCode.Int64: + { + return (delegate* <in T, godot_variant>)(delegate* <in long, godot_variant>) + &FromInt64; + } + case TypeCode.Byte: + { + return (delegate* <in T, godot_variant>)(delegate* <in byte, godot_variant>) + &FromUInt8; + } + case TypeCode.UInt16: + { + return (delegate* <in T, godot_variant>)(delegate* <in ushort, godot_variant>) + &FromUInt16; + } + case TypeCode.UInt32: + { + return (delegate* <in T, godot_variant>)(delegate* <in uint, godot_variant>) + &FromUInt32; + } + case TypeCode.UInt64: + { + return (delegate* <in T, godot_variant>)(delegate* <in ulong, godot_variant>) + &FromUInt64; + } + default: + return null; + } + } + + if (typeOfT == typeof(string)) + { + return (delegate* <in T, godot_variant>)(delegate* <in string, godot_variant>) + &FromString; + } + + if (typeOfT == typeof(byte[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in byte[], godot_variant>) + &FromByteArray; + } + + if (typeOfT == typeof(int[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in int[], godot_variant>) + &FromInt32Array; + } + + if (typeOfT == typeof(long[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in long[], godot_variant>) + &FromInt64Array; + } + + if (typeOfT == typeof(float[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in float[], godot_variant>) + &FromFloatArray; + } + + if (typeOfT == typeof(double[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in double[], godot_variant>) + &FromDoubleArray; + } + + if (typeOfT == typeof(string[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in string[], godot_variant>) + &FromStringArray; + } + + if (typeOfT == typeof(Vector2[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector2[], godot_variant>) + &FromVector2Array; + } + + if (typeOfT == typeof(Vector3[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in Vector3[], godot_variant>) + &FromVector3Array; + } + + if (typeOfT == typeof(Color[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in Color[], godot_variant>) + &FromColorArray; + } + + if (typeOfT == typeof(StringName[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in StringName[], godot_variant>) + &FromStringNameArray; + } + + if (typeOfT == typeof(NodePath[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in NodePath[], godot_variant>) + &FromNodePathArray; + } + + if (typeOfT == typeof(RID[])) + { + return (delegate* <in T, godot_variant>)(delegate* <in RID[], godot_variant>) + &FromRidArray; + } + + if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Godot.Object, godot_variant>) + &FromGodotObject; + } + + if (typeOfT == typeof(StringName)) + { + return (delegate* <in T, godot_variant>)(delegate* <in StringName, godot_variant>) + &FromStringName; + } + + if (typeOfT == typeof(NodePath)) + { + return (delegate* <in T, godot_variant>)(delegate* <in NodePath, godot_variant>) + &FromNodePath; + } + + if (typeOfT == typeof(RID)) + { + return (delegate* <in T, godot_variant>)(delegate* <in RID, godot_variant>) + &FromRid; + } + + if (typeOfT == typeof(Godot.Collections.Dictionary)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Godot.Collections.Dictionary, godot_variant>) + &FromGodotDictionary; + } + + if (typeOfT == typeof(Godot.Collections.Array)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Godot.Collections.Array, godot_variant>) + &FromGodotArray; + } + + if (typeOfT == typeof(Variant)) + { + return (delegate* <in T, godot_variant>)(delegate* <in Variant, godot_variant>) + &FromVariant; + } + + return null; + } + + [SuppressMessage("ReSharper", "RedundantNameQualifier")] + internal static delegate* <in godot_variant, T> GetToManagedCallback<T>() + { + static bool ToBool(in godot_variant variant) => + VariantUtils.ConvertToBool(variant); + + static char ToChar(in godot_variant variant) => + VariantUtils.ConvertToChar(variant); + + static sbyte ToInt8(in godot_variant variant) => + VariantUtils.ConvertToInt8(variant); + + static short ToInt16(in godot_variant variant) => + VariantUtils.ConvertToInt16(variant); + + static int ToInt32(in godot_variant variant) => + VariantUtils.ConvertToInt32(variant); + + static long ToInt64(in godot_variant variant) => + VariantUtils.ConvertToInt64(variant); + + static byte ToUInt8(in godot_variant variant) => + VariantUtils.ConvertToUInt8(variant); + + static ushort ToUInt16(in godot_variant variant) => + VariantUtils.ConvertToUInt16(variant); + + static uint ToUInt32(in godot_variant variant) => + VariantUtils.ConvertToUInt32(variant); + + static ulong ToUInt64(in godot_variant variant) => + VariantUtils.ConvertToUInt64(variant); + + static float ToFloat(in godot_variant variant) => + VariantUtils.ConvertToFloat32(variant); + + static double ToDouble(in godot_variant variant) => + VariantUtils.ConvertToFloat64(variant); + + static Vector2 ToVector2(in godot_variant variant) => + VariantUtils.ConvertToVector2(variant); + + static Vector2i ToVector2I(in godot_variant variant) => + VariantUtils.ConvertToVector2i(variant); + + static Rect2 ToRect2(in godot_variant variant) => + VariantUtils.ConvertToRect2(variant); + + static Rect2i ToRect2I(in godot_variant variant) => + VariantUtils.ConvertToRect2i(variant); + + static Transform2D ToTransform2D(in godot_variant variant) => + VariantUtils.ConvertToTransform2D(variant); + + static Vector3 ToVector3(in godot_variant variant) => + VariantUtils.ConvertToVector3(variant); + + static Vector3i ToVector3I(in godot_variant variant) => + VariantUtils.ConvertToVector3i(variant); + + static Basis ToBasis(in godot_variant variant) => + VariantUtils.ConvertToBasis(variant); + + static Quaternion ToQuaternion(in godot_variant variant) => + VariantUtils.ConvertToQuaternion(variant); + + static Transform3D ToTransform3D(in godot_variant variant) => + VariantUtils.ConvertToTransform3D(variant); + + static AABB ToAabb(in godot_variant variant) => + VariantUtils.ConvertToAABB(variant); + + static Color ToColor(in godot_variant variant) => + VariantUtils.ConvertToColor(variant); + + static Plane ToPlane(in godot_variant variant) => + VariantUtils.ConvertToPlane(variant); + + static Callable ToCallable(in godot_variant variant) => + VariantUtils.ConvertToCallableManaged(variant); + + static SignalInfo ToSignalInfo(in godot_variant variant) => + VariantUtils.ConvertToSignalInfo(variant); + + static string ToString(in godot_variant variant) => + VariantUtils.ConvertToStringObject(variant); + + static byte[] ToByteArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedByteArrayToSystemArray(variant); + + static int[] ToInt32Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedInt32ArrayToSystemArray(variant); + + static long[] ToInt64Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedInt64ArrayToSystemArray(variant); + + static float[] ToFloatArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray(variant); + + static double[] ToDoubleArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray(variant); + + static string[] ToStringArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedStringArrayToSystemArray(variant); + + static Vector2[] ToVector2Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedVector2ArrayToSystemArray(variant); + + static Vector3[] ToVector3Array(in godot_variant variant) => + VariantUtils.ConvertAsPackedVector3ArrayToSystemArray(variant); + + static Color[] ToColorArray(in godot_variant variant) => + VariantUtils.ConvertAsPackedColorArrayToSystemArray(variant); + + static StringName[] ToStringNameArray(in godot_variant variant) => + VariantUtils.ConvertToSystemArrayOfStringName(variant); + + static NodePath[] ToNodePathArray(in godot_variant variant) => + VariantUtils.ConvertToSystemArrayOfNodePath(variant); + + static RID[] ToRidArray(in godot_variant variant) => + VariantUtils.ConvertToSystemArrayOfRID(variant); + + static Godot.Object ToGodotObject(in godot_variant variant) => + VariantUtils.ConvertToGodotObject(variant); + + static StringName ToStringName(in godot_variant variant) => + VariantUtils.ConvertToStringNameObject(variant); + + static NodePath ToNodePath(in godot_variant variant) => + VariantUtils.ConvertToNodePathObject(variant); + + static RID ToRid(in godot_variant variant) => + VariantUtils.ConvertToRID(variant); + + static Collections.Dictionary ToGodotDictionary(in godot_variant variant) => + VariantUtils.ConvertToDictionaryObject(variant); + + static Collections.Array ToGodotArray(in godot_variant variant) => + VariantUtils.ConvertToArrayObject(variant); + + static Variant ToVariant(in godot_variant variant) => + Variant.CreateCopyingBorrowed(variant); + + var typeOfT = typeof(T); + + // ReSharper disable RedundantCast + // Rider is being stupid here. These casts are definitely needed. We get build errors without them. + + if (typeOfT == typeof(bool)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, bool>) + &ToBool; + } + + if (typeOfT == typeof(char)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, char>) + &ToChar; + } + + if (typeOfT == typeof(sbyte)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, sbyte>) + &ToInt8; + } + + if (typeOfT == typeof(short)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, short>) + &ToInt16; + } + + if (typeOfT == typeof(int)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, int>) + &ToInt32; + } + + if (typeOfT == typeof(long)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, long>) + &ToInt64; + } + + if (typeOfT == typeof(byte)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, byte>) + &ToUInt8; + } + + if (typeOfT == typeof(ushort)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, ushort>) + &ToUInt16; + } + + if (typeOfT == typeof(uint)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, uint>) + &ToUInt32; + } + + if (typeOfT == typeof(ulong)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, ulong>) + &ToUInt64; + } + + if (typeOfT == typeof(float)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, float>) + &ToFloat; + } + + if (typeOfT == typeof(double)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, double>) + &ToDouble; + } + + if (typeOfT == typeof(Vector2)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector2>) + &ToVector2; + } + + if (typeOfT == typeof(Vector2i)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector2i>) + &ToVector2I; + } + + if (typeOfT == typeof(Rect2)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Rect2>) + &ToRect2; + } + + if (typeOfT == typeof(Rect2i)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Rect2i>) + &ToRect2I; + } + + if (typeOfT == typeof(Transform2D)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Transform2D>) + &ToTransform2D; + } + + if (typeOfT == typeof(Vector3)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector3>) + &ToVector3; + } + + if (typeOfT == typeof(Vector3i)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector3i>) + &ToVector3I; + } + + if (typeOfT == typeof(Basis)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Basis>) + &ToBasis; + } + + if (typeOfT == typeof(Quaternion)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Quaternion>) + &ToQuaternion; + } + + if (typeOfT == typeof(Transform3D)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Transform3D>) + &ToTransform3D; + } + + if (typeOfT == typeof(AABB)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, AABB>) + &ToAabb; + } + + if (typeOfT == typeof(Color)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Color>) + &ToColor; + } + + if (typeOfT == typeof(Plane)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Plane>) + &ToPlane; + } + + if (typeOfT == typeof(Callable)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Callable>) + &ToCallable; + } + + if (typeOfT == typeof(SignalInfo)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, SignalInfo>) + &ToSignalInfo; + } + + if (typeOfT.IsEnum) + { + var enumUnderlyingType = typeOfT.GetEnumUnderlyingType(); + + switch (Type.GetTypeCode(enumUnderlyingType)) + { + case TypeCode.SByte: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, sbyte>) + &ToInt8; + } + case TypeCode.Int16: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, short>) + &ToInt16; + } + case TypeCode.Int32: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, int>) + &ToInt32; + } + case TypeCode.Int64: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, long>) + &ToInt64; + } + case TypeCode.Byte: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, byte>) + &ToUInt8; + } + case TypeCode.UInt16: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, ushort>) + &ToUInt16; + } + case TypeCode.UInt32: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, uint>) + &ToUInt32; + } + case TypeCode.UInt64: + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, ulong>) + &ToUInt64; + } + default: + return null; + } + } + + if (typeOfT == typeof(string)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, string>) + &ToString; + } + + if (typeOfT == typeof(byte[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, byte[]>) + &ToByteArray; + } + + if (typeOfT == typeof(int[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, int[]>) + &ToInt32Array; + } + + if (typeOfT == typeof(long[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, long[]>) + &ToInt64Array; + } + + if (typeOfT == typeof(float[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, float[]>) + &ToFloatArray; + } + + if (typeOfT == typeof(double[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, double[]>) + &ToDoubleArray; + } + + if (typeOfT == typeof(string[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, string[]>) + &ToStringArray; + } + + if (typeOfT == typeof(Vector2[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector2[]>) + &ToVector2Array; + } + + if (typeOfT == typeof(Vector3[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Vector3[]>) + &ToVector3Array; + } + + if (typeOfT == typeof(Color[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Color[]>) + &ToColorArray; + } + + if (typeOfT == typeof(StringName[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, StringName[]>) + &ToStringNameArray; + } + + if (typeOfT == typeof(NodePath[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, NodePath[]>) + &ToNodePathArray; + } + + if (typeOfT == typeof(RID[])) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, RID[]>) + &ToRidArray; + } + + if (typeof(Godot.Object).IsAssignableFrom(typeOfT)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Godot.Object>) + &ToGodotObject; + } + + if (typeOfT == typeof(StringName)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, StringName>) + &ToStringName; + } + + if (typeOfT == typeof(NodePath)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, NodePath>) + &ToNodePath; + } + + if (typeOfT == typeof(RID)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, RID>) + &ToRid; + } + + if (typeOfT == typeof(Godot.Collections.Dictionary)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Godot.Collections.Dictionary>) + &ToGodotDictionary; + } + + if (typeOfT == typeof(Godot.Collections.Array)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Godot.Collections.Array>) + &ToGodotArray; + } + + if (typeOfT == typeof(Variant)) + { + return (delegate* <in godot_variant, T>)(delegate* <in godot_variant, Variant>) + &ToVariant; + } + + // ReSharper restore RedundantCast + + return null; + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs new file mode 100644 index 0000000000..46f31bbf4e --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantSpanHelpers.cs @@ -0,0 +1,33 @@ +using System; + +namespace Godot.NativeInterop +{ + internal readonly ref struct VariantSpanDisposer + { + private readonly Span<godot_variant.movable> _variantSpan; + + // IMPORTANT: The span element must be default initialized. + // Make sure call Clear() on the span if it was created with stackalloc. + public VariantSpanDisposer(Span<godot_variant.movable> variantSpan) + { + _variantSpan = variantSpan; + } + + public void Dispose() + { + for (int i = 0; i < _variantSpan.Length; i++) + _variantSpan[i].DangerousSelfRef.Dispose(); + } + } + + internal static class VariantSpanExtensions + { + // Used to make sure we always initialize the span values to the default, + // as we need that in order to safely dispose all elements after. + public static Span<godot_variant.movable> Cleared(this Span<godot_variant.movable> span) + { + span.Clear(); + return span; + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs new file mode 100644 index 0000000000..491ccf904e --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/VariantUtils.cs @@ -0,0 +1,611 @@ +using System; +using System.Runtime.CompilerServices; +using Godot.Collections; + +// ReSharper disable InconsistentNaming + +#nullable enable + +namespace Godot.NativeInterop +{ + public static class VariantUtils + { + public static godot_variant CreateFromRID(RID from) + => new() { Type = Variant.Type.Rid, RID = from }; + + public static godot_variant CreateFromBool(bool from) + => new() { Type = Variant.Type.Bool, Bool = from.ToGodotBool() }; + + public static godot_variant CreateFromInt(long from) + => new() { Type = Variant.Type.Int, Int = from }; + + public static godot_variant CreateFromInt(ulong from) + => new() { Type = Variant.Type.Int, Int = (long)from }; + + public static godot_variant CreateFromFloat(double from) + => new() { Type = Variant.Type.Float, Float = from }; + + public static godot_variant CreateFromVector2(Vector2 from) + => new() { Type = Variant.Type.Vector2, Vector2 = from }; + + public static godot_variant CreateFromVector2i(Vector2i from) + => new() { Type = Variant.Type.Vector2i, Vector2i = from }; + + public static godot_variant CreateFromVector3(Vector3 from) + => new() { Type = Variant.Type.Vector3, Vector3 = from }; + + public static godot_variant CreateFromVector3i(Vector3i from) + => new() { Type = Variant.Type.Vector3i, Vector3i = from }; + + public static godot_variant CreateFromRect2(Rect2 from) + => new() { Type = Variant.Type.Rect2, Rect2 = from }; + + public static godot_variant CreateFromRect2i(Rect2i from) + => new() { Type = Variant.Type.Rect2i, Rect2i = from }; + + public static godot_variant CreateFromQuaternion(Quaternion from) + => new() { Type = Variant.Type.Quaternion, Quaternion = from }; + + public static godot_variant CreateFromColor(Color from) + => new() { Type = Variant.Type.Color, Color = from }; + + public static godot_variant CreateFromPlane(Plane from) + => new() { Type = Variant.Type.Plane, Plane = from }; + + public static godot_variant CreateFromTransform2D(Transform2D from) + { + NativeFuncs.godotsharp_variant_new_transform2d(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromVector4(Vector4 from) + { + NativeFuncs.godotsharp_variant_new_vector4(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromVector4i(Vector4i from) + { + NativeFuncs.godotsharp_variant_new_vector4i(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromBasis(Basis from) + { + NativeFuncs.godotsharp_variant_new_basis(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromTransform3D(Transform3D from) + { + NativeFuncs.godotsharp_variant_new_transform3d(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromProjection(Projection from) + { + NativeFuncs.godotsharp_variant_new_projection(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromAABB(AABB from) + { + NativeFuncs.godotsharp_variant_new_aabb(out godot_variant ret, from); + return ret; + } + + // Explicit name to make it very clear + public static godot_variant CreateFromCallableTakingOwnershipOfDisposableValue(godot_callable from) + => new() { Type = Variant.Type.Callable, Callable = from }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromCallable(Callable from) + => CreateFromCallableTakingOwnershipOfDisposableValue( + Marshaling.ConvertCallableToNative(from)); + + // Explicit name to make it very clear + public static godot_variant CreateFromSignalTakingOwnershipOfDisposableValue(godot_signal from) + => new() { Type = Variant.Type.Signal, Signal = from }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromSignalInfo(SignalInfo from) + => CreateFromSignalTakingOwnershipOfDisposableValue( + Marshaling.ConvertSignalToNative(from)); + + // Explicit name to make it very clear + public static godot_variant CreateFromStringTakingOwnershipOfDisposableValue(godot_string from) + => new() { Type = Variant.Type.String, String = from }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromString(string? from) + => CreateFromStringTakingOwnershipOfDisposableValue(Marshaling.ConvertStringToNative(from)); + + public static godot_variant CreateFromPackedByteArray(in godot_packed_byte_array from) + { + NativeFuncs.godotsharp_variant_new_packed_byte_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedInt32Array(in godot_packed_int32_array from) + { + NativeFuncs.godotsharp_variant_new_packed_int32_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedInt64Array(in godot_packed_int64_array from) + { + NativeFuncs.godotsharp_variant_new_packed_int64_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedFloat32Array(in godot_packed_float32_array from) + { + NativeFuncs.godotsharp_variant_new_packed_float32_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedFloat64Array(in godot_packed_float64_array from) + { + NativeFuncs.godotsharp_variant_new_packed_float64_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedStringArray(in godot_packed_string_array from) + { + NativeFuncs.godotsharp_variant_new_packed_string_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedVector2Array(in godot_packed_vector2_array from) + { + NativeFuncs.godotsharp_variant_new_packed_vector2_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedVector3Array(in godot_packed_vector3_array from) + { + NativeFuncs.godotsharp_variant_new_packed_vector3_array(out godot_variant ret, from); + return ret; + } + + public static godot_variant CreateFromPackedColorArray(in godot_packed_color_array from) + { + NativeFuncs.godotsharp_variant_new_packed_color_array(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedByteArray(Span<byte> from) + => CreateFromPackedByteArray(Marshaling.ConvertSystemArrayToNativePackedByteArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedInt32Array(Span<int> from) + => CreateFromPackedInt32Array(Marshaling.ConvertSystemArrayToNativePackedInt32Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedInt64Array(Span<long> from) + => CreateFromPackedInt64Array(Marshaling.ConvertSystemArrayToNativePackedInt64Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedFloat32Array(Span<float> from) + => CreateFromPackedFloat32Array(Marshaling.ConvertSystemArrayToNativePackedFloat32Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedFloat64Array(Span<double> from) + => CreateFromPackedFloat64Array(Marshaling.ConvertSystemArrayToNativePackedFloat64Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedStringArray(Span<string> from) + => CreateFromPackedStringArray(Marshaling.ConvertSystemArrayToNativePackedStringArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedVector2Array(Span<Vector2> from) + => CreateFromPackedVector2Array(Marshaling.ConvertSystemArrayToNativePackedVector2Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedVector3Array(Span<Vector3> from) + => CreateFromPackedVector3Array(Marshaling.ConvertSystemArrayToNativePackedVector3Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromPackedColorArray(Span<Color> from) + => CreateFromPackedColorArray(Marshaling.ConvertSystemArrayToNativePackedColorArray(from)); + + public static godot_variant CreateFromSystemArrayOfStringName(Span<StringName> from) + => CreateFromArray(new Collections.Array(from)); + + public static godot_variant CreateFromSystemArrayOfNodePath(Span<NodePath> from) + => CreateFromArray(new Collections.Array(from)); + + public static godot_variant CreateFromSystemArrayOfRID(Span<RID> from) + => CreateFromArray(new Collections.Array(from)); + + // ReSharper disable once RedundantNameQualifier + public static godot_variant CreateFromSystemArrayOfGodotObject(Godot.Object[]? from) + { + if (from == null) + return default; // Nil + using var fromGodot = new Collections.Array(from); + return CreateFromArray((godot_array)fromGodot.NativeValue); + } + + public static godot_variant CreateFromArray(godot_array from) + { + NativeFuncs.godotsharp_variant_new_array(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromArray(Collections.Array? from) + => from != null ? CreateFromArray((godot_array)from.NativeValue) : default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromArray<T>(Array<T>? from) + => from != null ? CreateFromArray((godot_array)((Collections.Array)from).NativeValue) : default; + + public static godot_variant CreateFromDictionary(godot_dictionary from) + { + NativeFuncs.godotsharp_variant_new_dictionary(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromDictionary(Dictionary? from) + => from != null ? CreateFromDictionary((godot_dictionary)from.NativeValue) : default; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromDictionary<TKey, TValue>(Dictionary<TKey, TValue>? from) + => from != null ? CreateFromDictionary((godot_dictionary)((Dictionary)from).NativeValue) : default; + + public static godot_variant CreateFromStringName(godot_string_name from) + { + NativeFuncs.godotsharp_variant_new_string_name(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromStringName(StringName? from) + => from != null ? CreateFromStringName((godot_string_name)from.NativeValue) : default; + + public static godot_variant CreateFromNodePath(godot_node_path from) + { + NativeFuncs.godotsharp_variant_new_node_path(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_variant CreateFromNodePath(NodePath? from) + => from != null ? CreateFromNodePath((godot_node_path)from.NativeValue) : default; + + public static godot_variant CreateFromGodotObjectPtr(IntPtr from) + { + if (from == IntPtr.Zero) + return new godot_variant(); + NativeFuncs.godotsharp_variant_new_object(out godot_variant ret, from); + return ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once RedundantNameQualifier + public static godot_variant CreateFromGodotObject(Godot.Object? from) + => from != null ? CreateFromGodotObjectPtr(Object.GetPtr(from)) : default; + + // We avoid the internal call if the stored type is the same we want. + + public static bool ConvertToBool(in godot_variant p_var) + => p_var.Type == Variant.Type.Bool ? + p_var.Bool.ToBool() : + NativeFuncs.godotsharp_variant_as_bool(p_var).ToBool(); + + public static char ConvertToChar(in godot_variant p_var) + => (char)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static sbyte ConvertToInt8(in godot_variant p_var) + => (sbyte)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static short ConvertToInt16(in godot_variant p_var) + => (short)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static int ConvertToInt32(in godot_variant p_var) + => (int)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static long ConvertToInt64(in godot_variant p_var) + => p_var.Type == Variant.Type.Int ? p_var.Int : NativeFuncs.godotsharp_variant_as_int(p_var); + + public static byte ConvertToUInt8(in godot_variant p_var) + => (byte)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static ushort ConvertToUInt16(in godot_variant p_var) + => (ushort)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static uint ConvertToUInt32(in godot_variant p_var) + => (uint)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static ulong ConvertToUInt64(in godot_variant p_var) + => (ulong)(p_var.Type == Variant.Type.Int ? + p_var.Int : + NativeFuncs.godotsharp_variant_as_int(p_var)); + + public static float ConvertToFloat32(in godot_variant p_var) + => (float)(p_var.Type == Variant.Type.Float ? + p_var.Float : + NativeFuncs.godotsharp_variant_as_float(p_var)); + + public static double ConvertToFloat64(in godot_variant p_var) + => p_var.Type == Variant.Type.Float ? + p_var.Float : + NativeFuncs.godotsharp_variant_as_float(p_var); + + public static Vector2 ConvertToVector2(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector2 ? + p_var.Vector2 : + NativeFuncs.godotsharp_variant_as_vector2(p_var); + + public static Vector2i ConvertToVector2i(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector2i ? + p_var.Vector2i : + NativeFuncs.godotsharp_variant_as_vector2i(p_var); + + public static Rect2 ConvertToRect2(in godot_variant p_var) + => p_var.Type == Variant.Type.Rect2 ? + p_var.Rect2 : + NativeFuncs.godotsharp_variant_as_rect2(p_var); + + public static Rect2i ConvertToRect2i(in godot_variant p_var) + => p_var.Type == Variant.Type.Rect2i ? + p_var.Rect2i : + NativeFuncs.godotsharp_variant_as_rect2i(p_var); + + public static unsafe Transform2D ConvertToTransform2D(in godot_variant p_var) + => p_var.Type == Variant.Type.Transform2d ? + *p_var.Transform2D : + NativeFuncs.godotsharp_variant_as_transform2d(p_var); + + public static Vector3 ConvertToVector3(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector3 ? + p_var.Vector3 : + NativeFuncs.godotsharp_variant_as_vector3(p_var); + + public static Vector3i ConvertToVector3i(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector3i ? + p_var.Vector3i : + NativeFuncs.godotsharp_variant_as_vector3i(p_var); + + public static unsafe Vector4 ConvertToVector4(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector4 ? + *p_var.Vector4 : + NativeFuncs.godotsharp_variant_as_vector4(p_var); + + public static unsafe Vector4i ConvertToVector4i(in godot_variant p_var) + => p_var.Type == Variant.Type.Vector4i ? + *p_var.Vector4i : + NativeFuncs.godotsharp_variant_as_vector4i(p_var); + + public static unsafe Basis ConvertToBasis(in godot_variant p_var) + => p_var.Type == Variant.Type.Basis ? + *p_var.Basis : + NativeFuncs.godotsharp_variant_as_basis(p_var); + + public static Quaternion ConvertToQuaternion(in godot_variant p_var) + => p_var.Type == Variant.Type.Quaternion ? + p_var.Quaternion : + NativeFuncs.godotsharp_variant_as_quaternion(p_var); + + public static unsafe Transform3D ConvertToTransform3D(in godot_variant p_var) + => p_var.Type == Variant.Type.Transform3d ? + *p_var.Transform3D : + NativeFuncs.godotsharp_variant_as_transform3d(p_var); + + public static unsafe Projection ConvertToProjection(in godot_variant p_var) + => p_var.Type == Variant.Type.Projection ? + *p_var.Projection : + NativeFuncs.godotsharp_variant_as_projection(p_var); + + public static unsafe AABB ConvertToAABB(in godot_variant p_var) + => p_var.Type == Variant.Type.Aabb ? + *p_var.AABB : + NativeFuncs.godotsharp_variant_as_aabb(p_var); + + public static Color ConvertToColor(in godot_variant p_var) + => p_var.Type == Variant.Type.Color ? + p_var.Color : + NativeFuncs.godotsharp_variant_as_color(p_var); + + public static Plane ConvertToPlane(in godot_variant p_var) + => p_var.Type == Variant.Type.Plane ? + p_var.Plane : + NativeFuncs.godotsharp_variant_as_plane(p_var); + + public static RID ConvertToRID(in godot_variant p_var) + => p_var.Type == Variant.Type.Rid ? + p_var.RID : + NativeFuncs.godotsharp_variant_as_rid(p_var); + + public static IntPtr ConvertToGodotObjectPtr(in godot_variant p_var) + => p_var.Type == Variant.Type.Object ? p_var.Object : IntPtr.Zero; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // ReSharper disable once RedundantNameQualifier + public static Godot.Object ConvertToGodotObject(in godot_variant p_var) + => InteropUtils.UnmanagedGetManaged(ConvertToGodotObjectPtr(p_var)); + + public static string ConvertToStringObject(in godot_variant p_var) + { + switch (p_var.Type) + { + case Variant.Type.Nil: + return ""; // Otherwise, Variant -> String would return the string "Null" + case Variant.Type.String: + { + // We avoid the internal call if the stored type is the same we want. + return Marshaling.ConvertStringToManaged(p_var.String); + } + default: + { + using godot_string godotString = NativeFuncs.godotsharp_variant_as_string(p_var); + return Marshaling.ConvertStringToManaged(godotString); + } + } + } + + public static godot_string_name ConvertToStringName(in godot_variant p_var) + => p_var.Type == Variant.Type.StringName ? + NativeFuncs.godotsharp_string_name_new_copy(p_var.StringName) : + NativeFuncs.godotsharp_variant_as_string_name(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static StringName ConvertToStringNameObject(in godot_variant p_var) + => StringName.CreateTakingOwnershipOfDisposableValue(ConvertToStringName(p_var)); + + public static godot_node_path ConvertToNodePath(in godot_variant p_var) + => p_var.Type == Variant.Type.NodePath ? + NativeFuncs.godotsharp_node_path_new_copy(p_var.NodePath) : + NativeFuncs.godotsharp_variant_as_node_path(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static NodePath ConvertToNodePathObject(in godot_variant p_var) + => NodePath.CreateTakingOwnershipOfDisposableValue(ConvertToNodePath(p_var)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_callable ConvertToCallable(in godot_variant p_var) + => NativeFuncs.godotsharp_variant_as_callable(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Callable ConvertToCallableManaged(in godot_variant p_var) + => Marshaling.ConvertCallableToManaged(ConvertToCallable(p_var)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static godot_signal ConvertToSignal(in godot_variant p_var) + => NativeFuncs.godotsharp_variant_as_signal(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SignalInfo ConvertToSignalInfo(in godot_variant p_var) + => Marshaling.ConvertSignalToManaged(ConvertToSignal(p_var)); + + public static godot_array ConvertToArray(in godot_variant p_var) + => p_var.Type == Variant.Type.Array ? + NativeFuncs.godotsharp_array_new_copy(p_var.Array) : + NativeFuncs.godotsharp_variant_as_array(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Collections.Array ConvertToArrayObject(in godot_variant p_var) + => Collections.Array.CreateTakingOwnershipOfDisposableValue(ConvertToArray(p_var)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Array<T> ConvertToArrayObject<T>(in godot_variant p_var) + => Array<T>.CreateTakingOwnershipOfDisposableValue(ConvertToArray(p_var)); + + public static godot_dictionary ConvertToDictionary(in godot_variant p_var) + => p_var.Type == Variant.Type.Dictionary ? + NativeFuncs.godotsharp_dictionary_new_copy(p_var.Dictionary) : + NativeFuncs.godotsharp_variant_as_dictionary(p_var); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary ConvertToDictionaryObject(in godot_variant p_var) + => Dictionary.CreateTakingOwnershipOfDisposableValue(ConvertToDictionary(p_var)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Dictionary<TKey, TValue> ConvertToDictionaryObject<TKey, TValue>(in godot_variant p_var) + => Dictionary<TKey, TValue>.CreateTakingOwnershipOfDisposableValue(ConvertToDictionary(p_var)); + + public static byte[] ConvertAsPackedByteArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_byte_array(p_var); + return Marshaling.ConvertNativePackedByteArrayToSystemArray(packedArray); + } + + public static int[] ConvertAsPackedInt32ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_int32_array(p_var); + return Marshaling.ConvertNativePackedInt32ArrayToSystemArray(packedArray); + } + + public static long[] ConvertAsPackedInt64ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_int64_array(p_var); + return Marshaling.ConvertNativePackedInt64ArrayToSystemArray(packedArray); + } + + public static float[] ConvertAsPackedFloat32ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_float32_array(p_var); + return Marshaling.ConvertNativePackedFloat32ArrayToSystemArray(packedArray); + } + + public static double[] ConvertAsPackedFloat64ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_float64_array(p_var); + return Marshaling.ConvertNativePackedFloat64ArrayToSystemArray(packedArray); + } + + public static string[] ConvertAsPackedStringArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_string_array(p_var); + return Marshaling.ConvertNativePackedStringArrayToSystemArray(packedArray); + } + + public static Vector2[] ConvertAsPackedVector2ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_vector2_array(p_var); + return Marshaling.ConvertNativePackedVector2ArrayToSystemArray(packedArray); + } + + public static Vector3[] ConvertAsPackedVector3ArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_vector3_array(p_var); + return Marshaling.ConvertNativePackedVector3ArrayToSystemArray(packedArray); + } + + public static Color[] ConvertAsPackedColorArrayToSystemArray(in godot_variant p_var) + { + using var packedArray = NativeFuncs.godotsharp_variant_as_packed_color_array(p_var); + return Marshaling.ConvertNativePackedColorArrayToSystemArray(packedArray); + } + + public static StringName[] ConvertToSystemArrayOfStringName(in godot_variant p_var) + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfStringName(godotArray); + } + + public static NodePath[] ConvertToSystemArrayOfNodePath(in godot_variant p_var) + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfNodePath(godotArray); + } + + public static RID[] ConvertToSystemArrayOfRID(in godot_variant p_var) + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfRID(godotArray); + } + + public static T[] ConvertToSystemArrayOfGodotObject<T>(in godot_variant p_var) + // ReSharper disable once RedundantNameQualifier + where T : Godot.Object + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfGodotObjectType<T>(godotArray); + } + + // ReSharper disable once RedundantNameQualifier + public static Godot.Object[] ConvertToSystemArrayOfGodotObject(in godot_variant p_var, Type type) + { + using var godotArray = NativeFuncs.godotsharp_variant_as_array(p_var); + return Marshaling.ConvertNativeGodotArrayToSystemArrayOfGodotObjectType(godotArray, type); + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs index 9ae01016cb..b02bd167a1 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NodePath.cs @@ -1,5 +1,5 @@ using System; -using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot { @@ -39,22 +39,11 @@ namespace Godot /// new NodePath("/root/MyAutoload"); // If you have an autoloaded node or scene. /// </code> /// </example> - public sealed partial class NodePath : IDisposable + public sealed class NodePath : IDisposable { - private bool _disposed = false; + internal godot_node_path.movable NativeValue; - private IntPtr ptr; - - internal static IntPtr GetPtr(NodePath instance) - { - if (instance == null) - throw new NullReferenceException($"The instance of type {nameof(NodePath)} is null."); - - if (instance._disposed) - throw new ObjectDisposedException(instance.GetType().FullName); - - return instance.ptr; - } + private WeakReference<IDisposable> _weakReferenceToSelf; ~NodePath() { @@ -70,29 +59,33 @@ namespace Godot GC.SuppressFinalize(this); } - private void Dispose(bool disposing) + public void Dispose(bool disposing) { - if (_disposed) - return; + // Always dispose `NativeValue` even if disposing is true + NativeValue.DangerousSelfRef.Dispose(); - if (ptr != IntPtr.Zero) + if (_weakReferenceToSelf != null) { - godot_icall_NodePath_Dtor(ptr); - ptr = IntPtr.Zero; + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); } - - _disposed = true; } - internal NodePath(IntPtr ptr) + private NodePath(godot_node_path nativeValueToOwn) { - this.ptr = ptr; + NativeValue = (godot_node_path.movable)nativeValueToOwn; + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } + // Explicit name to make it very clear + internal static NodePath CreateTakingOwnershipOfDisposableValue(godot_node_path nativeValueToOwn) + => new NodePath(nativeValueToOwn); + /// <summary> /// Constructs an empty <see cref="NodePath"/>. /// </summary> - public NodePath() : this(string.Empty) { } + public NodePath() + { + } /// <summary> /// Constructs a <see cref="NodePath"/> from a string <paramref name="path"/>, @@ -125,7 +118,11 @@ namespace Godot /// <param name="path">A string that represents a path in a scene tree.</param> public NodePath(string path) { - ptr = godot_icall_NodePath_Ctor(path); + if (!string.IsNullOrEmpty(path)) + { + NativeValue = (godot_node_path.movable)NativeFuncs.godotsharp_node_path_new_from_string(path); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } } /// <summary> @@ -138,7 +135,7 @@ namespace Godot /// Converts this <see cref="NodePath"/> to a string. /// </summary> /// <param name="from">The <see cref="NodePath"/> to convert.</param> - public static implicit operator string(NodePath from) => from.ToString(); + public static implicit operator string(NodePath from) => from?.ToString(); /// <summary> /// Converts this <see cref="NodePath"/> to a string. @@ -146,7 +143,13 @@ namespace Godot /// <returns>A string representation of this <see cref="NodePath"/>.</returns> public override string ToString() { - return godot_icall_NodePath_operator_String(GetPtr(this)); + if (IsEmpty) + return string.Empty; + + var src = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_as_string(out godot_string dest, src); + using (dest) + return Marshaling.ConvertStringToManaged(dest); } /// <summary> @@ -166,7 +169,10 @@ namespace Godot /// <returns>The <see cref="NodePath"/> as a pure property path.</returns> public NodePath GetAsPropertyPath() { - return new NodePath(godot_icall_NodePath_get_as_property_path(GetPtr(this))); + godot_node_path propertyPath = default; + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_as_property_path(self, ref propertyPath); + return CreateTakingOwnershipOfDisposableValue(propertyPath); } /// <summary> @@ -181,7 +187,10 @@ namespace Godot /// <returns>The names concatenated with <c>/</c>.</returns> public string GetConcatenatedNames() { - return godot_icall_NodePath_get_concatenated_names(GetPtr(this)); + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_concatenated_names(self, out godot_string names); + using (names) + return Marshaling.ConvertStringToManaged(names); } /// <summary> @@ -195,9 +204,12 @@ namespace Godot /// </code> /// </example> /// <returns>The subnames concatenated with <c>:</c>.</returns> - public string GetConcatenatedSubnames() + public string GetConcatenatedSubNames() { - return godot_icall_NodePath_get_concatenated_subnames(GetPtr(this)); + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_concatenated_subnames(self, out godot_string subNames); + using (subNames) + return Marshaling.ConvertStringToManaged(subNames); } /// <summary> @@ -215,28 +227,35 @@ namespace Godot /// <returns>The name at the given index <paramref name="idx"/>.</returns> public string GetName(int idx) { - return godot_icall_NodePath_get_name(GetPtr(this), idx); + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_name(self, idx, out godot_string name); + using (name) + return Marshaling.ConvertStringToManaged(name); } /// <summary> /// Gets the number of node names which make up the path. - /// Subnames (see <see cref="GetSubnameCount"/>) are not included. + /// Subnames (see <see cref="GetSubNameCount"/>) are not included. /// For example, <c>"Path2D/PathFollow2D/Sprite2D"</c> has 3 names. /// </summary> /// <returns>The number of node names which make up the path.</returns> public int GetNameCount() { - return godot_icall_NodePath_get_name_count(GetPtr(this)); + var self = (godot_node_path)NativeValue; + return NativeFuncs.godotsharp_node_path_get_name_count(self); } /// <summary> - /// Gets the resource or property name indicated by <paramref name="idx"/> (0 to <see cref="GetSubnameCount"/>). + /// Gets the resource or property name indicated by <paramref name="idx"/> (0 to <see cref="GetSubNameCount"/>). /// </summary> /// <param name="idx">The subname index.</param> /// <returns>The subname at the given index <paramref name="idx"/>.</returns> - public string GetSubname(int idx) + public string GetSubName(int idx) { - return godot_icall_NodePath_get_subname(GetPtr(this), idx); + var self = (godot_node_path)NativeValue; + NativeFuncs.godotsharp_node_path_get_subname(self, idx, out godot_string subName); + using (subName) + return Marshaling.ConvertStringToManaged(subName); } /// <summary> @@ -245,9 +264,10 @@ namespace Godot /// For example, <c>"Path2D/PathFollow2D/Sprite2D:texture:load_path"</c> has 2 subnames. /// </summary> /// <returns>The number of subnames in the path.</returns> - public int GetSubnameCount() + public int GetSubNameCount() { - return godot_icall_NodePath_get_subname_count(GetPtr(this)); + var self = (godot_node_path)NativeValue; + return NativeFuncs.godotsharp_node_path_get_subname_count(self); } /// <summary> @@ -259,52 +279,14 @@ namespace Godot /// <returns>If the <see cref="NodePath"/> is an absolute path.</returns> public bool IsAbsolute() { - return godot_icall_NodePath_is_absolute(GetPtr(this)); + var self = (godot_node_path)NativeValue; + return NativeFuncs.godotsharp_node_path_is_absolute(self).ToBool(); } /// <summary> /// Returns <see langword="true"/> if the node path is empty. /// </summary> /// <returns>If the <see cref="NodePath"/> is empty.</returns> - public bool IsEmpty() - { - return godot_icall_NodePath_is_empty(GetPtr(this)); - } - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern IntPtr godot_icall_NodePath_Ctor(string path); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void godot_icall_NodePath_Dtor(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_operator_String(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern IntPtr godot_icall_NodePath_get_as_property_path(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_get_concatenated_names(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_get_concatenated_subnames(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_get_name(IntPtr ptr, int arg1); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern int godot_icall_NodePath_get_name_count(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_NodePath_get_subname(IntPtr ptr, int arg1); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern int godot_icall_NodePath_get_subname_count(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool godot_icall_NodePath_is_absolute(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool godot_icall_NodePath_is_empty(IntPtr ptr); + public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index 746612477d..5cb678c280 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -1,54 +1,78 @@ using System; -using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.Bridge; +using Godot.NativeInterop; namespace Godot { public partial class Object : IDisposable { private bool _disposed = false; + private static readonly Type CachedType = typeof(Object); - private static StringName nativeName = "Object"; + internal IntPtr NativePtr; + private bool _memoryOwn; - internal IntPtr ptr; - internal bool memoryOwn; + private WeakReference<Object> _weakReferenceToSelf; /// <summary> /// Constructs a new <see cref="Object"/>. /// </summary> public Object() : this(false) { - if (ptr == IntPtr.Zero) - ptr = godot_icall_Object_Ctor(this); - _InitializeGodotScriptInstanceInternals(); + unsafe + { + _ConstructAndInitialize(NativeCtor, NativeName, CachedType, refCounted: false); + } } - internal void _InitializeGodotScriptInstanceInternals() + internal unsafe void _ConstructAndInitialize( + delegate* unmanaged<IntPtr> nativeCtor, + StringName nativeName, + Type cachedType, + bool refCounted + ) { - godot_icall_Object_ConnectEventSignals(ptr); + if (NativePtr == IntPtr.Zero) + { + NativePtr = nativeCtor(); + + InteropUtils.TieManagedToUnmanaged(this, NativePtr, + nativeName, refCounted, GetType(), cachedType); + } + else + { + InteropUtils.TieManagedToUnmanagedWithPreSetup(this, NativePtr, + GetType(), cachedType); + } + + _weakReferenceToSelf = DisposablesTracker.RegisterGodotObject(this); } internal Object(bool memoryOwn) { - this.memoryOwn = memoryOwn; + _memoryOwn = memoryOwn; } /// <summary> /// The pointer to the native instance of this <see cref="Object"/>. /// </summary> - public IntPtr NativeInstance - { - get { return ptr; } - } + public IntPtr NativeInstance => NativePtr; internal static IntPtr GetPtr(Object instance) { if (instance == null) return IntPtr.Zero; - if (instance._disposed) + // We check if NativePtr is null because this may be called by the debugger. + // If the debugger puts a breakpoint in one of the base constructors, before + // NativePtr is assigned, that would result in UB or crashes when calling + // native functions that receive the pointer, which can happen because the + // debugger calls ToString() and tries to get the value of properties. + if (instance._disposed || instance.NativePtr == IntPtr.Zero) throw new ObjectDisposedException(instance.GetType().FullName); - return instance.ptr; + return instance.NativePtr; } ~Object() @@ -73,22 +97,35 @@ namespace Godot if (_disposed) return; - if (ptr != IntPtr.Zero) + _disposed = true; + + if (NativePtr != IntPtr.Zero) { - if (memoryOwn) + IntPtr gcHandleToFree = NativeFuncs.godotsharp_internal_object_get_associated_gchandle(NativePtr); + + if (gcHandleToFree != IntPtr.Zero) + { + object target = GCHandle.FromIntPtr(gcHandleToFree).Target; + // The GC handle may have been replaced in another thread. Release it only if + // it's associated to this managed instance, or if the target is no longer alive. + if (target != this && target != null) + gcHandleToFree = IntPtr.Zero; + } + + if (_memoryOwn) { - memoryOwn = false; - godot_icall_RefCounted_Disposed(this, ptr, !disposing); + NativeFuncs.godotsharp_internal_refcounted_disposed(NativePtr, gcHandleToFree, + (!disposing).ToGodotBool()); } else { - godot_icall_Object_Disposed(this, ptr); + NativeFuncs.godotsharp_internal_object_disposed(NativePtr, gcHandleToFree); } - ptr = IntPtr.Zero; + NativePtr = IntPtr.Zero; } - _disposed = true; + DisposablesTracker.UnregisterGodotObject(this, _weakReferenceToSelf); } /// <summary> @@ -97,7 +134,9 @@ namespace Godot /// <returns>A string representation of this object.</returns> public override string ToString() { - return godot_icall_Object_ToString(GetPtr(this)); + NativeFuncs.godotsharp_object_to_string(GetPtr(this), out godot_string str); + using (str) + return Marshaling.ConvertStringToManaged(str); } /// <summary> @@ -132,33 +171,72 @@ namespace Godot return new SignalAwaiter(source, signal, this); } - /// <summary> - /// Gets a new <see cref="DynamicGodotObject"/> associated with this instance. - /// </summary> - public dynamic DynamicObject => new DynamicGodotObject(this); + internal static Type InternalGetClassNativeBase(Type t) + { + do + { + var assemblyName = t.Assembly.GetName(); - internal static IntPtr __ClassDB_get_method(StringName type, string method) + if (assemblyName.Name == "GodotSharp") + return t; + + if (assemblyName.Name == "GodotSharpEditor") + return t; + } while ((t = t.BaseType) != null); + + return null; + } + + // ReSharper disable once VirtualMemberNeverOverridden.Global + protected internal virtual bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) + { + return false; + } + + // ReSharper disable once VirtualMemberNeverOverridden.Global + protected internal virtual bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) + { + value = default; + return false; + } + + // ReSharper disable once VirtualMemberNeverOverridden.Global + protected internal virtual void RaiseGodotClassSignalCallbacks(in godot_string_name signal, + NativeVariantPtrArgs args, int argCount) { - return godot_icall_Object_ClassDB_get_method(StringName.GetPtr(type), method); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Object_Ctor(Object obj); + internal static IntPtr ClassDB_get_method(StringName type, StringName method) + { + var typeSelf = (godot_string_name)type.NativeValue; + var methodSelf = (godot_string_name)method.NativeValue; + IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method(typeSelf, methodSelf); + + if (methodBind == IntPtr.Zero) + throw new NativeMethodBindNotFoundException(type + "." + method); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Object_Disposed(Object obj, IntPtr ptr); + return methodBind; + } + + internal static unsafe delegate* unmanaged<IntPtr> ClassDB_get_constructor(StringName type) + { + // for some reason the '??' operator doesn't support 'delegate*' + var typeSelf = (godot_string_name)type.NativeValue; + var nativeConstructor = NativeFuncs.godotsharp_get_class_constructor(typeSelf); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_RefCounted_Disposed(Object obj, IntPtr ptr, bool isFinalizer); + if (nativeConstructor == null) + throw new NativeConstructorNotFoundException(type); - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_Object_ConnectEventSignals(IntPtr obj); + return nativeConstructor; + } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_Object_ToString(IntPtr ptr); + protected internal virtual void SaveGodotObjectData(GodotSerializationInfo info) + { + } - // Used by the generated API - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_Object_ClassDB_get_method(IntPtr type, string method); + // TODO: Should this be a constructor overload? + protected internal virtual void RestoreGodotObjectData(GodotSerializationInfo info) + { + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.exceptions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.exceptions.cs new file mode 100644 index 0000000000..eb2811c73d --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.exceptions.cs @@ -0,0 +1,135 @@ +using System; + +#nullable enable + +namespace Godot +{ + public partial class Object + { + public class NativeMemberNotFoundException : Exception + { + public NativeMemberNotFoundException() + { + } + + public NativeMemberNotFoundException(string? message) : base(message) + { + } + + public NativeMemberNotFoundException(string? message, Exception? innerException) + : base(message, innerException) + { + } + } + + public class NativeConstructorNotFoundException : NativeMemberNotFoundException + { + private readonly string? _nativeClassName; + + // ReSharper disable once InconsistentNaming + private const string Arg_NativeConstructorNotFoundException = "Unable to find the native constructor."; + + public NativeConstructorNotFoundException() + : base(Arg_NativeConstructorNotFoundException) + { + } + + public NativeConstructorNotFoundException(string? nativeClassName) + : this(Arg_NativeConstructorNotFoundException, nativeClassName) + { + } + + public NativeConstructorNotFoundException(string? message, Exception? innerException) + : base(message, innerException) + { + } + + public NativeConstructorNotFoundException(string? message, string? nativeClassName) + : base(message) + { + _nativeClassName = nativeClassName; + } + + public NativeConstructorNotFoundException(string? message, string? nativeClassName, Exception? innerException) + : base(message, innerException) + { + _nativeClassName = nativeClassName; + } + + public override string Message + { + get + { + string s = base.Message; + + if (string.IsNullOrEmpty(s)) + { + s = Arg_NativeConstructorNotFoundException; + } + + if (!string.IsNullOrEmpty(_nativeClassName)) + { + s += " " + string.Format("(Class '{0}')", _nativeClassName); + } + + return s; + } + } + } + + public class NativeMethodBindNotFoundException : NativeMemberNotFoundException + { + private readonly string? _nativeMethodName; + + // ReSharper disable once InconsistentNaming + private const string Arg_NativeMethodBindNotFoundException = "Unable to find the native method bind."; + + public NativeMethodBindNotFoundException() + : base(Arg_NativeMethodBindNotFoundException) + { + } + + public NativeMethodBindNotFoundException(string? nativeMethodName) + : this(Arg_NativeMethodBindNotFoundException, nativeMethodName) + { + } + + public NativeMethodBindNotFoundException(string? message, Exception? innerException) + : base(message, innerException) + { + } + + public NativeMethodBindNotFoundException(string? message, string? nativeMethodName) + : base(message) + { + _nativeMethodName = nativeMethodName; + } + + public NativeMethodBindNotFoundException(string? message, string? nativeMethodName, Exception? innerException) + : base(message, innerException) + { + _nativeMethodName = nativeMethodName; + } + + public override string Message + { + get + { + string s = base.Message; + + if (string.IsNullOrEmpty(s)) + { + s = Arg_NativeMethodBindNotFoundException; + } + + if (!string.IsNullOrEmpty(_nativeMethodName)) + { + s += " " + string.Format("(Method '{0}')", _nativeMethodName); + } + + return s; + } + } + } + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs index fd97a71e47..50832d7679 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Plane.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs index d774021131..df16fe5718 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Projection.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs index e38dca414f..90e4e3b41e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -73,7 +68,7 @@ namespace Godot case 3: return w; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -93,7 +88,7 @@ namespace Godot w = value; break; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs index 1588869ec0..a31fef8360 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/RID.cs @@ -1,5 +1,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { @@ -9,99 +11,32 @@ namespace Godot /// resource by themselves. They are used by and with the low-level Server /// classes such as <see cref="RenderingServer"/>. /// </summary> - public sealed partial class RID : IDisposable + [StructLayout(LayoutKind.Sequential)] + public struct RID { - private bool _disposed = false; + private ulong _id; // Default is 0 - internal IntPtr ptr; - - internal static IntPtr GetPtr(RID instance) - { - if (instance == null) - throw new NullReferenceException($"The instance of type {nameof(RID)} is null."); - - if (instance._disposed) - throw new ObjectDisposedException(instance.GetType().FullName); - - return instance.ptr; - } - - ~RID() - { - Dispose(false); - } - - /// <summary> - /// Disposes of this <see cref="RID"/>. - /// </summary> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (_disposed) - return; - - if (ptr != IntPtr.Zero) - { - godot_icall_RID_Dtor(ptr); - ptr = IntPtr.Zero; - } - - _disposed = true; - } - - internal RID(IntPtr ptr) + internal RID(ulong id) { - this.ptr = ptr; - } - - /// <summary> - /// The pointer to the native instance of this <see cref="RID"/>. - /// </summary> - public IntPtr NativeInstance - { - get { return ptr; } - } - - internal RID() - { - this.ptr = IntPtr.Zero; + _id = id; } /// <summary> /// Constructs a new <see cref="RID"/> for the given <see cref="Object"/> <paramref name="from"/>. /// </summary> public RID(Object from) - { - this.ptr = godot_icall_RID_Ctor(Object.GetPtr(from)); - } + => _id = from is Resource res ? res.GetRid()._id : default; /// <summary> /// Returns the ID of the referenced resource. /// </summary> /// <returns>The ID of the referenced resource.</returns> - public int GetId() - { - return godot_icall_RID_get_id(GetPtr(this)); - } + public ulong Id => _id; /// <summary> /// Converts this <see cref="RID"/> to a string. /// </summary> /// <returns>A string representation of this RID.</returns> - public override string ToString() => "[RID]"; - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern IntPtr godot_icall_RID_Ctor(IntPtr from); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern void godot_icall_RID_Dtor(IntPtr ptr); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_RID_get_id(IntPtr ptr); + public override string ToString() => $"RID({Id})"; } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs index ec16920fed..d2c9b0ca8b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs new file mode 100644 index 0000000000..ee605f8d8f --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/ReflectionUtils.cs @@ -0,0 +1,16 @@ +using System; +using System.Linq; + +#nullable enable + +namespace Godot; + +internal class ReflectionUtils +{ + public static Type? FindTypeInLoadedAssemblies(string assemblyName, string typeFullName) + { + return AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == assemblyName)? + .GetType(typeFullName); + } +} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs index 2ba0493002..96fb891086 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalAwaiter.cs @@ -1,50 +1,67 @@ using System; -using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Godot.NativeInterop; namespace Godot { - public class SignalAwaiter : IAwaiter<object[]>, IAwaitable<object[]> + public class SignalAwaiter : IAwaiter<Variant[]>, IAwaitable<Variant[]> { private bool _completed; - private object[] _result; - private Action _action; + private Variant[] _result; + private Action _continuation; public SignalAwaiter(Object source, StringName signal, Object target) { - godot_icall_SignalAwaiter_connect(Object.GetPtr(source), StringName.GetPtr(signal), Object.GetPtr(target), this); + var awaiterGcHandle = CustomGCHandle.AllocStrong(this); + using godot_string_name signalSrc = NativeFuncs.godotsharp_string_name_new_copy( + (godot_string_name)(signal?.NativeValue ?? default)); + NativeFuncs.godotsharp_internal_signal_awaiter_connect(Object.GetPtr(source), in signalSrc, + Object.GetPtr(target), GCHandle.ToIntPtr(awaiterGcHandle)); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Error godot_icall_SignalAwaiter_connect(IntPtr source, IntPtr signal, IntPtr target, SignalAwaiter awaiter); + public bool IsCompleted => _completed; - public bool IsCompleted + public void OnCompleted(Action continuation) { - get - { - return _completed; - } + _continuation = continuation; } - public void OnCompleted(Action action) - { - this._action = action; - } + public Variant[] GetResult() => _result; - public object[] GetResult() - { - return _result; - } + public IAwaiter<Variant[]> GetAwaiter() => this; - public IAwaiter<object[]> GetAwaiter() + [UnmanagedCallersOnly] + internal static unsafe void SignalCallback(IntPtr awaiterGCHandlePtr, godot_variant** args, int argCount, + godot_bool* outAwaiterIsNull) { - return this; - } + try + { + var awaiter = (SignalAwaiter)GCHandle.FromIntPtr(awaiterGCHandlePtr).Target; - internal void SignalCallback(object[] args) - { - _completed = true; - _result = args; - _action?.Invoke(); + if (awaiter == null) + { + *outAwaiterIsNull = godot_bool.True; + return; + } + + *outAwaiterIsNull = godot_bool.False; + + awaiter._completed = true; + + Variant[] signalArgs = new Variant[argCount]; + + for (int i = 0; i < argCount; i++) + signalArgs[i] = Variant.CreateCopyingBorrowed(*args[i]); + + awaiter._result = signalArgs; + + awaiter._continuation?.Invoke(); + } + catch (Exception e) + { + ExceptionUtils.LogException(e); + *outAwaiterIsNull = godot_bool.False; + } } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs index da01300586..3f50df0a0d 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/SignalInfo.cs @@ -3,7 +3,7 @@ namespace Godot /// <summary> /// Represents a signal defined in an object. /// </summary> - public struct SignalInfo + public readonly struct SignalInfo { private readonly Object _owner; private readonly StringName _signalName; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index a1f058ffe5..f0bc5949df 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Text.RegularExpressions; +using Godot.NativeInterop; + +#nullable enable namespace Godot { @@ -177,6 +179,7 @@ namespace Godot { return 0; } + if (from == 0 && to == len) { str = instance; @@ -214,7 +217,7 @@ namespace Godot /// <returns>The escaped string.</returns> public static string CEscape(this string instance) { - var sb = new StringBuilder(string.Copy(instance)); + var sb = new StringBuilder(instance); sb.Replace("\\", "\\\\"); sb.Replace("\a", "\\a"); @@ -239,7 +242,7 @@ namespace Godot /// <returns>The unescaped string.</returns> public static string CUnescape(this string instance) { - var sb = new StringBuilder(string.Copy(instance)); + var sb = new StringBuilder(instance); sb.Replace("\\a", "\a"); sb.Replace("\\b", "\b"); @@ -471,7 +474,8 @@ namespace Godot /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int Find(this string instance, string what, int from = 0, bool caseSensitive = true) { - return instance.IndexOf(what, from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return instance.IndexOf(what, from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -490,7 +494,8 @@ namespace Godot { // TODO: Could be more efficient if we get a char version of `IndexOf`. // See https://github.com/dotnet/runtime/issues/44116 - return instance.IndexOf(what.ToString(), from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return instance.IndexOf(what.ToString(), from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// <summary>Find the last occurrence of a substring.</summary> @@ -519,7 +524,8 @@ namespace Godot /// <returns>The starting position of the substring, or -1 if not found.</returns> public static int FindLast(this string instance, string what, int from, bool caseSensitive = true) { - return instance.LastIndexOf(what, from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); + return instance.LastIndexOf(what, from, + caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -804,6 +810,7 @@ namespace Godot { match = instance[source] == text[target]; } + if (match) { source++; @@ -926,7 +933,7 @@ namespace Godot /// <returns>The escaped string.</returns> public static string JSONEscape(this string instance) { - var sb = new StringBuilder(string.Copy(instance)); + var sb = new StringBuilder(instance); sb.Replace("\\", "\\\\"); sb.Replace("\b", "\\b"); @@ -1015,15 +1022,18 @@ namespace Godot switch (expr[0]) { case '*': - return ExprMatch(instance, expr.Substring(1), caseSensitive) || (instance.Length > 0 && ExprMatch(instance.Substring(1), expr, caseSensitive)); + return ExprMatch(instance, expr.Substring(1), caseSensitive) || (instance.Length > 0 && + ExprMatch(instance.Substring(1), expr, caseSensitive)); case '?': - return instance.Length > 0 && instance[0] != '.' && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); + return instance.Length > 0 && instance[0] != '.' && + ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); default: if (instance.Length == 0) return false; if (caseSensitive) return instance[0] == expr[0]; - return (char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); + return (char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && + ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive); } } @@ -1070,12 +1080,12 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static byte[] MD5Buffer(this string instance) { - return godot_icall_String_md5_buffer(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_md5_buffer(instanceStr, out var md5Buffer); + using (md5Buffer) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(md5Buffer); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern byte[] godot_icall_String_md5_buffer(string str); - /// <summary> /// Returns the MD5 hash of the string as a string. /// </summary> @@ -1084,12 +1094,12 @@ namespace Godot /// <returns>The MD5 hash of the string.</returns> public static string MD5Text(this string instance) { - return godot_icall_String_md5_text(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_md5_text(instanceStr, out var md5Text); + using (md5Text) + return Marshaling.ConvertStringToManaged(md5Text); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_String_md5_text(string str); - /// <summary> /// Perform a case-insensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater. /// </summary> @@ -1244,12 +1254,11 @@ namespace Godot /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFind(this string instance, string what, int from = -1) { - return godot_icall_String_rfind(instance, what, from); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + using godot_string whatStr = Marshaling.ConvertStringToNative(instance); + return NativeFuncs.godotsharp_string_rfind(instanceStr, whatStr, from); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_String_rfind(string str, string what, int from); - /// <summary> /// Perform a search for a substring, but start from the end of the string instead of the beginning. /// Also search case-insensitive. @@ -1261,12 +1270,11 @@ namespace Godot /// <returns>The position at which the substring was found, or -1 if not found.</returns> public static int RFindN(this string instance, string what, int from = -1) { - return godot_icall_String_rfindn(instance, what, from); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + using godot_string whatStr = Marshaling.ConvertStringToNative(instance); + return NativeFuncs.godotsharp_string_rfindn(instanceStr, whatStr, from); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern int godot_icall_String_rfindn(string str, string what, int from); - /// <summary> /// Returns the right side of the string from a given position. /// </summary> @@ -1321,12 +1329,12 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static byte[] SHA256Buffer(this string instance) { - return godot_icall_String_sha256_buffer(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_sha256_buffer(instanceStr, out var sha256Buffer); + using (sha256Buffer) + return Marshaling.ConvertNativePackedByteArrayToSystemArray(sha256Buffer); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern byte[] godot_icall_String_sha256_buffer(string str); - /// <summary> /// Returns the SHA-256 hash of the string as a string. /// </summary> @@ -1335,12 +1343,12 @@ namespace Godot /// <returns>The SHA-256 hash of the string.</returns> public static string SHA256Text(this string instance) { - return godot_icall_String_sha256_text(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_sha256_text(instanceStr, out var sha256Text); + using (sha256Text) + return Marshaling.ConvertStringToManaged(sha256Text); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_String_sha256_text(string str); - /// <summary> /// Returns the similarity index of the text compared to this string. /// 1 means totally similar and 0 means totally dissimilar. @@ -1355,6 +1363,7 @@ namespace Godot // Equal strings are totally similar return 1.0f; } + if (instance.Length < 2 || text.Length < 2) { // No way to calculate similarity without a single bigram @@ -1390,12 +1399,12 @@ namespace Godot /// </summary> public static string SimplifyPath(this string instance) { - return godot_icall_String_simplify_path(instance); + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_simplify_path(instanceStr, out godot_string simplifiedPath); + using (simplifiedPath) + return Marshaling.ConvertStringToManaged(simplifiedPath); } - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern string godot_icall_String_simplify_path(string str); - /// <summary> /// Split the string by a divisor string, return an array of the substrings. /// Example "One,Two,Three" will return ["One","Two","Three"] if split by ",". @@ -1409,7 +1418,8 @@ namespace Godot /// <returns>The array of strings split from the string.</returns> public static string[] Split(this string instance, string divisor, bool allowEmpty = true) { - return instance.Split(new[] { divisor }, allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); + return instance.Split(new[] { divisor }, + allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries); } /// <summary> @@ -1605,9 +1615,9 @@ namespace Godot /// <seealso cref="XMLEscape(string)"/> /// <param name="instance">The string to unescape.</param> /// <returns>The unescaped string.</returns> - public static string XMLUnescape(this string instance) + public static string? XMLUnescape(this string instance) { - return SecurityElement.FromString(instance).Text; + return SecurityElement.FromString(instance)?.Text; } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs index b1d504410b..10739c02a7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs @@ -1,5 +1,5 @@ using System; -using System.Runtime.CompilerServices; +using Godot.NativeInterop; namespace Godot { @@ -10,20 +10,11 @@ namespace Godot /// Comparing them is much faster than with regular strings, because only the pointers are compared, /// not the whole strings. /// </summary> - public sealed partial class StringName : IDisposable + public sealed class StringName : IDisposable { - private IntPtr ptr; + internal godot_string_name.movable NativeValue; - internal static IntPtr GetPtr(StringName instance) - { - if (instance == null) - throw new NullReferenceException($"The instance of type {nameof(StringName)} is null."); - - if (instance.ptr == IntPtr.Zero) - throw new ObjectDisposedException(instance.GetType().FullName); - - return instance.ptr; - } + private WeakReference<IDisposable> _weakReferenceToSelf; ~StringName() { @@ -39,35 +30,45 @@ namespace Godot GC.SuppressFinalize(this); } - private void Dispose(bool disposing) + public void Dispose(bool disposing) { - if (ptr != IntPtr.Zero) + // Always dispose `NativeValue` even if disposing is true + NativeValue.DangerousSelfRef.Dispose(); + + if (_weakReferenceToSelf != null) { - godot_icall_StringName_Dtor(ptr); - ptr = IntPtr.Zero; + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); } } - internal StringName(IntPtr ptr) + private StringName(godot_string_name nativeValueToOwn) { - this.ptr = ptr; + NativeValue = (godot_string_name.movable)nativeValueToOwn; + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); } + // Explicit name to make it very clear + internal static StringName CreateTakingOwnershipOfDisposableValue(godot_string_name nativeValueToOwn) + => new StringName(nativeValueToOwn); + /// <summary> /// Constructs an empty <see cref="StringName"/>. /// </summary> public StringName() { - ptr = IntPtr.Zero; } /// <summary> /// Constructs a <see cref="StringName"/> from the given <paramref name="path"/> string. /// </summary> /// <param name="path">String to construct the <see cref="StringName"/> from.</param> - public StringName(string path) + public StringName(string name) { - ptr = path == null ? IntPtr.Zero : godot_icall_StringName_Ctor(path); + if (!string.IsNullOrEmpty(name)) + { + NativeValue = (godot_string_name.movable)NativeFuncs.godotsharp_string_name_new_from_string(name); + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } } /// <summary> @@ -80,7 +81,7 @@ namespace Godot /// Converts a <see cref="StringName"/> to a string. /// </summary> /// <param name="from">The <see cref="StringName"/> to convert.</param> - public static implicit operator string(StringName from) => from.ToString(); + public static implicit operator string(StringName from) => from?.ToString(); /// <summary> /// Converts this <see cref="StringName"/> to a string. @@ -88,28 +89,75 @@ namespace Godot /// <returns>A string representation of this <see cref="StringName"/>.</returns> public override string ToString() { - return ptr == IntPtr.Zero ? string.Empty : godot_icall_StringName_operator_String(GetPtr(this)); + if (IsEmpty) + return string.Empty; + + var src = (godot_string_name)NativeValue; + NativeFuncs.godotsharp_string_name_as_string(out godot_string dest, src); + using (dest) + return Marshaling.ConvertStringToManaged(dest); } /// <summary> /// Check whether this <see cref="StringName"/> is empty. /// </summary> /// <returns>If the <see cref="StringName"/> is empty.</returns> - public bool IsEmpty() + public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty; + + public static bool operator ==(StringName left, StringName right) { - return ptr == IntPtr.Zero || godot_icall_StringName_is_empty(GetPtr(this)); + if (left is null) + return right is null; + return left.Equals(right); } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern IntPtr godot_icall_StringName_Ctor(string path); + public static bool operator !=(StringName left, StringName right) + { + return !(left == right); + } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern void godot_icall_StringName_Dtor(IntPtr ptr); + public bool Equals(StringName other) + { + if (other is null) + return false; + return NativeValue.DangerousSelfRef == other.NativeValue.DangerousSelfRef; + } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern string godot_icall_StringName_operator_String(IntPtr ptr); + public static bool operator ==(StringName left, in godot_string_name right) + { + if (left is null) + return right.IsEmpty; + return left.Equals(right); + } - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern bool godot_icall_StringName_is_empty(IntPtr ptr); + public static bool operator !=(StringName left, in godot_string_name right) + { + return !(left == right); + } + + public static bool operator ==(in godot_string_name left, StringName right) + { + return right == left; + } + + public static bool operator !=(in godot_string_name left, StringName right) + { + return !(right == left); + } + + public bool Equals(in godot_string_name other) + { + return NativeValue.DangerousSelfRef == other; + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj) || (obj is StringName other && Equals(other)); + } + + public override int GetHashCode() + { + return NativeValue.GetHashCode(); + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs index 68d097eb4e..ab2c0cd785 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -93,7 +88,7 @@ namespace Godot case 2: return origin; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } set @@ -110,7 +105,7 @@ namespace Godot origin = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs index 9eaf4f3252..810f55e150 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform3D.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -52,7 +47,7 @@ namespace Godot case 3: return origin; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } set @@ -72,7 +67,7 @@ namespace Godot origin = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(column)); } } } @@ -168,7 +163,7 @@ namespace Godot /// <param name="target">The object to look at.</param> /// <param name="up">The relative up direction.</param> /// <returns>The resulting transform.</returns> - public Transform3D LookingAt(Vector3 target, Vector3 up) + public readonly Transform3D LookingAt(Vector3 target, Vector3 up) { Transform3D t = this; t.SetLookAt(origin, target, up); @@ -194,7 +189,7 @@ namespace Godot /// <param name="axis">The axis to rotate around. Must be normalized.</param> /// <param name="angle">The angle to rotate, in radians.</param> /// <returns>The rotated transformation matrix.</returns> - public Transform3D Rotated(Vector3 axis, real_t angle) + public readonly Transform3D Rotated(Vector3 axis, real_t angle) { return new Transform3D(new Basis(axis, angle), new Vector3()) * this; } @@ -443,7 +438,7 @@ namespace Godot /// </summary> /// <param name="obj">The object to compare with.</param> /// <returns>Whether or not the transform and the object are exactly equal.</returns> - public override bool Equals(object obj) + public override readonly bool Equals(object obj) { if (obj is Transform3D) { @@ -460,7 +455,7 @@ namespace Godot /// </summary> /// <param name="other">The other transform to compare.</param> /// <returns>Whether or not the matrices are exactly equal.</returns> - public bool Equals(Transform3D other) + public readonly bool Equals(Transform3D other) { return basis.Equals(other.basis) && origin.Equals(other.origin); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs deleted file mode 100644 index eae8927ceb..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/UnhandledExceptionArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace Godot -{ - /// <summary> - /// Event arguments for when unhandled exceptions occur. - /// </summary> - public class UnhandledExceptionArgs - { - /// <summary> - /// Exception object. - /// </summary> - public Exception Exception { get; private set; } - - internal UnhandledExceptionArgs(Exception exception) - { - Exception = exception; - } - } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs index 67f70390dd..e47efacf69 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -62,7 +57,7 @@ namespace Godot case 1: return y; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -76,7 +71,7 @@ namespace Godot y = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs index b61954a84c..84790404d7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector2i.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -62,7 +57,7 @@ namespace Godot case 1: return y; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -76,7 +71,7 @@ namespace Godot y = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs index 67a98efc2d..e796d2f20f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -74,7 +69,7 @@ namespace Godot case 2: return z; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -91,7 +86,7 @@ namespace Godot z = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs index 0d4894f206..897e14ae88 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector3i.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; @@ -74,7 +69,7 @@ namespace Godot case 2: return z; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } set @@ -91,7 +86,7 @@ namespace Godot z = value; return; default: - throw new IndexOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(index)); } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs index 4af817455c..f60033078c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs index 365dcef486..2802c1bb06 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Vector4i.cs @@ -1,8 +1,3 @@ -#if REAL_T_IS_DOUBLE -using real_t = System.Double; -#else -using real_t = System.Single; -#endif using System; using System.Runtime.InteropServices; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GlobalUsings.cs b/modules/mono/glue/GodotSharp/GodotSharp/GlobalUsings.cs new file mode 100644 index 0000000000..263a934fae --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/GlobalUsings.cs @@ -0,0 +1,5 @@ +#if REAL_T_IS_DOUBLE +global using real_t = System.Double; +#else +global using real_t = System.Single; +#endif diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index 4f55ce47e8..111920ecf6 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -4,25 +4,67 @@ <OutputPath>bin/$(Configuration)</OutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <RootNamespace>Godot</RootNamespace> - <TargetFramework>netstandard2.1</TargetFramework> + <TargetFramework>net6.0</TargetFramework> <DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile> <EnableDefaultItems>false</EnableDefaultItems> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <LangVersion>10</LangVersion> + + <AnalysisMode>Recommended</AnalysisMode> + + <!-- Disabled temporarily as it pollutes the warnings, but we need to document public APIs. --> + <NoWarn>CS1591</NoWarn> </PropertyGroup> <PropertyGroup> + <Description>Godot C# Core API.</Description> + <Authors>Godot Engine contributors</Authors> + + <PackageId>GodotSharp</PackageId> + <Version>4.0.0</Version> + <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> + <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharp</RepositoryUrl> + <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> + <PackageLicenseExpression>MIT</PackageLicenseExpression> + + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> + </PropertyGroup> + <ItemGroup> + <!-- SdkPackageVersions.props for easy access --> + <None Include="$(GodotSdkPackageVersionsFilePath)"> + <Link>SdkPackageVersions.props</Link> + </None> + </ItemGroup> + <PropertyGroup> <DefineConstants>$(DefineConstants);GODOT</DefineConstants> </PropertyGroup> <ItemGroup> + <PackageReference Include="ReflectionAnalyzers" Version="0.1.22-dev" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers" /> + <!--PackageReference Include="IDisposableAnalyzers" Version="3.4.13" PrivateAssets="all" IncludeAssets="runtime; build; native; contentfiles; analyzers" /--> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\Godot.SourceGenerators.Internal\Godot.SourceGenerators.Internal.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> + </ItemGroup> + <!-- Sources --> + <ItemGroup> <Compile Include="Core\AABB.cs" /> + <Compile Include="Core\Bridge\GodotSerializationInfo.cs" /> + <Compile Include="Core\Bridge\MethodInfo.cs" /> + <Compile Include="Core\CustomGCHandle.cs" /> <Compile Include="Core\Array.cs" /> <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" /> - <Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" /> <Compile Include="Core\Attributes\ExportAttribute.cs" /> - <Compile Include="Core\Attributes\GodotMethodAttribute.cs" /> <Compile Include="Core\Attributes\RPCAttribute.cs" /> <Compile Include="Core\Attributes\ScriptPathAttribute.cs" /> <Compile Include="Core\Attributes\SignalAttribute.cs" /> <Compile Include="Core\Attributes\ToolAttribute.cs" /> <Compile Include="Core\Basis.cs" /> + <Compile Include="Core\Bridge\CSharpInstanceBridge.cs" /> + <Compile Include="Core\Bridge\GCHandleBridge.cs" /> + <Compile Include="Core\Bridge\AlcReloadCfg.cs" /> + <Compile Include="Core\Bridge\ManagedCallbacks.cs" /> + <Compile Include="Core\Bridge\PropertyInfo.cs" /> + <Compile Include="Core\Bridge\ScriptManagerBridge.cs" /> + <Compile Include="Core\Bridge\ScriptManagerBridge.types.cs" /> <Compile Include="Core\Callable.cs" /> <Compile Include="Core\Color.cs" /> <Compile Include="Core\Colors.cs" /> @@ -30,45 +72,58 @@ <Compile Include="Core\DelegateUtils.cs" /> <Compile Include="Core\Dictionary.cs" /> <Compile Include="Core\Dispatcher.cs" /> - <Compile Include="Core\DynamicObject.cs" /> <Compile Include="Core\Extensions\NodeExtensions.cs" /> <Compile Include="Core\Extensions\ObjectExtensions.cs" /> <Compile Include="Core\Extensions\PackedSceneExtensions.cs" /> <Compile Include="Core\Extensions\ResourceLoaderExtensions.cs" /> - <Compile Include="Core\Extensions\SceneTreeExtensions.cs" /> <Compile Include="Core\GD.cs" /> <Compile Include="Core\GodotSynchronizationContext.cs" /> <Compile Include="Core\GodotTaskScheduler.cs" /> <Compile Include="Core\GodotTraceListener.cs" /> <Compile Include="Core\GodotUnhandledExceptionEvent.cs" /> + <Compile Include="Core\DisposablesTracker.cs" /> <Compile Include="Core\Interfaces\IAwaitable.cs" /> <Compile Include="Core\Interfaces\IAwaiter.cs" /> <Compile Include="Core\Interfaces\ISerializationListener.cs" /> - <Compile Include="Core\MarshalUtils.cs" /> <Compile Include="Core\Mathf.cs" /> <Compile Include="Core\MathfEx.cs" /> + <Compile Include="Core\NativeInterop\CustomUnsafe.cs" /> + <Compile Include="Core\NativeInterop\ExceptionUtils.cs" /> + <Compile Include="Core\NativeInterop\GodotDllImportResolver.cs" /> + <Compile Include="Core\NativeInterop\InteropUtils.cs" /> + <Compile Include="Core\NativeInterop\NativeFuncs.extended.cs" /> + <Compile Include="Core\NativeInterop\NativeVariantPtrArgs.cs" /> + <Compile Include="Core\NativeInterop\VariantConversionCallbacks.cs" /> + <Compile Include="Core\NativeInterop\VariantSpanHelpers.cs" /> + <Compile Include="Core\NativeInterop\VariantUtils.cs" /> <Compile Include="Core\NodePath.cs" /> <Compile Include="Core\Object.base.cs" /> + <Compile Include="Core\Object.exceptions.cs" /> <Compile Include="Core\Plane.cs" /> <Compile Include="Core\Projection.cs" /> <Compile Include="Core\Quaternion.cs" /> <Compile Include="Core\Rect2.cs" /> <Compile Include="Core\Rect2i.cs" /> + <Compile Include="Core\ReflectionUtils.cs" /> <Compile Include="Core\RID.cs" /> + <Compile Include="Core\NativeInterop\NativeFuncs.cs" /> + <Compile Include="Core\NativeInterop\InteropStructs.cs" /> + <Compile Include="Core\NativeInterop\Marshaling.cs" /> <Compile Include="Core\SignalInfo.cs" /> <Compile Include="Core\SignalAwaiter.cs" /> <Compile Include="Core\StringExtensions.cs" /> <Compile Include="Core\StringName.cs" /> <Compile Include="Core\Transform2D.cs" /> <Compile Include="Core\Transform3D.cs" /> - <Compile Include="Core\UnhandledExceptionArgs.cs" /> <Compile Include="Core\Vector2.cs" /> <Compile Include="Core\Vector2i.cs" /> <Compile Include="Core\Vector3.cs" /> <Compile Include="Core\Vector3i.cs" /> <Compile Include="Core\Vector4.cs" /> <Compile Include="Core\Vector4i.cs" /> + <Compile Include="GlobalUsings.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> + <Compile Include="Variant.cs" /> </ItemGroup> <!-- We import a props file with auto-generated includes. This works well with Rider. diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj.DotSettings b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj.DotSettings new file mode 100644 index 0000000000..1add6cc77e --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj.DotSettings @@ -0,0 +1,5 @@ +<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=core/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated_005Cgodotobjects/@EntryIndexedValue">True</s:Boolean> +</wpf:ResourceDictionary> diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs b/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs new file mode 100644 index 0000000000..eb8b061120 --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs @@ -0,0 +1,841 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Godot.NativeInterop; + +namespace Godot; + +#nullable enable + +[SuppressMessage("ReSharper", "RedundantNameQualifier")] +public partial struct Variant : IDisposable +{ + internal godot_variant.movable NativeVar; + private object? _obj; + private Disposer? _disposer; + + private class Disposer : IDisposable + { + private godot_variant.movable _native; + + private WeakReference<IDisposable>? _weakReferenceToSelf; + + public Disposer(in godot_variant.movable nativeVar) + { + _native = nativeVar; + _weakReferenceToSelf = DisposablesTracker.RegisterDisposable(this); + } + + ~Disposer() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose(bool disposing) + { + _native.DangerousSelfRef.Dispose(); + + if (_weakReferenceToSelf != null) + { + DisposablesTracker.UnregisterDisposable(_weakReferenceToSelf); + } + } + } + + private Variant(in godot_variant nativeVar) + { + NativeVar = (godot_variant.movable)nativeVar; + _obj = null; + + switch (nativeVar.Type) + { + case Type.Nil: + case Type.Bool: + case Type.Int: + case Type.Float: + case Type.Vector2: + case Type.Vector2i: + case Type.Rect2: + case Type.Rect2i: + case Type.Vector3: + case Type.Vector3i: + case Type.Plane: + case Type.Quaternion: + case Type.Color: + case Type.Rid: + _disposer = null; + break; + default: + { + _disposer = new Disposer(NativeVar); + break; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + // Explicit name to make it very clear + public static Variant CreateTakingOwnershipOfDisposableValue(in godot_variant nativeValueToOwn) => + new(nativeValueToOwn); + + // Explicit name to make it very clear + public static Variant CreateCopyingBorrowed(in godot_variant nativeValueToOwn) => + new(NativeFuncs.godotsharp_variant_new_copy(nativeValueToOwn)); + + /// <summary> + /// Constructs a new <see cref="Godot.NativeInterop.godot_variant"/> from this instance. + /// The caller is responsible of disposing the new instance to avoid memory leaks. + /// </summary> + public godot_variant CopyNativeVariant() => + NativeFuncs.godotsharp_variant_new_copy((godot_variant)NativeVar); + + public void Dispose() + { + _disposer?.Dispose(); + NativeVar = default; + _obj = null; + } + + // TODO: Consider renaming Variant.Type to VariantType and this property to Type. VariantType would also avoid ambiguity with System.Type. + public Type VariantType => NativeVar.DangerousSelfRef.Type; + + public override string ToString() => AsString(); + + public object? Obj + { + get + { + if (_obj == null) + _obj = Marshaling.ConvertVariantToManagedObject((godot_variant)NativeVar); + + return _obj; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool AsBool() => + VariantUtils.ConvertToBool((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public char AsChar() => + (char)VariantUtils.ConvertToUInt16((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public sbyte AsSByte() => + VariantUtils.ConvertToInt8((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public short AsInt16() => + VariantUtils.ConvertToInt16((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int AsInt32() => + VariantUtils.ConvertToInt32((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long AsInt64() => + VariantUtils.ConvertToInt64((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte AsByte() => + VariantUtils.ConvertToUInt8((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ushort AsUInt16() => + VariantUtils.ConvertToUInt16((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public uint AsUInt32() => + VariantUtils.ConvertToUInt32((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong AsUInt64() => + VariantUtils.ConvertToUInt64((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float AsSingle() => + VariantUtils.ConvertToFloat32((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double AsDouble() => + VariantUtils.ConvertToFloat64((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string AsString() => + VariantUtils.ConvertToStringObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector2 AsVector2() => + VariantUtils.ConvertToVector2((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector2i AsVector2i() => + VariantUtils.ConvertToVector2i((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rect2 AsRect2() => + VariantUtils.ConvertToRect2((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rect2i AsRect2i() => + VariantUtils.ConvertToRect2i((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Transform2D AsTransform2D() => + VariantUtils.ConvertToTransform2D((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector3 AsVector3() => + VariantUtils.ConvertToVector3((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector3i AsVector3i() => + VariantUtils.ConvertToVector3i((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Basis AsBasis() => + VariantUtils.ConvertToBasis((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Quaternion AsQuaternion() => + VariantUtils.ConvertToQuaternion((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Transform3D AsTransform3D() => + VariantUtils.ConvertToTransform3D((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 AsVector4() => + VariantUtils.ConvertToVector4((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4i AsVector4i() => + VariantUtils.ConvertToVector4i((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Projection AsProjection() => + VariantUtils.ConvertToProjection((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public AABB AsAABB() => + VariantUtils.ConvertToAABB((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Color AsColor() => + VariantUtils.ConvertToColor((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Plane AsPlane() => + VariantUtils.ConvertToPlane((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Callable AsCallable() => + VariantUtils.ConvertToCallableManaged((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public SignalInfo AsSignalInfo() => + VariantUtils.ConvertToSignalInfo((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public byte[] AsByteArray() => + VariantUtils.ConvertAsPackedByteArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int[] AsInt32Array() => + VariantUtils.ConvertAsPackedInt32ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public long[] AsInt64Array() => + VariantUtils.ConvertAsPackedInt64ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float[] AsFloat32Array() => + VariantUtils.ConvertAsPackedFloat32ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public double[] AsFloat64Array() => + VariantUtils.ConvertAsPackedFloat64ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string[] AsStringArray() => + VariantUtils.ConvertAsPackedStringArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector2[] AsVector2Array() => + VariantUtils.ConvertAsPackedVector2ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector3[] AsVector3Array() => + VariantUtils.ConvertAsPackedVector3ArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Color[] AsColorArray() => + VariantUtils.ConvertAsPackedColorArrayToSystemArray((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T[] AsGodotObjectArray<T>() + where T : Godot.Object => + VariantUtils.ConvertToSystemArrayOfGodotObject<T>((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Dictionary<TKey, TValue> AsGodotDictionary<TKey, TValue>() => + VariantUtils.ConvertToDictionaryObject<TKey, TValue>((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Array<T> AsGodotArray<T>() => + VariantUtils.ConvertToArrayObject<T>((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StringName[] AsSystemArrayOfStringName() => + VariantUtils.ConvertToSystemArrayOfStringName((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NodePath[] AsSystemArrayOfNodePath() => + VariantUtils.ConvertToSystemArrayOfNodePath((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RID[] AsSystemArrayOfRID() => + VariantUtils.ConvertToSystemArrayOfRID((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Godot.Object AsGodotObject() => + VariantUtils.ConvertToGodotObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public StringName AsStringName() => + VariantUtils.ConvertToStringNameObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public NodePath AsNodePath() => + VariantUtils.ConvertToNodePathObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public RID AsRID() => + VariantUtils.ConvertToRID((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Dictionary AsGodotDictionary() => + VariantUtils.ConvertToDictionaryObject((godot_variant)NativeVar); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Collections.Array AsGodotArray() => + VariantUtils.ConvertToArrayObject((godot_variant)NativeVar); + + // Explicit conversion operators to supported types + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator bool(Variant from) => from.AsBool(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator char(Variant from) => from.AsChar(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator sbyte(Variant from) => from.AsSByte(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator short(Variant from) => from.AsInt16(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator int(Variant from) => from.AsInt32(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator long(Variant from) => from.AsInt64(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator byte(Variant from) => from.AsByte(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator ushort(Variant from) => from.AsUInt16(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator uint(Variant from) => from.AsUInt32(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator ulong(Variant from) => from.AsUInt64(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator float(Variant from) => from.AsSingle(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator double(Variant from) => from.AsDouble(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator string(Variant from) => from.AsString(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector2(Variant from) => from.AsVector2(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector2i(Variant from) => from.AsVector2i(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Rect2(Variant from) => from.AsRect2(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Rect2i(Variant from) => from.AsRect2i(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Transform2D(Variant from) => from.AsTransform2D(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector3(Variant from) => from.AsVector3(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector3i(Variant from) => from.AsVector3i(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Basis(Variant from) => from.AsBasis(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Quaternion(Variant from) => from.AsQuaternion(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Transform3D(Variant from) => from.AsTransform3D(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector4(Variant from) => from.AsVector4(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector4i(Variant from) => from.AsVector4i(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Projection(Variant from) => from.AsProjection(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator AABB(Variant from) => from.AsAABB(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Color(Variant from) => from.AsColor(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Plane(Variant from) => from.AsPlane(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Callable(Variant from) => from.AsCallable(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator SignalInfo(Variant from) => from.AsSignalInfo(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator byte[](Variant from) => from.AsByteArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator int[](Variant from) => from.AsInt32Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator long[](Variant from) => from.AsInt64Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator float[](Variant from) => from.AsFloat32Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator double[](Variant from) => from.AsFloat64Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator string[](Variant from) => from.AsStringArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector2[](Variant from) => from.AsVector2Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Vector3[](Variant from) => from.AsVector3Array(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Color[](Variant from) => from.AsColorArray(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator StringName[](Variant from) => from.AsSystemArrayOfStringName(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator NodePath[](Variant from) => from.AsSystemArrayOfNodePath(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator RID[](Variant from) => from.AsSystemArrayOfRID(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Godot.Object(Variant from) => from.AsGodotObject(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator StringName(Variant from) => from.AsStringName(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator NodePath(Variant from) => from.AsNodePath(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator RID(Variant from) => from.AsRID(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Collections.Dictionary(Variant from) => from.AsGodotDictionary(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Collections.Array(Variant from) => from.AsGodotArray(); + + // While we provide implicit conversion operators, normal methods are still needed for + // casts that are not done implicitly (e.g.: raw array to Span, enum to integer, etc). + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(bool from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(char from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(sbyte from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(short from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(int from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(long from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(byte from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(ushort from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(uint from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(ulong from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(float from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(double from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(string from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector2 from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector2i from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Rect2 from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Rect2i from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Transform2D from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector3 from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector3i from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Basis from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Quaternion from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Transform3D from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector4 from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Vector4i from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Projection from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(AABB from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Color from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Plane from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Callable from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(SignalInfo from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<byte> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<int> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<long> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<float> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<double> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<string> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<Vector2> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<Vector3> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<Color> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Godot.Object[] from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom<TKey, TValue>(Collections.Dictionary<TKey, TValue> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromDictionary(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom<T>(Collections.Array<T> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<StringName> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<NodePath> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Span<RID> from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Godot.Object from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(StringName from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(NodePath from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(RID from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Collections.Dictionary from) => from; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Variant CreateFrom(Collections.Array from) => from; + + // Implicit conversion operators + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(bool from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromBool(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(char from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(sbyte from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(short from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(int from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(long from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(byte from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(ushort from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(uint from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(ulong from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromInt(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(float from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromFloat(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(double from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromFloat(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(string from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromString(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector2 from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector2(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector2i from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector2i(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Rect2 from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromRect2(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Rect2i from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromRect2i(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Transform2D from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromTransform2D(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector3 from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector3(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector3i from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector3i(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Basis from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromBasis(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Quaternion from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromQuaternion(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Transform3D from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromTransform3D(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector4 from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector4(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Vector4i from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromVector4i(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Projection from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromProjection(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(AABB from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromAABB(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Color from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromColor(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Plane from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPlane(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Callable from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromCallable(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(SignalInfo from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSignalInfo(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<byte> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedByteArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<int> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedInt32Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<long> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedInt64Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<float> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedFloat32Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<double> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedFloat64Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<string> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedStringArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<Vector2> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedVector2Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<Vector3> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedVector3Array(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<Color> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedColorArray(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Godot.Object[] from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfGodotObject(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<StringName> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfStringName(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<NodePath> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfNodePath(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Span<RID> from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfRID(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Godot.Object from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromGodotObject(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(StringName from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromStringName(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(NodePath from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromNodePath(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(RID from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromRID(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Collections.Dictionary from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromDictionary(from)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Variant(Collections.Array from) => + CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromArray(from)); +} diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj index a8c4ba96b5..ebf09aab7b 100644 --- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj @@ -4,9 +4,24 @@ <OutputPath>bin/$(Configuration)</OutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <RootNamespace>Godot</RootNamespace> - <TargetFramework>netstandard2.1</TargetFramework> + <TargetFramework>net6.0</TargetFramework> <DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile> <EnableDefaultItems>false</EnableDefaultItems> + <AllowUnsafeBlocks>true</AllowUnsafeBlocks> + <LangVersion>10</LangVersion> + </PropertyGroup> + <PropertyGroup> + <Description>Godot C# Editor API.</Description> + <Authors>Godot Engine contributors</Authors> + + <PackageId>GodotSharpEditor</PackageId> + <Version>4.0.0</Version> + <PackageVersion>$(PackageVersion_GodotSharp)</PackageVersion> + <RepositoryUrl>https://github.com/godotengine/godot/tree/master/modules/mono/glue/GodotSharp/GodotSharpEditor</RepositoryUrl> + <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> + <PackageLicenseExpression>MIT</PackageLicenseExpression> + + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> </PropertyGroup> <PropertyGroup> <DefineConstants>$(DefineConstants);GODOT</DefineConstants> diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj.DotSettings b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj.DotSettings new file mode 100644 index 0000000000..c7ff6fd3ee --- /dev/null +++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj.DotSettings @@ -0,0 +1,4 @@ +<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=generated_005Cgodotobjects/@EntryIndexedValue">True</s:Boolean> +</wpf:ResourceDictionary> diff --git a/modules/mono/glue/arguments_vector.h b/modules/mono/glue/arguments_vector.h deleted file mode 100644 index 4405809887..0000000000 --- a/modules/mono/glue/arguments_vector.h +++ /dev/null @@ -1,67 +0,0 @@ -/*************************************************************************/ -/* arguments_vector.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 ARGUMENTS_VECTOR_H -#define ARGUMENTS_VECTOR_H - -#include "core/os/memory.h" - -template <typename T, int POOL_SIZE = 5> -struct ArgumentsVector { -private: - T pool[POOL_SIZE]; - T *_ptr = nullptr; - int size; - - ArgumentsVector() = delete; - ArgumentsVector(const ArgumentsVector &) = delete; - -public: - T *ptr() { return _ptr; } - T &get(int p_idx) { return _ptr[p_idx]; } - void set(int p_idx, const T &p_value) { _ptr[p_idx] = p_value; } - - explicit ArgumentsVector(int p_size) : - size(p_size) { - if (p_size <= POOL_SIZE) { - _ptr = pool; - } else { - _ptr = memnew_arr(T, p_size); - } - } - - ~ArgumentsVector() { - if (size > POOL_SIZE) { - memdelete_arr(_ptr); - } - } -}; - -#endif // ARGUMENTS_VECTOR_H diff --git a/modules/mono/glue/base_object_glue.cpp b/modules/mono/glue/base_object_glue.cpp deleted file mode 100644 index 7b9dbc87cf..0000000000 --- a/modules/mono/glue/base_object_glue.cpp +++ /dev/null @@ -1,257 +0,0 @@ -/*************************************************************************/ -/* base_object_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/object/class_db.h" -#include "core/object/ref_counted.h" -#include "core/string/string_name.h" - -#include "../csharp_script.h" -#include "../mono_gd/gd_mono_cache.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_internals.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../mono_gd/gd_mono_utils.h" -#include "../signal_awaiter_utils.h" -#include "arguments_vector.h" - -Object *godot_icall_Object_Ctor(MonoObject *p_obj) { - Object *instance = memnew(Object); - GDMonoInternals::tie_managed_to_unmanaged(p_obj, instance); - return instance; -} - -void godot_icall_Object_Disposed(MonoObject *p_obj, Object *p_ptr) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_ptr == nullptr); -#endif - - if (p_ptr->get_script_instance()) { - CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); - if (cs_instance) { - if (!cs_instance->is_destructing_script_instance()) { - cs_instance->mono_object_disposed(p_obj); - p_ptr->set_script_instance(nullptr); - } - return; - } - } - - void *data = CSharpLanguage::get_existing_instance_binding(p_ptr); - - if (data) { - CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); - if (script_binding.inited) { - MonoGCHandleData &gchandle = script_binding.gchandle; - if (!gchandle.is_released()) { - CSharpLanguage::release_script_gchandle(p_obj, gchandle); - } - } - } -} - -void godot_icall_RefCounted_Disposed(MonoObject *p_obj, Object *p_ptr, MonoBoolean p_is_finalizer) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_ptr == nullptr); - // This is only called with RefCounted derived classes - CRASH_COND(!Object::cast_to<RefCounted>(p_ptr)); -#endif - - RefCounted *rc = static_cast<RefCounted *>(p_ptr); - - if (rc->get_script_instance()) { - CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(rc->get_script_instance()); - if (cs_instance) { - if (!cs_instance->is_destructing_script_instance()) { - bool delete_owner; - bool remove_script_instance; - - cs_instance->mono_object_disposed_baseref(p_obj, p_is_finalizer, delete_owner, remove_script_instance); - - if (delete_owner) { - memdelete(rc); - } else if (remove_script_instance) { - rc->set_script_instance(nullptr); - } - } - return; - } - } - - // Unsafe refcount decrement. The managed instance also counts as a reference. - // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) - CSharpLanguage::get_singleton()->pre_unsafe_unreference(rc); - if (rc->unreference()) { - memdelete(rc); - } else { - void *data = CSharpLanguage::get_existing_instance_binding(rc); - - if (data) { - CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); - if (script_binding.inited) { - MonoGCHandleData &gchandle = script_binding.gchandle; - if (!gchandle.is_released()) { - CSharpLanguage::release_script_gchandle(p_obj, gchandle); - } - } - } - } -} - -void godot_icall_Object_ConnectEventSignals(Object *p_ptr) { - CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); - if (csharp_instance) { - csharp_instance->connect_event_signals(); - } -} - -MethodBind *godot_icall_Object_ClassDB_get_method(StringName *p_type, MonoString *p_method) { - StringName type = p_type ? *p_type : StringName(); - StringName method(GDMonoMarshal::mono_string_to_godot(p_method)); - return ClassDB::get_method(type, method); -} - -MonoObject *godot_icall_Object_weakref(Object *p_ptr) { - if (!p_ptr) { - return nullptr; - } - - Ref<WeakRef> wref; - RefCounted *rc = Object::cast_to<RefCounted>(p_ptr); - - if (rc) { - Ref<RefCounted> r = rc; - if (!r.is_valid()) { - return nullptr; - } - - wref.instantiate(); - wref->set_ref(r); - } else { - wref.instantiate(); - wref->set_obj(p_ptr); - } - - return GDMonoUtils::unmanaged_get_managed(wref.ptr()); -} - -int32_t godot_icall_SignalAwaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, MonoObject *p_awaiter) { - StringName signal = p_signal ? *p_signal : StringName(); - return (int32_t)gd_mono_connect_signal_awaiter(p_source, signal, p_target, p_awaiter); -} - -MonoArray *godot_icall_DynamicGodotObject_SetMemberList(Object *p_ptr) { - List<PropertyInfo> property_list; - p_ptr->get_property_list(&property_list); - - MonoArray *result = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), property_list.size()); - - int i = 0; - for (const PropertyInfo &E : property_list) { - MonoString *boxed = GDMonoMarshal::mono_string_from_godot(E.name); - mono_array_setref(result, i, boxed); - i++; - } - - return result; -} - -MonoBoolean godot_icall_DynamicGodotObject_InvokeMember(Object *p_ptr, MonoString *p_name, MonoArray *p_args, MonoObject **r_result) { - String name = GDMonoMarshal::mono_string_to_godot(p_name); - - int argc = mono_array_length(p_args); - - ArgumentsVector<Variant> arg_store(argc); - ArgumentsVector<const Variant *> args(argc); - - for (int i = 0; i < argc; i++) { - MonoObject *elem = mono_array_get(p_args, MonoObject *, i); - arg_store.set(i, GDMonoMarshal::mono_object_to_variant(elem)); - args.set(i, &arg_store.get(i)); - } - - Callable::CallError error; - Variant result = p_ptr->callp(StringName(name), args.ptr(), argc, error); - - *r_result = GDMonoMarshal::variant_to_mono_object(result); - - return error.error == Callable::CallError::CALL_OK; -} - -MonoBoolean godot_icall_DynamicGodotObject_GetMember(Object *p_ptr, MonoString *p_name, MonoObject **r_result) { - String name = GDMonoMarshal::mono_string_to_godot(p_name); - - bool valid; - Variant value = p_ptr->get(StringName(name), &valid); - - if (valid) { - *r_result = GDMonoMarshal::variant_to_mono_object(value); - } - - return valid; -} - -MonoBoolean godot_icall_DynamicGodotObject_SetMember(Object *p_ptr, MonoString *p_name, MonoObject *p_value) { - String name = GDMonoMarshal::mono_string_to_godot(p_name); - Variant value = GDMonoMarshal::mono_object_to_variant(p_value); - - bool valid; - p_ptr->set(StringName(name), value, &valid); - - return valid; -} - -MonoString *godot_icall_Object_ToString(Object *p_ptr) { -#ifdef DEBUG_ENABLED - // Cannot happen in C#; would get an ObjectDisposedException instead. - CRASH_COND(p_ptr == nullptr); -#endif - // Can't call 'Object::to_string()' here, as that can end up calling 'ToString' again resulting in an endless circular loop. - String result = "[" + p_ptr->get_class() + ":" + itos(p_ptr->get_instance_id()) + "]"; - return GDMonoMarshal::mono_string_from_godot(result); -} - -void godot_register_object_icalls() { - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_Ctor", godot_icall_Object_Ctor); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_Disposed", godot_icall_Object_Disposed); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_RefCounted_Disposed", godot_icall_RefCounted_Disposed); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ConnectEventSignals", godot_icall_Object_ConnectEventSignals); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ClassDB_get_method", godot_icall_Object_ClassDB_get_method); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_ToString", godot_icall_Object_ToString); - GDMonoUtils::add_internal_call("Godot.Object::godot_icall_Object_weakref", godot_icall_Object_weakref); - GDMonoUtils::add_internal_call("Godot.SignalAwaiter::godot_icall_SignalAwaiter_connect", godot_icall_SignalAwaiter_connect); - GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMemberList", godot_icall_DynamicGodotObject_SetMemberList); - GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_InvokeMember", godot_icall_DynamicGodotObject_InvokeMember); - GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_GetMember", godot_icall_DynamicGodotObject_GetMember); - GDMonoUtils::add_internal_call("Godot.DynamicGodotObject::godot_icall_DynamicGodotObject_SetMember", godot_icall_DynamicGodotObject_SetMember); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp deleted file mode 100644 index 8a9f30459c..0000000000 --- a/modules/mono/glue/collections_glue.cpp +++ /dev/null @@ -1,374 +0,0 @@ -/*************************************************************************/ -/* collections_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include <mono/metadata/exception.h> - -#include "core/variant/array.h" - -#include "../mono_gd/gd_mono_cache.h" -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../mono_gd/gd_mono_utils.h" - -Array *godot_icall_Array_Ctor() { - return memnew(Array); -} - -void godot_icall_Array_Dtor(Array *ptr) { - memdelete(ptr); -} - -MonoObject *godot_icall_Array_At(Array *ptr, int32_t index) { - if (index < 0 || index >= ptr->size()) { - GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return nullptr; - } - return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index)); -} - -MonoObject *godot_icall_Array_At_Generic(Array *ptr, int32_t index, uint32_t type_encoding, GDMonoClass *type_class) { - if (index < 0 || index >= ptr->size()) { - GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return nullptr; - } - return GDMonoMarshal::variant_to_mono_object(ptr->operator[](index), ManagedType(type_encoding, type_class)); -} - -void godot_icall_Array_SetAt(Array *ptr, int32_t index, MonoObject *value) { - if (index < 0 || index >= ptr->size()) { - GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return; - } - ptr->operator[](index) = GDMonoMarshal::mono_object_to_variant(value); -} - -int32_t godot_icall_Array_Count(Array *ptr) { - return ptr->size(); -} - -int32_t godot_icall_Array_Add(Array *ptr, MonoObject *item) { - ptr->append(GDMonoMarshal::mono_object_to_variant(item)); - return ptr->size(); -} - -void godot_icall_Array_Clear(Array *ptr) { - ptr->clear(); -} - -MonoBoolean godot_icall_Array_Contains(Array *ptr, MonoObject *item) { - return ptr->find(GDMonoMarshal::mono_object_to_variant(item)) != -1; -} - -void godot_icall_Array_CopyTo(Array *ptr, MonoArray *array, int32_t array_index) { - unsigned int count = ptr->size(); - - if (mono_array_length(array) < (array_index + count)) { - MonoException *exc = mono_get_exception_argument("", "Destination array was not long enough. Check destIndex and length, and the array's lower bounds."); - GDMonoUtils::set_pending_exception(exc); - return; - } - - for (unsigned int i = 0; i < count; i++) { - MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(ptr->operator[](i)); - mono_array_setref(array, array_index, boxed); - array_index++; - } -} - -Array *godot_icall_Array_Ctor_MonoArray(MonoArray *mono_array) { - Array *godot_array = memnew(Array); - unsigned int count = mono_array_length(mono_array); - godot_array->resize(count); - for (unsigned int i = 0; i < count; i++) { - MonoObject *item = mono_array_get(mono_array, MonoObject *, i); - godot_icall_Array_SetAt(godot_array, i, item); - } - return godot_array; -} - -Array *godot_icall_Array_Duplicate(Array *ptr, MonoBoolean deep) { - return memnew(Array(ptr->duplicate(deep))); -} - -Array *godot_icall_Array_Concatenate(Array *left, Array *right) { - int count = left->size() + right->size(); - Array *new_array = memnew(Array(left->duplicate(false))); - new_array->resize(count); - for (unsigned int i = 0; i < (unsigned int)right->size(); i++) { - new_array->operator[](i + left->size()) = right->operator[](i); - } - return new_array; -} - -int32_t godot_icall_Array_IndexOf(Array *ptr, MonoObject *item) { - return ptr->find(GDMonoMarshal::mono_object_to_variant(item)); -} - -void godot_icall_Array_Insert(Array *ptr, int32_t index, MonoObject *item) { - if (index < 0 || index > ptr->size()) { - GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return; - } - ptr->insert(index, GDMonoMarshal::mono_object_to_variant(item)); -} - -MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item) { - int idx = ptr->find(GDMonoMarshal::mono_object_to_variant(item)); - if (idx >= 0) { - ptr->remove_at(idx); - return true; - } - return false; -} - -void godot_icall_Array_RemoveAt(Array *ptr, int32_t index) { - if (index < 0 || index >= ptr->size()) { - GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); - return; - } - ptr->remove_at(index); -} - -int32_t godot_icall_Array_Resize(Array *ptr, int32_t new_size) { - return (int32_t)ptr->resize(new_size); -} - -void godot_icall_Array_Shuffle(Array *ptr) { - ptr->shuffle(); -} - -void godot_icall_Array_Generic_GetElementTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class) { - MonoType *elem_type = mono_reflection_type_get_type(refltype); - - *type_encoding = mono_type_get_type(elem_type); - MonoClass *type_class_raw = mono_class_from_mono_type(elem_type); - *type_class = GDMono::get_singleton()->get_class(type_class_raw); -} - -MonoString *godot_icall_Array_ToString(Array *ptr) { - return GDMonoMarshal::mono_string_from_godot(Variant(*ptr).operator String()); -} - -Dictionary *godot_icall_Dictionary_Ctor() { - return memnew(Dictionary); -} - -void godot_icall_Dictionary_Dtor(Dictionary *ptr) { - memdelete(ptr); -} - -MonoObject *godot_icall_Dictionary_GetValue(Dictionary *ptr, MonoObject *key) { - Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == nullptr) { - MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr()); -#ifdef DEBUG_ENABLED - CRASH_COND(!exc); -#endif - GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException)); - GDMonoUtils::set_pending_exception((MonoException *)exc); - return nullptr; - } - return GDMonoMarshal::variant_to_mono_object(ret); -} - -MonoObject *godot_icall_Dictionary_GetValue_Generic(Dictionary *ptr, MonoObject *key, uint32_t type_encoding, GDMonoClass *type_class) { - Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == nullptr) { - MonoObject *exc = mono_object_new(mono_domain_get(), CACHED_CLASS(KeyNotFoundException)->get_mono_ptr()); -#ifdef DEBUG_ENABLED - CRASH_COND(!exc); -#endif - GDMonoUtils::runtime_object_init(exc, CACHED_CLASS(KeyNotFoundException)); - GDMonoUtils::set_pending_exception((MonoException *)exc); - return nullptr; - } - return GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class)); -} - -void godot_icall_Dictionary_SetValue(Dictionary *ptr, MonoObject *key, MonoObject *value) { - ptr->operator[](GDMonoMarshal::mono_object_to_variant(key)) = GDMonoMarshal::mono_object_to_variant(value); -} - -Array *godot_icall_Dictionary_Keys(Dictionary *ptr) { - return memnew(Array(ptr->keys())); -} - -Array *godot_icall_Dictionary_Values(Dictionary *ptr) { - return memnew(Array(ptr->values())); -} - -int32_t godot_icall_Dictionary_Count(Dictionary *ptr) { - return ptr->size(); -} - -int32_t godot_icall_Dictionary_KeyValuePairs(Dictionary *ptr, Array **keys, Array **values) { - *keys = godot_icall_Dictionary_Keys(ptr); - *values = godot_icall_Dictionary_Values(ptr); - return godot_icall_Dictionary_Count(ptr); -} - -void godot_icall_Dictionary_KeyValuePairAt(Dictionary *ptr, int index, MonoObject **key, MonoObject **value) { - *key = GDMonoMarshal::variant_to_mono_object(ptr->get_key_at_index(index)); - *value = GDMonoMarshal::variant_to_mono_object(ptr->get_value_at_index(index)); -} - -void godot_icall_Dictionary_KeyValuePairAt_Generic(Dictionary *ptr, int index, MonoObject **key, MonoObject **value, uint32_t value_type_encoding, GDMonoClass *value_type_class) { - ManagedType type(value_type_encoding, value_type_class); - *key = GDMonoMarshal::variant_to_mono_object(ptr->get_key_at_index(index)); - *value = GDMonoMarshal::variant_to_mono_object(ptr->get_value_at_index(index), type); -} - -void godot_icall_Dictionary_Add(Dictionary *ptr, MonoObject *key, MonoObject *value) { - Variant varKey = GDMonoMarshal::mono_object_to_variant(key); - Variant *ret = ptr->getptr(varKey); - if (ret != nullptr) { - GDMonoUtils::set_pending_exception(mono_get_exception_argument("key", "An element with the same key already exists")); - return; - } - ptr->operator[](varKey) = GDMonoMarshal::mono_object_to_variant(value); -} - -void godot_icall_Dictionary_Clear(Dictionary *ptr) { - ptr->clear(); -} - -MonoBoolean godot_icall_Dictionary_Contains(Dictionary *ptr, MonoObject *key, MonoObject *value) { - // no dupes - Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - return ret != nullptr && *ret == GDMonoMarshal::mono_object_to_variant(value); -} - -MonoBoolean godot_icall_Dictionary_ContainsKey(Dictionary *ptr, MonoObject *key) { - return ptr->has(GDMonoMarshal::mono_object_to_variant(key)); -} - -Dictionary *godot_icall_Dictionary_Duplicate(Dictionary *ptr, MonoBoolean deep) { - return memnew(Dictionary(ptr->duplicate(deep))); -} - -MonoBoolean godot_icall_Dictionary_RemoveKey(Dictionary *ptr, MonoObject *key) { - return ptr->erase(GDMonoMarshal::mono_object_to_variant(key)); -} - -MonoBoolean godot_icall_Dictionary_Remove(Dictionary *ptr, MonoObject *key, MonoObject *value) { - Variant varKey = GDMonoMarshal::mono_object_to_variant(key); - - // no dupes - Variant *ret = ptr->getptr(varKey); - if (ret != nullptr && *ret == GDMonoMarshal::mono_object_to_variant(value)) { - ptr->erase(varKey); - return true; - } - - return false; -} - -MonoBoolean godot_icall_Dictionary_TryGetValue(Dictionary *ptr, MonoObject *key, MonoObject **value) { - Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == nullptr) { - *value = nullptr; - return false; - } - *value = GDMonoMarshal::variant_to_mono_object(ret); - return true; -} - -MonoBoolean godot_icall_Dictionary_TryGetValue_Generic(Dictionary *ptr, MonoObject *key, MonoObject **value, uint32_t type_encoding, GDMonoClass *type_class) { - Variant *ret = ptr->getptr(GDMonoMarshal::mono_object_to_variant(key)); - if (ret == nullptr) { - *value = nullptr; - return false; - } - *value = GDMonoMarshal::variant_to_mono_object(ret, ManagedType(type_encoding, type_class)); - return true; -} - -void godot_icall_Dictionary_Generic_GetValueTypeInfo(MonoReflectionType *refltype, uint32_t *type_encoding, GDMonoClass **type_class) { - MonoType *value_type = mono_reflection_type_get_type(refltype); - - *type_encoding = mono_type_get_type(value_type); - MonoClass *type_class_raw = mono_class_from_mono_type(value_type); - *type_class = GDMono::get_singleton()->get_class(type_class_raw); -} - -MonoString *godot_icall_Dictionary_ToString(Dictionary *ptr) { - return GDMonoMarshal::mono_string_from_godot(Variant(*ptr).operator String()); -} - -void godot_register_collections_icalls() { - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor", godot_icall_Array_Ctor); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Ctor_MonoArray", godot_icall_Array_Ctor_MonoArray); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Dtor", godot_icall_Array_Dtor); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_At", godot_icall_Array_At); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_At_Generic", godot_icall_Array_At_Generic); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_SetAt", godot_icall_Array_SetAt); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Count", godot_icall_Array_Count); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Add", godot_icall_Array_Add); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Clear", godot_icall_Array_Clear); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Concatenate", godot_icall_Array_Concatenate); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Contains", godot_icall_Array_Contains); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_CopyTo", godot_icall_Array_CopyTo); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Duplicate", godot_icall_Array_Duplicate); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_IndexOf", godot_icall_Array_IndexOf); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Insert", godot_icall_Array_Insert); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Remove", godot_icall_Array_Remove); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_RemoveAt", godot_icall_Array_RemoveAt); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Resize", godot_icall_Array_Resize); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Shuffle", godot_icall_Array_Shuffle); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_Generic_GetElementTypeInfo", godot_icall_Array_Generic_GetElementTypeInfo); - GDMonoUtils::add_internal_call("Godot.Collections.Array::godot_icall_Array_ToString", godot_icall_Array_ToString); - - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Ctor", godot_icall_Dictionary_Ctor); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Dtor", godot_icall_Dictionary_Dtor); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue", godot_icall_Dictionary_GetValue); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_GetValue_Generic", godot_icall_Dictionary_GetValue_Generic); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_SetValue", godot_icall_Dictionary_SetValue); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Keys", godot_icall_Dictionary_Keys); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Values", godot_icall_Dictionary_Values); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Count", godot_icall_Dictionary_Count); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_KeyValuePairs", godot_icall_Dictionary_KeyValuePairs); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_KeyValuePairAt", godot_icall_Dictionary_KeyValuePairAt); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_KeyValuePairAt_Generic", godot_icall_Dictionary_KeyValuePairAt_Generic); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Add", godot_icall_Dictionary_Add); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Clear", godot_icall_Dictionary_Clear); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Contains", godot_icall_Dictionary_Contains); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ContainsKey", godot_icall_Dictionary_ContainsKey); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Duplicate", godot_icall_Dictionary_Duplicate); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_RemoveKey", godot_icall_Dictionary_RemoveKey); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Remove", godot_icall_Dictionary_Remove); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue", godot_icall_Dictionary_TryGetValue); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_TryGetValue_Generic", godot_icall_Dictionary_TryGetValue_Generic); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_Generic_GetValueTypeInfo", godot_icall_Dictionary_Generic_GetValueTypeInfo); - GDMonoUtils::add_internal_call("Godot.Collections.Dictionary::godot_icall_Dictionary_ToString", godot_icall_Dictionary_ToString); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/gd_glue.cpp b/modules/mono/glue/gd_glue.cpp deleted file mode 100644 index 8b1c2b729e..0000000000 --- a/modules/mono/glue/gd_glue.cpp +++ /dev/null @@ -1,348 +0,0 @@ -/*************************************************************************/ -/* gd_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/io/marshalls.h" -#include "core/os/os.h" -#include "core/string/ustring.h" -#include "core/variant/array.h" -#include "core/variant/variant.h" -#include "core/variant/variant_parser.h" - -#include "../mono_gd/gd_mono_cache.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../mono_gd/gd_mono_utils.h" - -MonoObject *godot_icall_GD_bytes2var(MonoArray *p_bytes, MonoBoolean p_allow_objects) { - Variant ret; - PackedByteArray varr = GDMonoMarshal::mono_array_to_PackedByteArray(p_bytes); - Error err = decode_variant(ret, varr.ptr(), varr.size(), nullptr, p_allow_objects); - if (err != OK) { - ret = RTR("Not enough bytes for decoding bytes, or invalid format."); - } - return GDMonoMarshal::variant_to_mono_object(ret); -} - -MonoObject *godot_icall_GD_convert(MonoObject *p_what, int32_t p_type) { - Variant what = GDMonoMarshal::mono_object_to_variant(p_what); - const Variant *args[1] = { &what }; - Callable::CallError ce; - Variant ret; - Variant::construct(Variant::Type(p_type), ret, args, 1, ce); - ERR_FAIL_COND_V(ce.error != Callable::CallError::CALL_OK, nullptr); - return GDMonoMarshal::variant_to_mono_object(ret); -} - -int godot_icall_GD_hash(MonoObject *p_var) { - return GDMonoMarshal::mono_object_to_variant(p_var).hash(); -} - -MonoObject *godot_icall_GD_instance_from_id(uint64_t p_instance_id) { - return GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(ObjectID(p_instance_id))); -} - -void godot_icall_GD_print(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - str += elem_str; - } - - print_line(str); -} - -void godot_icall_GD_print_rich(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - str += elem_str; - } - - print_line_rich(str); -} - -void godot_icall_GD_printerr(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - str += elem_str; - } - - print_error(str); -} - -void godot_icall_GD_printraw(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - str += elem_str; - } - - OS::get_singleton()->print("%s", str.utf8().get_data()); -} - -void godot_icall_GD_prints(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - if (i) { - str += " "; - } - - str += elem_str; - } - - print_line(str); -} - -void godot_icall_GD_printt(MonoArray *p_what) { - String str; - int length = mono_array_length(p_what); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_what, MonoObject *, i); - - MonoException *exc = nullptr; - String elem_str = GDMonoMarshal::mono_object_to_variant_string(elem, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - return; - } - - if (i) { - str += "\t"; - } - - str += elem_str; - } - - print_line(str); -} - -void godot_icall_GD_randomize() { - Math::randomize(); -} - -uint32_t godot_icall_GD_randi() { - return Math::rand(); -} - -float godot_icall_GD_randf() { - return Math::randf(); -} - -int32_t godot_icall_GD_randi_range(int32_t from, int32_t to) { - return Math::random(from, to); -} - -double godot_icall_GD_randf_range(double from, double to) { - return Math::random(from, to); -} - -double godot_icall_GD_randfn(double mean, double deviation) { - return Math::randfn(mean, deviation); -} - -uint32_t godot_icall_GD_rand_seed(uint64_t seed, uint64_t *newSeed) { - uint32_t ret = Math::rand_from_seed(&seed); - *newSeed = seed; - return ret; -} - -void godot_icall_GD_seed(uint64_t p_seed) { - Math::seed(p_seed); -} - -MonoString *godot_icall_GD_str(MonoArray *p_what) { - String str; - Array what = GDMonoMarshal::mono_array_to_Array(p_what); - - for (int i = 0; i < what.size(); i++) { - String os = what[i].operator String(); - - if (i == 0) { - str = os; - } else { - str += os; - } - } - - return GDMonoMarshal::mono_string_from_godot(str); -} - -MonoObject *godot_icall_GD_str2var(MonoString *p_str) { - Variant ret; - - VariantParser::StreamString ss; - ss.s = GDMonoMarshal::mono_string_to_godot(p_str); - - String errs; - int line; - Error err = VariantParser::parse(&ss, ret, errs, line); - if (err != OK) { - String err_str = "Parse error at line " + itos(line) + ": " + errs + "."; - ERR_PRINT(err_str); - ret = err_str; - } - - return GDMonoMarshal::variant_to_mono_object(ret); -} - -MonoBoolean godot_icall_GD_type_exists(StringName *p_type) { - StringName type = p_type ? *p_type : StringName(); - return ClassDB::class_exists(type); -} - -void godot_icall_GD_pusherror(MonoString *p_str) { - ERR_PRINT(GDMonoMarshal::mono_string_to_godot(p_str)); -} - -void godot_icall_GD_pushwarning(MonoString *p_str) { - WARN_PRINT(GDMonoMarshal::mono_string_to_godot(p_str)); -} - -MonoArray *godot_icall_GD_var2bytes(MonoObject *p_var, MonoBoolean p_full_objects) { - Variant var = GDMonoMarshal::mono_object_to_variant(p_var); - - PackedByteArray barr; - int len; - Error err = encode_variant(var, nullptr, len, p_full_objects); - ERR_FAIL_COND_V_MSG(err != OK, nullptr, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); - - barr.resize(len); - encode_variant(var, barr.ptrw(), len, p_full_objects); - - return GDMonoMarshal::PackedByteArray_to_mono_array(barr); -} - -MonoString *godot_icall_GD_var2str(MonoObject *p_var) { - String vars; - VariantWriter::write_to_string(GDMonoMarshal::mono_object_to_variant(p_var), vars); - return GDMonoMarshal::mono_string_from_godot(vars); -} - -uint32_t godot_icall_TypeToVariantType(MonoReflectionType *p_refl_type) { - return (uint32_t)GDMonoMarshal::managed_to_variant_type(ManagedType::from_reftype(p_refl_type)); -} - -MonoObject *godot_icall_DefaultGodotTaskScheduler() { - return GDMonoCache::cached_data.task_scheduler_handle->get_target(); -} - -void godot_register_gd_icalls() { - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_bytes2var", godot_icall_GD_bytes2var); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_convert", godot_icall_GD_convert); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_hash", godot_icall_GD_hash); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_instance_from_id", godot_icall_GD_instance_from_id); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pusherror", godot_icall_GD_pusherror); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_pushwarning", godot_icall_GD_pushwarning); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_print", godot_icall_GD_print); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_print_rich", godot_icall_GD_print_rich); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printerr", godot_icall_GD_printerr); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printraw", godot_icall_GD_printraw); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_prints", godot_icall_GD_prints); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_printt", godot_icall_GD_printt); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randomize", godot_icall_GD_randomize); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randi", godot_icall_GD_randi); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randf", godot_icall_GD_randf); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randi_range", godot_icall_GD_randi_range); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randf_range", godot_icall_GD_randf_range); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_randfn", godot_icall_GD_randfn); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_rand_seed", godot_icall_GD_rand_seed); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_seed", godot_icall_GD_seed); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_str", godot_icall_GD_str); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_str2var", godot_icall_GD_str2var); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_type_exists", godot_icall_GD_type_exists); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_var2bytes", godot_icall_GD_var2bytes); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_GD_var2str", godot_icall_GD_var2str); - GDMonoUtils::add_internal_call("Godot.GD::godot_icall_TypeToVariantType", godot_icall_TypeToVariantType); - - // Dispatcher - GDMonoUtils::add_internal_call("Godot.Dispatcher::godot_icall_DefaultGodotTaskScheduler", godot_icall_DefaultGodotTaskScheduler); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/glue_header.h b/modules/mono/glue/glue_header.h deleted file mode 100644 index f9ad1a9893..0000000000 --- a/modules/mono/glue/glue_header.h +++ /dev/null @@ -1,91 +0,0 @@ -/*************************************************************************/ -/* glue_header.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GLUE_HEADER_H -#define GLUE_HEADER_H - -#ifdef MONO_GLUE_ENABLED - -#include "../mono_gd/gd_mono_marshal.h" - -void godot_register_collections_icalls(); -void godot_register_gd_icalls(); -void godot_register_string_name_icalls(); -void godot_register_nodepath_icalls(); -void godot_register_callable_icalls(); -void godot_register_object_icalls(); -void godot_register_rid_icalls(); -void godot_register_string_icalls(); -void godot_register_scene_tree_icalls(); - -/** - * Registers internal calls that were not generated. This function is called - * from the generated GodotSharpBindings::register_generated_icalls() function. - */ -void godot_register_glue_header_icalls() { - godot_register_collections_icalls(); - godot_register_gd_icalls(); - godot_register_string_name_icalls(); - godot_register_nodepath_icalls(); - godot_register_callable_icalls(); - godot_register_object_icalls(); - godot_register_rid_icalls(); - godot_register_string_icalls(); - godot_register_scene_tree_icalls(); -} - -// Used by the generated glue - -#include "core/config/engine.h" -#include "core/object/class_db.h" -#include "core/object/method_bind.h" -#include "core/object/ref_counted.h" -#include "core/string/node_path.h" -#include "core/string/ustring.h" -#include "core/typedefs.h" -#include "core/variant/array.h" -#include "core/variant/dictionary.h" - -#include "../mono_gd/gd_mono_class.h" -#include "../mono_gd/gd_mono_internals.h" -#include "../mono_gd/gd_mono_utils.h" - -#define GODOTSHARP_INSTANCE_OBJECT(m_instance, m_type) \ - static ClassDB::ClassInfo *ci = nullptr; \ - if (!ci) { \ - ci = ClassDB::classes.getptr(m_type); \ - } \ - Object *m_instance = ci->creation_func(); - -#include "arguments_vector.h" - -#endif // MONO_GLUE_ENABLED - -#endif // GLUE_HEADER_H diff --git a/modules/mono/glue/nodepath_glue.cpp b/modules/mono/glue/nodepath_glue.cpp deleted file mode 100644 index 16e1509eb0..0000000000 --- a/modules/mono/glue/nodepath_glue.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/*************************************************************************/ -/* nodepath_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/string/node_path.h" -#include "core/string/ustring.h" - -#include "../mono_gd/gd_mono_marshal.h" - -NodePath *godot_icall_NodePath_Ctor(MonoString *p_path) { - return memnew(NodePath(GDMonoMarshal::mono_string_to_godot(p_path))); -} - -void godot_icall_NodePath_Dtor(NodePath *p_ptr) { - ERR_FAIL_NULL(p_ptr); - memdelete(p_ptr); -} - -MonoString *godot_icall_NodePath_operator_String(NodePath *p_np) { - return GDMonoMarshal::mono_string_from_godot(p_np->operator String()); -} - -MonoBoolean godot_icall_NodePath_is_absolute(NodePath *p_ptr) { - return (MonoBoolean)p_ptr->is_absolute(); -} - -int32_t godot_icall_NodePath_get_name_count(NodePath *p_ptr) { - return p_ptr->get_name_count(); -} - -MonoString *godot_icall_NodePath_get_name(NodePath *p_ptr, uint32_t p_idx) { - return GDMonoMarshal::mono_string_from_godot(p_ptr->get_name(p_idx)); -} - -int32_t godot_icall_NodePath_get_subname_count(NodePath *p_ptr) { - return p_ptr->get_subname_count(); -} - -MonoString *godot_icall_NodePath_get_subname(NodePath *p_ptr, uint32_t p_idx) { - return GDMonoMarshal::mono_string_from_godot(p_ptr->get_subname(p_idx)); -} - -MonoString *godot_icall_NodePath_get_concatenated_names(NodePath *p_ptr) { - return GDMonoMarshal::mono_string_from_godot(p_ptr->get_concatenated_names()); -} - -MonoString *godot_icall_NodePath_get_concatenated_subnames(NodePath *p_ptr) { - return GDMonoMarshal::mono_string_from_godot(p_ptr->get_concatenated_subnames()); -} - -NodePath *godot_icall_NodePath_get_as_property_path(NodePath *p_ptr) { - return memnew(NodePath(p_ptr->get_as_property_path())); -} - -MonoBoolean godot_icall_NodePath_is_empty(NodePath *p_ptr) { - return (MonoBoolean)p_ptr->is_empty(); -} - -void godot_register_nodepath_icalls() { - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_Ctor", godot_icall_NodePath_Ctor); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_Dtor", godot_icall_NodePath_Dtor); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_operator_String", godot_icall_NodePath_operator_String); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_as_property_path", godot_icall_NodePath_get_as_property_path); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_names", godot_icall_NodePath_get_concatenated_names); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_concatenated_subnames", godot_icall_NodePath_get_concatenated_subnames); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name", godot_icall_NodePath_get_name); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_name_count", godot_icall_NodePath_get_name_count); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname", godot_icall_NodePath_get_subname); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_get_subname_count", godot_icall_NodePath_get_subname_count); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_is_absolute", godot_icall_NodePath_is_absolute); - GDMonoUtils::add_internal_call("Godot.NodePath::godot_icall_NodePath_is_empty", godot_icall_NodePath_is_empty); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/rid_glue.cpp b/modules/mono/glue/rid_glue.cpp deleted file mode 100644 index 3e09564539..0000000000 --- a/modules/mono/glue/rid_glue.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/*************************************************************************/ -/* rid_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/io/resource.h" -#include "core/object/class_db.h" -#include "core/templates/rid.h" - -#include "../mono_gd/gd_mono_marshal.h" - -RID *godot_icall_RID_Ctor(Object *p_from) { - Resource *res_from = Object::cast_to<Resource>(p_from); - - if (res_from) { - return memnew(RID(res_from->get_rid())); - } - - return memnew(RID); -} - -void godot_icall_RID_Dtor(RID *p_ptr) { - ERR_FAIL_NULL(p_ptr); - memdelete(p_ptr); -} - -uint32_t godot_icall_RID_get_id(RID *p_ptr) { - return p_ptr->get_id(); -} - -void godot_register_rid_icalls() { - GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_Ctor", godot_icall_RID_Ctor); - GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_Dtor", godot_icall_RID_Dtor); - GDMonoUtils::add_internal_call("Godot.RID::godot_icall_RID_get_id", godot_icall_RID_get_id); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp new file mode 100644 index 0000000000..13d4395a64 --- /dev/null +++ b/modules/mono/glue/runtime_interop.cpp @@ -0,0 +1,1513 @@ +/*************************************************************************/ +/* runtime_interop.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "runtime_interop.h" + +#include "core/config/engine.h" +#include "core/debugger/engine_debugger.h" +#include "core/debugger/script_debugger.h" +#include "core/io/marshalls.h" +#include "core/object/class_db.h" +#include "core/object/method_bind.h" +#include "core/os/os.h" +#include "core/string/string_name.h" + +#include "../interop_types.h" + +#include "modules/mono/csharp_script.h" +#include "modules/mono/managed_callable.h" +#include "modules/mono/mono_gd/gd_mono_cache.h" +#include "modules/mono/signal_awaiter_utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// For ArrayPrivate and DictionaryPrivate +static_assert(sizeof(SafeRefCount) == sizeof(uint32_t)); + +typedef Object *(*godotsharp_class_creation_func)(); + +MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, const StringName *p_methodname) { + return ClassDB::get_method(*p_classname, *p_methodname); +} + +godotsharp_class_creation_func godotsharp_get_class_constructor(const StringName *p_classname) { + ClassDB::ClassInfo *class_info = ClassDB::classes.getptr(*p_classname); + if (class_info) { + return class_info->creation_func; + } + return nullptr; +} + +Object *godotsharp_engine_get_singleton(const String *p_name) { + return Engine::get_singleton()->get_singleton_object(*p_name); +} + +int32_t godotsharp_stack_info_vector_resize( + Vector<ScriptLanguage::StackInfo> *p_stack_info_vector, int p_size) { + return (int32_t)p_stack_info_vector->resize(p_size); +} + +void godotsharp_stack_info_vector_destroy( + Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) { + p_stack_info_vector->~Vector(); +} + +void godotsharp_internal_script_debugger_send_error(const String *p_func, + const String *p_file, int32_t p_line, const String *p_err, const String *p_descr, + bool p_warning, const Vector<ScriptLanguage::StackInfo> *p_stack_info_vector) { + EngineDebugger::get_script_debugger()->send_error(*p_func, *p_file, p_line, *p_err, *p_descr, + true, p_warning ? ERR_HANDLER_WARNING : ERR_HANDLER_ERROR, *p_stack_info_vector); +} + +bool godotsharp_internal_script_debugger_is_active() { + return EngineDebugger::is_active(); +} + +GCHandleIntPtr godotsharp_internal_object_get_associated_gchandle(Object *p_ptr) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_ptr == nullptr); +#endif + + if (p_ptr->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); + if (cs_instance) { + if (!cs_instance->is_destructing_script_instance()) { + return cs_instance->get_gchandle_intptr(); + } + return { nullptr }; + } + } + + void *data = CSharpLanguage::get_existing_instance_binding(p_ptr); + + if (data) { + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); + if (script_binding.inited) { + MonoGCHandleData &gchandle = script_binding.gchandle; + return !gchandle.is_released() ? gchandle.get_intptr() : GCHandleIntPtr{ nullptr }; + } + } + + return { nullptr }; +} + +void godotsharp_internal_object_disposed(Object *p_ptr, GCHandleIntPtr p_gchandle_to_free) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_ptr == nullptr); +#endif + + if (p_ptr->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_ptr->get_script_instance()); + if (cs_instance) { + if (!cs_instance->is_destructing_script_instance()) { + cs_instance->mono_object_disposed(p_gchandle_to_free); + p_ptr->set_script_instance(nullptr); + } + return; + } + } + + void *data = CSharpLanguage::get_existing_instance_binding(p_ptr); + + if (data) { + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); + if (script_binding.inited) { + if (!script_binding.gchandle.is_released()) { + CSharpLanguage::release_binding_gchandle_thread_safe(p_gchandle_to_free, script_binding); + } + } + } +} + +void godotsharp_internal_refcounted_disposed(Object *p_ptr, GCHandleIntPtr p_gchandle_to_free, bool p_is_finalizer) { +#ifdef DEBUG_ENABLED + CRASH_COND(p_ptr == nullptr); + // This is only called with RefCounted derived classes + CRASH_COND(!Object::cast_to<RefCounted>(p_ptr)); +#endif + + RefCounted *rc = static_cast<RefCounted *>(p_ptr); + + if (rc->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(rc->get_script_instance()); + if (cs_instance) { + if (!cs_instance->is_destructing_script_instance()) { + bool delete_owner; + bool remove_script_instance; + + cs_instance->mono_object_disposed_baseref(p_gchandle_to_free, p_is_finalizer, + delete_owner, remove_script_instance); + + if (delete_owner) { + memdelete(rc); + } else if (remove_script_instance) { + rc->set_script_instance(nullptr); + } + } + return; + } + } + + // Unsafe refcount decrement. The managed instance also counts as a reference. + // See: CSharpLanguage::alloc_instance_binding_data(Object *p_object) + CSharpLanguage::get_singleton()->pre_unsafe_unreference(rc); + if (rc->unreference()) { + memdelete(rc); + } else { + void *data = CSharpLanguage::get_existing_instance_binding(rc); + + if (data) { + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->get(); + if (script_binding.inited) { + if (!script_binding.gchandle.is_released()) { + CSharpLanguage::release_binding_gchandle_thread_safe(p_gchandle_to_free, script_binding); + } + } + } + } +} + +int32_t godotsharp_internal_signal_awaiter_connect(Object *p_source, StringName *p_signal, Object *p_target, GCHandleIntPtr p_awaiter_handle_ptr) { + StringName signal = p_signal ? *p_signal : StringName(); + return (int32_t)gd_mono_connect_signal_awaiter(p_source, signal, p_target, p_awaiter_handle_ptr); +} + +GCHandleIntPtr godotsharp_internal_unmanaged_get_script_instance_managed(Object *p_unmanaged, bool *r_has_cs_script_instance) { +#ifdef DEBUG_ENABLED + CRASH_COND(!p_unmanaged); + CRASH_COND(!r_has_cs_script_instance); +#endif + + if (p_unmanaged->get_script_instance()) { + CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(p_unmanaged->get_script_instance()); + + if (cs_instance) { + *r_has_cs_script_instance = true; + return cs_instance->get_gchandle_intptr(); + } + } + + *r_has_cs_script_instance = false; + return { nullptr }; +} + +GCHandleIntPtr godotsharp_internal_unmanaged_get_instance_binding_managed(Object *p_unmanaged) { +#ifdef DEBUG_ENABLED + CRASH_COND(!p_unmanaged); +#endif + + void *data = CSharpLanguage::get_instance_binding(p_unmanaged); + ERR_FAIL_NULL_V(data, { nullptr }); + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->value(); + ERR_FAIL_COND_V(!script_binding.inited, { nullptr }); + + return script_binding.gchandle.get_intptr(); +} + +GCHandleIntPtr godotsharp_internal_unmanaged_instance_binding_create_managed(Object *p_unmanaged, GCHandleIntPtr p_old_gchandle) { +#ifdef DEBUG_ENABLED + CRASH_COND(!p_unmanaged); +#endif + + void *data = CSharpLanguage::get_instance_binding(p_unmanaged); + ERR_FAIL_NULL_V(data, { nullptr }); + CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->value(); + ERR_FAIL_COND_V(!script_binding.inited, { nullptr }); + + MonoGCHandleData &gchandle = script_binding.gchandle; + + // TODO: Possible data race? + CRASH_COND(gchandle.get_intptr().value != p_old_gchandle.value); + + CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); + script_binding.inited = false; + + // Create a new one + +#ifdef DEBUG_ENABLED + CRASH_COND(script_binding.type_name == StringName()); +#endif + + bool parent_is_object_class = ClassDB::is_parent_class(p_unmanaged->get_class_name(), script_binding.type_name); + ERR_FAIL_COND_V_MSG(!parent_is_object_class, { nullptr }, + "Type inherits from native type '" + script_binding.type_name + "', so it can't be instantiated in object of type: '" + p_unmanaged->get_class() + "'."); + + GCHandleIntPtr strong_gchandle = + GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding( + &script_binding.type_name, p_unmanaged); + + ERR_FAIL_NULL_V(strong_gchandle.value, { nullptr }); + + gchandle = MonoGCHandleData(strong_gchandle, gdmono::GCHandleType::STRONG_HANDLE); + script_binding.inited = true; + + // Tie managed to unmanaged + RefCounted *rc = Object::cast_to<RefCounted>(p_unmanaged); + + if (rc) { + // Unsafe refcount increment. The managed instance also counts as a reference. + // This way if the unmanaged world has no references to our owner + // but the managed instance is alive, the refcount will be 1 instead of 0. + // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) + rc->reference(); + CSharpLanguage::get_singleton()->post_unsafe_reference(rc); + } + + return gchandle.get_intptr(); +} + +void godotsharp_internal_tie_native_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, const StringName *p_native_name, bool p_ref_counted) { + CSharpLanguage::tie_native_managed_to_unmanaged(p_gchandle_intptr, p_unmanaged, p_native_name, p_ref_counted); +} + +void godotsharp_internal_tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged, Ref<CSharpScript> *p_script, bool p_ref_counted) { + CSharpLanguage::tie_user_managed_to_unmanaged(p_gchandle_intptr, p_unmanaged, p_script, p_ref_counted); +} + +void godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup(GCHandleIntPtr p_gchandle_intptr, Object *p_unmanaged) { + CSharpLanguage::tie_managed_to_unmanaged_with_pre_setup(p_gchandle_intptr, p_unmanaged); +} + +void godotsharp_internal_new_csharp_script(Ref<CSharpScript> *r_dest) { + memnew_placement(r_dest, Ref<CSharpScript>(memnew(CSharpScript))); +} + +bool godotsharp_internal_script_load(const String *p_path, Ref<CSharpScript> *r_dest) { + Ref<Resource> res = ResourceLoader::load(*p_path); + if (res.is_valid()) { + memnew_placement(r_dest, Ref<CSharpScript>(res)); + return true; + } else { + memnew_placement(r_dest, Ref<CSharpScript>()); + return false; + } +} + +void godotsharp_internal_reload_registered_script(CSharpScript *p_script) { + CRASH_COND(!p_script); + CSharpScript::reload_registered_script(Ref<CSharpScript>(p_script)); +} + +void godotsharp_array_filter_godot_objects_by_native(StringName *p_native_name, const Array *p_input, Array *r_output) { + memnew_placement(r_output, Array); + + for (int i = 0; i < p_input->size(); ++i) { + if (ClassDB::is_parent_class(((Object *)(*p_input)[i])->get_class(), *p_native_name)) { + r_output->push_back(p_input[i]); + } + } +} + +void godotsharp_array_filter_godot_objects_by_non_native(const Array *p_input, Array *r_output) { + memnew_placement(r_output, Array); + + for (int i = 0; i < p_input->size(); ++i) { + CSharpInstance *si = CAST_CSHARP_INSTANCE(((Object *)(*p_input)[i])->get_script_instance()); + + if (si != nullptr) { + r_output->push_back(p_input[i]); + } + } +} + +void godotsharp_ref_new_from_ref_counted_ptr(Ref<RefCounted> *r_dest, RefCounted *p_ref_counted_ptr) { + memnew_placement(r_dest, Ref<RefCounted>(p_ref_counted_ptr)); +} + +void godotsharp_ref_destroy(Ref<RefCounted> *p_instance) { + p_instance->~Ref(); +} + +void godotsharp_string_name_new_from_string(StringName *r_dest, const String *p_name) { + memnew_placement(r_dest, StringName(*p_name)); +} + +void godotsharp_node_path_new_from_string(NodePath *r_dest, const String *p_name) { + memnew_placement(r_dest, NodePath(*p_name)); +} + +void godotsharp_string_name_as_string(String *r_dest, const StringName *p_name) { + memnew_placement(r_dest, String(p_name->operator String())); +} + +void godotsharp_node_path_as_string(String *r_dest, const NodePath *p_np) { + memnew_placement(r_dest, String(p_np->operator String())); +} + +godot_packed_array godotsharp_packed_byte_array_new_mem_copy(const uint8_t *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedByteArray); + PackedByteArray *array = reinterpret_cast<PackedByteArray *>(&ret); + array->resize(p_length); + uint8_t *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(uint8_t)); + return ret; +} + +godot_packed_array godotsharp_packed_int32_array_new_mem_copy(const int32_t *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedInt32Array); + PackedInt32Array *array = reinterpret_cast<PackedInt32Array *>(&ret); + array->resize(p_length); + int32_t *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(int32_t)); + return ret; +} + +godot_packed_array godotsharp_packed_int64_array_new_mem_copy(const int64_t *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedInt64Array); + PackedInt64Array *array = reinterpret_cast<PackedInt64Array *>(&ret); + array->resize(p_length); + int64_t *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(int64_t)); + return ret; +} + +godot_packed_array godotsharp_packed_float32_array_new_mem_copy(const float *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedFloat32Array); + PackedFloat32Array *array = reinterpret_cast<PackedFloat32Array *>(&ret); + array->resize(p_length); + float *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(float)); + return ret; +} + +godot_packed_array godotsharp_packed_float64_array_new_mem_copy(const double *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedFloat64Array); + PackedFloat64Array *array = reinterpret_cast<PackedFloat64Array *>(&ret); + array->resize(p_length); + double *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(double)); + return ret; +} + +godot_packed_array godotsharp_packed_vector2_array_new_mem_copy(const Vector2 *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedVector2Array); + PackedVector2Array *array = reinterpret_cast<PackedVector2Array *>(&ret); + array->resize(p_length); + Vector2 *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(Vector2)); + return ret; +} + +godot_packed_array godotsharp_packed_vector3_array_new_mem_copy(const Vector3 *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedVector3Array); + PackedVector3Array *array = reinterpret_cast<PackedVector3Array *>(&ret); + array->resize(p_length); + Vector3 *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(Vector3)); + return ret; +} + +godot_packed_array godotsharp_packed_color_array_new_mem_copy(const Color *p_src, int32_t p_length) { + godot_packed_array ret; + memnew_placement(&ret, PackedColorArray); + PackedColorArray *array = reinterpret_cast<PackedColorArray *>(&ret); + array->resize(p_length); + Color *dst = array->ptrw(); + memcpy(dst, p_src, p_length * sizeof(Color)); + return ret; +} + +void godotsharp_packed_string_array_add(PackedStringArray *r_dest, const String *p_element) { + r_dest->append(*p_element); +} + +void godotsharp_callable_new_with_delegate(GCHandleIntPtr p_delegate_handle, Callable *r_callable) { + // TODO: Use pooling for ManagedCallable instances. + CallableCustom *managed_callable = memnew(ManagedCallable(p_delegate_handle)); + memnew_placement(r_callable, Callable(managed_callable)); +} + +bool godotsharp_callable_get_data_for_marshalling(const Callable *p_callable, + GCHandleIntPtr *r_delegate_handle, Object **r_object, StringName *r_name) { + if (p_callable->is_custom()) { + CallableCustom *custom = p_callable->get_custom(); + CallableCustom::CompareEqualFunc compare_equal_func = custom->get_compare_equal_func(); + + if (compare_equal_func == ManagedCallable::compare_equal_func_ptr) { + ManagedCallable *managed_callable = static_cast<ManagedCallable *>(custom); + *r_delegate_handle = managed_callable->get_delegate(); + *r_object = nullptr; + memnew_placement(r_name, StringName()); + return true; + } else if (compare_equal_func == SignalAwaiterCallable::compare_equal_func_ptr) { + SignalAwaiterCallable *signal_awaiter_callable = static_cast<SignalAwaiterCallable *>(custom); + *r_delegate_handle = { nullptr }; + *r_object = ObjectDB::get_instance(signal_awaiter_callable->get_object()); + memnew_placement(r_name, StringName(signal_awaiter_callable->get_signal())); + return true; + } else if (compare_equal_func == EventSignalCallable::compare_equal_func_ptr) { + EventSignalCallable *event_signal_callable = static_cast<EventSignalCallable *>(custom); + *r_delegate_handle = { nullptr }; + *r_object = ObjectDB::get_instance(event_signal_callable->get_object()); + memnew_placement(r_name, StringName(event_signal_callable->get_signal())); + return true; + } + + // Some other CallableCustom. We only support ManagedCallable. + *r_delegate_handle = { nullptr }; + *r_object = nullptr; + memnew_placement(r_name, StringName()); + return false; + } else { + *r_delegate_handle = { nullptr }; + *r_object = ObjectDB::get_instance(p_callable->get_object_id()); + memnew_placement(r_name, StringName(p_callable->get_method())); + return true; + } +} + +godot_variant godotsharp_callable_call(Callable *p_callable, const Variant **p_args, const int32_t p_arg_count, Callable::CallError *p_call_error) { + godot_variant ret; + memnew_placement(&ret, Variant); + + Variant *ret_val = (Variant *)&ret; + + p_callable->callp(p_args, p_arg_count, *ret_val, *p_call_error); + + return ret; +} + +void godotsharp_callable_call_deferred(Callable *p_callable, const Variant **p_args, const int32_t p_arg_count) { + p_callable->call_deferredp(p_args, p_arg_count); +} + +// GDNative functions + +// gdnative.h + +void godotsharp_method_bind_ptrcall(MethodBind *p_method_bind, Object *p_instance, const void **p_args, void *p_ret) { + p_method_bind->ptrcall(p_instance, p_args, p_ret); +} + +godot_variant godotsharp_method_bind_call(MethodBind *p_method_bind, Object *p_instance, const godot_variant **p_args, const int32_t p_arg_count, Callable::CallError *p_call_error) { + godot_variant ret; + memnew_placement(&ret, Variant()); + + Variant *ret_val = (Variant *)&ret; + + *ret_val = p_method_bind->call(p_instance, (const Variant **)p_args, p_arg_count, *p_call_error); + + return ret; +} + +// variant.h + +void godotsharp_variant_new_copy(godot_variant *r_dest, const Variant *p_src) { + memnew_placement(r_dest, Variant(*p_src)); +} + +void godotsharp_variant_new_string_name(godot_variant *r_dest, const StringName *p_s) { + memnew_placement(r_dest, Variant(*p_s)); +} + +void godotsharp_variant_new_node_path(godot_variant *r_dest, const NodePath *p_np) { + memnew_placement(r_dest, Variant(*p_np)); +} + +void godotsharp_variant_new_object(godot_variant *r_dest, const Object *p_obj) { + memnew_placement(r_dest, Variant(p_obj)); +} + +void godotsharp_variant_new_transform2d(godot_variant *r_dest, const Transform2D *p_t2d) { + memnew_placement(r_dest, Variant(*p_t2d)); +} + +void godotsharp_variant_new_vector4(godot_variant *r_dest, const Vector4 *p_vec4) { + memnew_placement(r_dest, Variant(*p_vec4)); +} + +void godotsharp_variant_new_vector4i(godot_variant *r_dest, const Vector4i *p_vec4i) { + memnew_placement(r_dest, Variant(*p_vec4i)); +} + +void godotsharp_variant_new_basis(godot_variant *r_dest, const Basis *p_basis) { + memnew_placement(r_dest, Variant(*p_basis)); +} + +void godotsharp_variant_new_transform3d(godot_variant *r_dest, const Transform3D *p_trans) { + memnew_placement(r_dest, Variant(*p_trans)); +} + +void godotsharp_variant_new_projection(godot_variant *r_dest, const Projection *p_proj) { + memnew_placement(r_dest, Variant(*p_proj)); +} + +void godotsharp_variant_new_aabb(godot_variant *r_dest, const AABB *p_aabb) { + memnew_placement(r_dest, Variant(*p_aabb)); +} + +void godotsharp_variant_new_dictionary(godot_variant *r_dest, const Dictionary *p_dict) { + memnew_placement(r_dest, Variant(*p_dict)); +} + +void godotsharp_variant_new_array(godot_variant *r_dest, const Array *p_arr) { + memnew_placement(r_dest, Variant(*p_arr)); +} + +void godotsharp_variant_new_packed_byte_array(godot_variant *r_dest, const PackedByteArray *p_pba) { + memnew_placement(r_dest, Variant(*p_pba)); +} + +void godotsharp_variant_new_packed_int32_array(godot_variant *r_dest, const PackedInt32Array *p_pia) { + memnew_placement(r_dest, Variant(*p_pia)); +} + +void godotsharp_variant_new_packed_int64_array(godot_variant *r_dest, const PackedInt64Array *p_pia) { + memnew_placement(r_dest, Variant(*p_pia)); +} + +void godotsharp_variant_new_packed_float32_array(godot_variant *r_dest, const PackedFloat32Array *p_pra) { + memnew_placement(r_dest, Variant(*p_pra)); +} + +void godotsharp_variant_new_packed_float64_array(godot_variant *r_dest, const PackedFloat64Array *p_pra) { + memnew_placement(r_dest, Variant(*p_pra)); +} + +void godotsharp_variant_new_packed_string_array(godot_variant *r_dest, const PackedStringArray *p_psa) { + memnew_placement(r_dest, Variant(*p_psa)); +} + +void godotsharp_variant_new_packed_vector2_array(godot_variant *r_dest, const PackedVector2Array *p_pv2a) { + memnew_placement(r_dest, Variant(*p_pv2a)); +} + +void godotsharp_variant_new_packed_vector3_array(godot_variant *r_dest, const PackedVector3Array *p_pv3a) { + memnew_placement(r_dest, Variant(*p_pv3a)); +} + +void godotsharp_variant_new_packed_color_array(godot_variant *r_dest, const PackedColorArray *p_pca) { + memnew_placement(r_dest, Variant(*p_pca)); +} + +bool godotsharp_variant_as_bool(const Variant *p_self) { + return p_self->operator bool(); +} + +int64_t godotsharp_variant_as_int(const Variant *p_self) { + return p_self->operator int64_t(); +} + +double godotsharp_variant_as_float(const Variant *p_self) { + return p_self->operator double(); +} + +godot_string godotsharp_variant_as_string(const Variant *p_self) { + godot_string raw_dest; + String *dest = (String *)&raw_dest; + memnew_placement(dest, String(p_self->operator String())); + return raw_dest; +} + +godot_vector2 godotsharp_variant_as_vector2(const Variant *p_self) { + godot_vector2 raw_dest; + Vector2 *dest = (Vector2 *)&raw_dest; + memnew_placement(dest, Vector2(p_self->operator Vector2())); + return raw_dest; +} + +godot_vector2i godotsharp_variant_as_vector2i(const Variant *p_self) { + godot_vector2i raw_dest; + Vector2i *dest = (Vector2i *)&raw_dest; + memnew_placement(dest, Vector2i(p_self->operator Vector2i())); + return raw_dest; +} + +godot_rect2 godotsharp_variant_as_rect2(const Variant *p_self) { + godot_rect2 raw_dest; + Rect2 *dest = (Rect2 *)&raw_dest; + memnew_placement(dest, Rect2(p_self->operator Rect2())); + return raw_dest; +} + +godot_rect2i godotsharp_variant_as_rect2i(const Variant *p_self) { + godot_rect2i raw_dest; + Rect2i *dest = (Rect2i *)&raw_dest; + memnew_placement(dest, Rect2i(p_self->operator Rect2i())); + return raw_dest; +} + +godot_vector3 godotsharp_variant_as_vector3(const Variant *p_self) { + godot_vector3 raw_dest; + Vector3 *dest = (Vector3 *)&raw_dest; + memnew_placement(dest, Vector3(p_self->operator Vector3())); + return raw_dest; +} + +godot_vector3i godotsharp_variant_as_vector3i(const Variant *p_self) { + godot_vector3i raw_dest; + Vector3i *dest = (Vector3i *)&raw_dest; + memnew_placement(dest, Vector3i(p_self->operator Vector3i())); + return raw_dest; +} + +godot_transform2d godotsharp_variant_as_transform2d(const Variant *p_self) { + godot_transform2d raw_dest; + Transform2D *dest = (Transform2D *)&raw_dest; + memnew_placement(dest, Transform2D(p_self->operator Transform2D())); + return raw_dest; +} + +godot_vector4 godotsharp_variant_as_vector4(const Variant *p_self) { + godot_vector4 raw_dest; + Vector4 *dest = (Vector4 *)&raw_dest; + memnew_placement(dest, Vector4(p_self->operator Vector4())); + return raw_dest; +} + +godot_vector4i godotsharp_variant_as_vector4i(const Variant *p_self) { + godot_vector4i raw_dest; + Vector4i *dest = (Vector4i *)&raw_dest; + memnew_placement(dest, Vector4i(p_self->operator Vector4i())); + return raw_dest; +} + +godot_plane godotsharp_variant_as_plane(const Variant *p_self) { + godot_plane raw_dest; + Plane *dest = (Plane *)&raw_dest; + memnew_placement(dest, Plane(p_self->operator Plane())); + return raw_dest; +} + +godot_quaternion godotsharp_variant_as_quaternion(const Variant *p_self) { + godot_quaternion raw_dest; + Quaternion *dest = (Quaternion *)&raw_dest; + memnew_placement(dest, Quaternion(p_self->operator Quaternion())); + return raw_dest; +} + +godot_aabb godotsharp_variant_as_aabb(const Variant *p_self) { + godot_aabb raw_dest; + AABB *dest = (AABB *)&raw_dest; + memnew_placement(dest, AABB(p_self->operator ::AABB())); + return raw_dest; +} + +godot_basis godotsharp_variant_as_basis(const Variant *p_self) { + godot_basis raw_dest; + Basis *dest = (Basis *)&raw_dest; + memnew_placement(dest, Basis(p_self->operator Basis())); + return raw_dest; +} + +godot_transform3d godotsharp_variant_as_transform3d(const Variant *p_self) { + godot_transform3d raw_dest; + Transform3D *dest = (Transform3D *)&raw_dest; + memnew_placement(dest, Transform3D(p_self->operator Transform3D())); + return raw_dest; +} + +godot_projection godotsharp_variant_as_projection(const Variant *p_self) { + godot_projection raw_dest; + Projection *dest = (Projection *)&raw_dest; + memnew_placement(dest, Projection(p_self->operator Projection())); + return raw_dest; +} + +godot_color godotsharp_variant_as_color(const Variant *p_self) { + godot_color raw_dest; + Color *dest = (Color *)&raw_dest; + memnew_placement(dest, Color(p_self->operator Color())); + return raw_dest; +} + +godot_string_name godotsharp_variant_as_string_name(const Variant *p_self) { + godot_string_name raw_dest; + StringName *dest = (StringName *)&raw_dest; + memnew_placement(dest, StringName(p_self->operator StringName())); + return raw_dest; +} + +godot_node_path godotsharp_variant_as_node_path(const Variant *p_self) { + godot_node_path raw_dest; + NodePath *dest = (NodePath *)&raw_dest; + memnew_placement(dest, NodePath(p_self->operator NodePath())); + return raw_dest; +} + +godot_rid godotsharp_variant_as_rid(const Variant *p_self) { + godot_rid raw_dest; + RID *dest = (RID *)&raw_dest; + memnew_placement(dest, RID(p_self->operator ::RID())); + return raw_dest; +} + +godot_callable godotsharp_variant_as_callable(const Variant *p_self) { + godot_callable raw_dest; + Callable *dest = (Callable *)&raw_dest; + memnew_placement(dest, Callable(p_self->operator Callable())); + return raw_dest; +} + +godot_signal godotsharp_variant_as_signal(const Variant *p_self) { + godot_signal raw_dest; + Signal *dest = (Signal *)&raw_dest; + memnew_placement(dest, Signal(p_self->operator Signal())); + return raw_dest; +} + +godot_dictionary godotsharp_variant_as_dictionary(const Variant *p_self) { + godot_dictionary raw_dest; + Dictionary *dest = (Dictionary *)&raw_dest; + memnew_placement(dest, Dictionary(p_self->operator Dictionary())); + return raw_dest; +} + +godot_array godotsharp_variant_as_array(const Variant *p_self) { + godot_array raw_dest; + Array *dest = (Array *)&raw_dest; + memnew_placement(dest, Array(p_self->operator Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_byte_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedByteArray *dest = (PackedByteArray *)&raw_dest; + memnew_placement(dest, PackedByteArray(p_self->operator PackedByteArray())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_int32_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedInt32Array *dest = (PackedInt32Array *)&raw_dest; + memnew_placement(dest, PackedInt32Array(p_self->operator PackedInt32Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_int64_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedInt64Array *dest = (PackedInt64Array *)&raw_dest; + memnew_placement(dest, PackedInt64Array(p_self->operator PackedInt64Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_float32_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedFloat32Array *dest = (PackedFloat32Array *)&raw_dest; + memnew_placement(dest, PackedFloat32Array(p_self->operator PackedFloat32Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_float64_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedFloat64Array *dest = (PackedFloat64Array *)&raw_dest; + memnew_placement(dest, PackedFloat64Array(p_self->operator PackedFloat64Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_string_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedStringArray *dest = (PackedStringArray *)&raw_dest; + memnew_placement(dest, PackedStringArray(p_self->operator PackedStringArray())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_vector2_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedVector2Array *dest = (PackedVector2Array *)&raw_dest; + memnew_placement(dest, PackedVector2Array(p_self->operator PackedVector2Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_vector3_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedVector3Array *dest = (PackedVector3Array *)&raw_dest; + memnew_placement(dest, PackedVector3Array(p_self->operator PackedVector3Array())); + return raw_dest; +} + +godot_packed_array godotsharp_variant_as_packed_color_array(const Variant *p_self) { + godot_packed_array raw_dest; + PackedColorArray *dest = (PackedColorArray *)&raw_dest; + memnew_placement(dest, PackedColorArray(p_self->operator PackedColorArray())); + return raw_dest; +} + +bool godotsharp_variant_equals(const godot_variant *p_a, const godot_variant *p_b) { + return *reinterpret_cast<const Variant *>(p_a) == *reinterpret_cast<const Variant *>(p_b); +} + +// string.h + +void godotsharp_string_new_with_utf16_chars(String *r_dest, const char16_t *p_contents) { + memnew_placement(r_dest, String()); + r_dest->parse_utf16(p_contents); +} + +// string_name.h + +void godotsharp_string_name_new_copy(StringName *r_dest, const StringName *p_src) { + memnew_placement(r_dest, StringName(*p_src)); +} + +// node_path.h + +void godotsharp_node_path_new_copy(NodePath *r_dest, const NodePath *p_src) { + memnew_placement(r_dest, NodePath(*p_src)); +} + +// array.h + +void godotsharp_array_new(Array *r_dest) { + memnew_placement(r_dest, Array); +} + +void godotsharp_array_new_copy(Array *r_dest, const Array *p_src) { + memnew_placement(r_dest, Array(*p_src)); +} + +godot_variant *godotsharp_array_ptrw(godot_array *p_self) { + return reinterpret_cast<godot_variant *>(&reinterpret_cast<Array *>(p_self)->operator[](0)); +} + +// dictionary.h + +void godotsharp_dictionary_new(Dictionary *r_dest) { + memnew_placement(r_dest, Dictionary); +} + +void godotsharp_dictionary_new_copy(Dictionary *r_dest, const Dictionary *p_src) { + memnew_placement(r_dest, Dictionary(*p_src)); +} + +// destroy functions + +void godotsharp_packed_byte_array_destroy(PackedByteArray *p_self) { + p_self->~PackedByteArray(); +} + +void godotsharp_packed_int32_array_destroy(PackedInt32Array *p_self) { + p_self->~PackedInt32Array(); +} + +void godotsharp_packed_int64_array_destroy(PackedInt64Array *p_self) { + p_self->~PackedInt64Array(); +} + +void godotsharp_packed_float32_array_destroy(PackedFloat32Array *p_self) { + p_self->~PackedFloat32Array(); +} + +void godotsharp_packed_float64_array_destroy(PackedFloat64Array *p_self) { + p_self->~PackedFloat64Array(); +} + +void godotsharp_packed_string_array_destroy(PackedStringArray *p_self) { + p_self->~PackedStringArray(); +} + +void godotsharp_packed_vector2_array_destroy(PackedVector2Array *p_self) { + p_self->~PackedVector2Array(); +} + +void godotsharp_packed_vector3_array_destroy(PackedVector3Array *p_self) { + p_self->~PackedVector3Array(); +} + +void godotsharp_packed_color_array_destroy(PackedColorArray *p_self) { + p_self->~PackedColorArray(); +} + +void godotsharp_variant_destroy(Variant *p_self) { + p_self->~Variant(); +} + +void godotsharp_string_destroy(String *p_self) { + p_self->~String(); +} + +void godotsharp_string_name_destroy(StringName *p_self) { + p_self->~StringName(); +} + +void godotsharp_node_path_destroy(NodePath *p_self) { + p_self->~NodePath(); +} + +void godotsharp_signal_destroy(Signal *p_self) { + p_self->~Signal(); +} + +void godotsharp_callable_destroy(Callable *p_self) { + p_self->~Callable(); +} + +void godotsharp_array_destroy(Array *p_self) { + p_self->~Array(); +} + +void godotsharp_dictionary_destroy(Dictionary *p_self) { + p_self->~Dictionary(); +} + +// Array + +int32_t godotsharp_array_add(Array *p_self, const Variant *p_item) { + p_self->append(*p_item); + return p_self->size(); +} + +void godotsharp_array_duplicate(const Array *p_self, bool p_deep, Array *r_dest) { + memnew_placement(r_dest, Array(p_self->duplicate(p_deep))); +} + +int32_t godotsharp_array_index_of(const Array *p_self, const Variant *p_item) { + return p_self->find(*p_item); +} + +void godotsharp_array_insert(Array *p_self, int32_t p_index, const Variant *p_item) { + p_self->insert(p_index, *p_item); +} + +void godotsharp_array_remove_at(Array *p_self, int32_t p_index) { + p_self->remove_at(p_index); +} + +int32_t godotsharp_array_resize(Array *p_self, int32_t p_new_size) { + return (int32_t)p_self->resize(p_new_size); +} + +void godotsharp_array_shuffle(Array *p_self) { + p_self->shuffle(); +} + +void godotsharp_array_to_string(const Array *p_self, String *r_str) { + *r_str = Variant(*p_self).operator String(); +} + +// Dictionary + +bool godotsharp_dictionary_try_get_value(const Dictionary *p_self, const Variant *p_key, Variant *r_value) { + const Variant *ret = p_self->getptr(*p_key); + if (ret == nullptr) { + memnew_placement(r_value, Variant()); + return false; + } + memnew_placement(r_value, Variant(*ret)); + return true; +} + +void godotsharp_dictionary_set_value(Dictionary *p_self, const Variant *p_key, const Variant *p_value) { + p_self->operator[](*p_key) = *p_value; +} + +void godotsharp_dictionary_keys(const Dictionary *p_self, Array *r_dest) { + memnew_placement(r_dest, Array(p_self->keys())); +} + +void godotsharp_dictionary_values(const Dictionary *p_self, Array *r_dest) { + memnew_placement(r_dest, Array(p_self->values())); +} + +int32_t godotsharp_dictionary_count(const Dictionary *p_self) { + return p_self->size(); +} + +void godotsharp_dictionary_key_value_pair_at(const Dictionary *p_self, int32_t p_index, Variant *r_key, Variant *r_value) { + memnew_placement(r_key, Variant(p_self->get_key_at_index(p_index))); + memnew_placement(r_value, Variant(p_self->get_value_at_index(p_index))); +} + +void godotsharp_dictionary_add(Dictionary *p_self, const Variant *p_key, const Variant *p_value) { + p_self->operator[](*p_key) = *p_value; +} + +void godotsharp_dictionary_clear(Dictionary *p_self) { + p_self->clear(); +} + +bool godotsharp_dictionary_contains_key(const Dictionary *p_self, const Variant *p_key) { + return p_self->has(*p_key); +} + +void godotsharp_dictionary_duplicate(const Dictionary *p_self, bool p_deep, Dictionary *r_dest) { + memnew_placement(r_dest, Dictionary(p_self->duplicate(p_deep))); +} + +bool godotsharp_dictionary_remove_key(Dictionary *p_self, const Variant *p_key) { + return p_self->erase(*p_key); +} + +void godotsharp_dictionary_to_string(const Dictionary *p_self, String *r_str) { + *r_str = Variant(*p_self).operator String(); +} + +void godotsharp_string_md5_buffer(const String *p_self, PackedByteArray *r_md5_buffer) { + memnew_placement(r_md5_buffer, PackedByteArray(p_self->md5_buffer())); +} + +void godotsharp_string_md5_text(const String *p_self, String *r_md5_text) { + memnew_placement(r_md5_text, String(p_self->md5_text())); +} + +int32_t godotsharp_string_rfind(const String *p_self, const String *p_what, int32_t p_from) { + return p_self->rfind(*p_what, p_from); +} + +int32_t godotsharp_string_rfindn(const String *p_self, const String *p_what, int32_t p_from) { + return p_self->rfindn(*p_what, p_from); +} + +void godotsharp_string_sha256_buffer(const String *p_self, PackedByteArray *r_sha256_buffer) { + memnew_placement(r_sha256_buffer, PackedByteArray(p_self->sha256_buffer())); +} + +void godotsharp_string_sha256_text(const String *p_self, String *r_sha256_text) { + memnew_placement(r_sha256_text, String(p_self->sha256_text())); +} + +void godotsharp_string_simplify_path(const String *p_self, String *r_simplified_path) { + memnew_placement(r_simplified_path, String(p_self->simplify_path())); +} + +void godotsharp_node_path_get_as_property_path(const NodePath *p_ptr, NodePath *r_dest) { + memnew_placement(r_dest, NodePath(p_ptr->get_as_property_path())); +} + +void godotsharp_node_path_get_concatenated_names(const NodePath *p_self, String *r_subnames) { + memnew_placement(r_subnames, String(p_self->get_concatenated_names())); +} + +void godotsharp_node_path_get_concatenated_subnames(const NodePath *p_self, String *r_subnames) { + memnew_placement(r_subnames, String(p_self->get_concatenated_subnames())); +} + +void godotsharp_node_path_get_name(const NodePath *p_self, uint32_t p_idx, String *r_name) { + memnew_placement(r_name, String(p_self->get_name(p_idx))); +} + +int32_t godotsharp_node_path_get_name_count(const NodePath *p_self) { + return p_self->get_name_count(); +} + +void godotsharp_node_path_get_subname(const NodePath *p_self, uint32_t p_idx, String *r_subname) { + memnew_placement(r_subname, String(p_self->get_subname(p_idx))); +} + +int32_t godotsharp_node_path_get_subname_count(const NodePath *p_self) { + return p_self->get_subname_count(); +} + +bool godotsharp_node_path_is_absolute(const NodePath *p_self) { + return p_self->is_absolute(); +} + +void godotsharp_randomize() { + Math::randomize(); +} + +uint32_t godotsharp_randi() { + return Math::rand(); +} + +float godotsharp_randf() { + return Math::randf(); +} + +int32_t godotsharp_randi_range(int32_t p_from, int32_t p_to) { + return Math::random(p_from, p_to); +} + +double godotsharp_randf_range(double p_from, double p_to) { + return Math::random(p_from, p_to); +} + +double godotsharp_randfn(double p_mean, double p_deviation) { + return Math::randfn(p_mean, p_deviation); +} + +void godotsharp_seed(uint64_t p_seed) { + Math::seed(p_seed); +} + +uint32_t godotsharp_rand_from_seed(uint64_t p_seed, uint64_t *r_new_seed) { + uint32_t ret = Math::rand_from_seed(&p_seed); + *r_new_seed = p_seed; + return ret; +} + +void godotsharp_weakref(Object *p_ptr, Ref<RefCounted> *r_weak_ref) { + if (!p_ptr) { + return; + } + + Ref<WeakRef> wref; + RefCounted *rc = Object::cast_to<RefCounted>(p_ptr); + + if (rc) { + Ref<RefCounted> r = rc; + if (!r.is_valid()) { + return; + } + + wref.instantiate(); + wref->set_ref(r); + } else { + wref.instantiate(); + wref->set_obj(p_ptr); + } + + memnew_placement(r_weak_ref, Ref<RefCounted>(wref)); +} + +void godotsharp_str(const godot_array *p_what, godot_string *r_ret) { + String &str = *memnew_placement(r_ret, String); + const Array &what = *reinterpret_cast<const Array *>(p_what); + + for (int i = 0; i < what.size(); i++) { + String os = what[i].operator String(); + + if (i == 0) { + str = os; + } else { + str += os; + } + } +} + +void godotsharp_print(const godot_string *p_what) { + print_line(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_print_rich(const godot_string *p_what) { + print_line_rich(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_printerr(const godot_string *p_what) { + print_error(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_printt(const godot_string *p_what) { + print_line(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_prints(const godot_string *p_what) { + print_line(*reinterpret_cast<const String *>(p_what)); +} + +void godotsharp_printraw(const godot_string *p_what) { + OS::get_singleton()->print("%s", reinterpret_cast<const String *>(p_what)->utf8().get_data()); +} + +void godotsharp_pusherror(const godot_string *p_str) { + ERR_PRINT(*reinterpret_cast<const String *>(p_str)); +} + +void godotsharp_pushwarning(const godot_string *p_str) { + WARN_PRINT(*reinterpret_cast<const String *>(p_str)); +} + +void godotsharp_var2str(const godot_variant *p_var, godot_string *r_ret) { + const Variant &var = *reinterpret_cast<const Variant *>(p_var); + String &vars = *memnew_placement(r_ret, String); + VariantWriter::write_to_string(var, vars); +} + +void godotsharp_str2var(const godot_string *p_str, godot_variant *r_ret) { + Variant ret; + + VariantParser::StreamString ss; + ss.s = *reinterpret_cast<const String *>(p_str); + + String errs; + int line; + Error err = VariantParser::parse(&ss, ret, errs, line); + if (err != OK) { + String err_str = "Parse error at line " + itos(line) + ": " + errs + "."; + ERR_PRINT(err_str); + ret = err_str; + } + memnew_placement(r_ret, Variant(ret)); +} + +void godotsharp_var2bytes(const godot_variant *p_var, bool p_full_objects, godot_packed_array *r_bytes) { + const Variant &var = *reinterpret_cast<const Variant *>(p_var); + PackedByteArray &bytes = *memnew_placement(r_bytes, PackedByteArray); + + int len; + Error err = encode_variant(var, nullptr, len, p_full_objects); + ERR_FAIL_COND_MSG(err != OK, "Unexpected error encoding variable to bytes, likely unserializable type found (Object or RID)."); + + bytes.resize(len); + encode_variant(var, bytes.ptrw(), len, p_full_objects); +} + +void godotsharp_bytes2var(const godot_packed_array *p_bytes, bool p_allow_objects, godot_variant *r_ret) { + const PackedByteArray *bytes = reinterpret_cast<const PackedByteArray *>(p_bytes); + Variant ret; + Error err = decode_variant(ret, bytes->ptr(), bytes->size(), nullptr, p_allow_objects); + if (err != OK) { + ret = RTR("Not enough bytes for decoding bytes, or invalid format."); + } + memnew_placement(r_ret, Variant(ret)); +} + +int godotsharp_hash(const godot_variant *p_var) { + return reinterpret_cast<const Variant *>(p_var)->hash(); +} + +void godotsharp_convert(const godot_variant *p_what, int32_t p_type, godot_variant *r_ret) { + const Variant *args[1] = { reinterpret_cast<const Variant *>(p_what) }; + Callable::CallError ce; + Variant ret; + Variant::construct(Variant::Type(p_type), ret, args, 1, ce); + if (ce.error != Callable::CallError::CALL_OK) { + memnew_placement(r_ret, Variant); + ERR_FAIL_MSG("Unable to convert parameter from '" + + Variant::get_type_name(reinterpret_cast<const Variant *>(p_what)->get_type()) + + "' to '" + Variant::get_type_name(Variant::Type(p_type)) + "'."); + } + memnew_placement(r_ret, Variant(ret)); +} + +Object *godotsharp_instance_from_id(uint64_t p_instance_id) { + return ObjectDB::get_instance(ObjectID(p_instance_id)); +} + +void godotsharp_object_to_string(Object *p_ptr, godot_string *r_str) { +#ifdef DEBUG_ENABLED + // Cannot happen in C#; would get an ObjectDisposedException instead. + CRASH_COND(p_ptr == nullptr); +#endif + // Can't call 'Object::to_string()' here, as that can end up calling 'ToString' again resulting in an endless circular loop. + memnew_placement(r_str, + String("[" + p_ptr->get_class() + ":" + itos(p_ptr->get_instance_id()) + "]")); +} + +#ifdef __cplusplus +} +#endif + +// The order in this array must match the declaration order of +// the methods in 'GodotSharp/Core/NativeInterop/NativeFuncs.cs'. +static const void *unmanaged_callbacks[]{ + (void *)godotsharp_method_bind_get_method, + (void *)godotsharp_get_class_constructor, + (void *)godotsharp_engine_get_singleton, + (void *)godotsharp_stack_info_vector_resize, + (void *)godotsharp_stack_info_vector_destroy, + (void *)godotsharp_internal_script_debugger_send_error, + (void *)godotsharp_internal_script_debugger_is_active, + (void *)godotsharp_internal_object_get_associated_gchandle, + (void *)godotsharp_internal_object_disposed, + (void *)godotsharp_internal_refcounted_disposed, + (void *)godotsharp_internal_signal_awaiter_connect, + (void *)godotsharp_internal_tie_native_managed_to_unmanaged, + (void *)godotsharp_internal_tie_user_managed_to_unmanaged, + (void *)godotsharp_internal_tie_managed_to_unmanaged_with_pre_setup, + (void *)godotsharp_internal_unmanaged_get_script_instance_managed, + (void *)godotsharp_internal_unmanaged_get_instance_binding_managed, + (void *)godotsharp_internal_unmanaged_instance_binding_create_managed, + (void *)godotsharp_internal_new_csharp_script, + (void *)godotsharp_internal_script_load, + (void *)godotsharp_internal_reload_registered_script, + (void *)godotsharp_array_filter_godot_objects_by_native, + (void *)godotsharp_array_filter_godot_objects_by_non_native, + (void *)godotsharp_ref_new_from_ref_counted_ptr, + (void *)godotsharp_ref_destroy, + (void *)godotsharp_string_name_new_from_string, + (void *)godotsharp_node_path_new_from_string, + (void *)godotsharp_string_name_as_string, + (void *)godotsharp_node_path_as_string, + (void *)godotsharp_packed_byte_array_new_mem_copy, + (void *)godotsharp_packed_int32_array_new_mem_copy, + (void *)godotsharp_packed_int64_array_new_mem_copy, + (void *)godotsharp_packed_float32_array_new_mem_copy, + (void *)godotsharp_packed_float64_array_new_mem_copy, + (void *)godotsharp_packed_vector2_array_new_mem_copy, + (void *)godotsharp_packed_vector3_array_new_mem_copy, + (void *)godotsharp_packed_color_array_new_mem_copy, + (void *)godotsharp_packed_string_array_add, + (void *)godotsharp_callable_new_with_delegate, + (void *)godotsharp_callable_get_data_for_marshalling, + (void *)godotsharp_callable_call, + (void *)godotsharp_callable_call_deferred, + (void *)godotsharp_method_bind_ptrcall, + (void *)godotsharp_method_bind_call, + (void *)godotsharp_variant_new_string_name, + (void *)godotsharp_variant_new_copy, + (void *)godotsharp_variant_new_node_path, + (void *)godotsharp_variant_new_object, + (void *)godotsharp_variant_new_transform2d, + (void *)godotsharp_variant_new_vector4, + (void *)godotsharp_variant_new_vector4i, + (void *)godotsharp_variant_new_basis, + (void *)godotsharp_variant_new_transform3d, + (void *)godotsharp_variant_new_projection, + (void *)godotsharp_variant_new_aabb, + (void *)godotsharp_variant_new_dictionary, + (void *)godotsharp_variant_new_array, + (void *)godotsharp_variant_new_packed_byte_array, + (void *)godotsharp_variant_new_packed_int32_array, + (void *)godotsharp_variant_new_packed_int64_array, + (void *)godotsharp_variant_new_packed_float32_array, + (void *)godotsharp_variant_new_packed_float64_array, + (void *)godotsharp_variant_new_packed_string_array, + (void *)godotsharp_variant_new_packed_vector2_array, + (void *)godotsharp_variant_new_packed_vector3_array, + (void *)godotsharp_variant_new_packed_color_array, + (void *)godotsharp_variant_as_bool, + (void *)godotsharp_variant_as_int, + (void *)godotsharp_variant_as_float, + (void *)godotsharp_variant_as_string, + (void *)godotsharp_variant_as_vector2, + (void *)godotsharp_variant_as_vector2i, + (void *)godotsharp_variant_as_rect2, + (void *)godotsharp_variant_as_rect2i, + (void *)godotsharp_variant_as_vector3, + (void *)godotsharp_variant_as_vector3i, + (void *)godotsharp_variant_as_transform2d, + (void *)godotsharp_variant_as_vector4, + (void *)godotsharp_variant_as_vector4i, + (void *)godotsharp_variant_as_plane, + (void *)godotsharp_variant_as_quaternion, + (void *)godotsharp_variant_as_aabb, + (void *)godotsharp_variant_as_basis, + (void *)godotsharp_variant_as_transform3d, + (void *)godotsharp_variant_as_projection, + (void *)godotsharp_variant_as_color, + (void *)godotsharp_variant_as_string_name, + (void *)godotsharp_variant_as_node_path, + (void *)godotsharp_variant_as_rid, + (void *)godotsharp_variant_as_callable, + (void *)godotsharp_variant_as_signal, + (void *)godotsharp_variant_as_dictionary, + (void *)godotsharp_variant_as_array, + (void *)godotsharp_variant_as_packed_byte_array, + (void *)godotsharp_variant_as_packed_int32_array, + (void *)godotsharp_variant_as_packed_int64_array, + (void *)godotsharp_variant_as_packed_float32_array, + (void *)godotsharp_variant_as_packed_float64_array, + (void *)godotsharp_variant_as_packed_string_array, + (void *)godotsharp_variant_as_packed_vector2_array, + (void *)godotsharp_variant_as_packed_vector3_array, + (void *)godotsharp_variant_as_packed_color_array, + (void *)godotsharp_variant_equals, + (void *)godotsharp_string_new_with_utf16_chars, + (void *)godotsharp_string_name_new_copy, + (void *)godotsharp_node_path_new_copy, + (void *)godotsharp_array_new, + (void *)godotsharp_array_new_copy, + (void *)godotsharp_array_ptrw, + (void *)godotsharp_dictionary_new, + (void *)godotsharp_dictionary_new_copy, + (void *)godotsharp_packed_byte_array_destroy, + (void *)godotsharp_packed_int32_array_destroy, + (void *)godotsharp_packed_int64_array_destroy, + (void *)godotsharp_packed_float32_array_destroy, + (void *)godotsharp_packed_float64_array_destroy, + (void *)godotsharp_packed_string_array_destroy, + (void *)godotsharp_packed_vector2_array_destroy, + (void *)godotsharp_packed_vector3_array_destroy, + (void *)godotsharp_packed_color_array_destroy, + (void *)godotsharp_variant_destroy, + (void *)godotsharp_string_destroy, + (void *)godotsharp_string_name_destroy, + (void *)godotsharp_node_path_destroy, + (void *)godotsharp_signal_destroy, + (void *)godotsharp_callable_destroy, + (void *)godotsharp_array_destroy, + (void *)godotsharp_dictionary_destroy, + (void *)godotsharp_array_add, + (void *)godotsharp_array_duplicate, + (void *)godotsharp_array_index_of, + (void *)godotsharp_array_insert, + (void *)godotsharp_array_remove_at, + (void *)godotsharp_array_resize, + (void *)godotsharp_array_shuffle, + (void *)godotsharp_array_to_string, + (void *)godotsharp_dictionary_try_get_value, + (void *)godotsharp_dictionary_set_value, + (void *)godotsharp_dictionary_keys, + (void *)godotsharp_dictionary_values, + (void *)godotsharp_dictionary_count, + (void *)godotsharp_dictionary_key_value_pair_at, + (void *)godotsharp_dictionary_add, + (void *)godotsharp_dictionary_clear, + (void *)godotsharp_dictionary_contains_key, + (void *)godotsharp_dictionary_duplicate, + (void *)godotsharp_dictionary_remove_key, + (void *)godotsharp_dictionary_to_string, + (void *)godotsharp_string_md5_buffer, + (void *)godotsharp_string_md5_text, + (void *)godotsharp_string_rfind, + (void *)godotsharp_string_rfindn, + (void *)godotsharp_string_sha256_buffer, + (void *)godotsharp_string_sha256_text, + (void *)godotsharp_string_simplify_path, + (void *)godotsharp_node_path_get_as_property_path, + (void *)godotsharp_node_path_get_concatenated_names, + (void *)godotsharp_node_path_get_concatenated_subnames, + (void *)godotsharp_node_path_get_name, + (void *)godotsharp_node_path_get_name_count, + (void *)godotsharp_node_path_get_subname, + (void *)godotsharp_node_path_get_subname_count, + (void *)godotsharp_node_path_is_absolute, + (void *)godotsharp_bytes2var, + (void *)godotsharp_convert, + (void *)godotsharp_hash, + (void *)godotsharp_instance_from_id, + (void *)godotsharp_print, + (void *)godotsharp_print_rich, + (void *)godotsharp_printerr, + (void *)godotsharp_printraw, + (void *)godotsharp_prints, + (void *)godotsharp_printt, + (void *)godotsharp_randf, + (void *)godotsharp_randi, + (void *)godotsharp_randomize, + (void *)godotsharp_randf_range, + (void *)godotsharp_randfn, + (void *)godotsharp_randi_range, + (void *)godotsharp_rand_from_seed, + (void *)godotsharp_seed, + (void *)godotsharp_weakref, + (void *)godotsharp_str, + (void *)godotsharp_str2var, + (void *)godotsharp_var2bytes, + (void *)godotsharp_var2str, + (void *)godotsharp_pusherror, + (void *)godotsharp_pushwarning, + (void *)godotsharp_object_to_string, +}; + +const void **godotsharp::get_runtime_interop_funcs(int32_t &r_size) { + r_size = sizeof(unmanaged_callbacks); + return unmanaged_callbacks; +} diff --git a/modules/mono/mono_gd/gd_mono_header.h b/modules/mono/glue/runtime_interop.h index bf21283080..9725ced593 100644 --- a/modules/mono/mono_gd/gd_mono_header.h +++ b/modules/mono/glue/runtime_interop.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gd_mono_header.h */ +/* runtime_interop.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,25 +28,13 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GD_MONO_HEADER_H -#define GD_MONO_HEADER_H +#ifndef RUNTIME_INTEROP_H +#define RUNTIME_INTEROP_H -#include <cstdint> +#include "core/typedefs.h" -#ifdef WIN32 -#define GD_MONO_STDCALL __stdcall -#else -#define GD_MONO_STDCALL -#endif +namespace godotsharp { +const void **get_runtime_interop_funcs(int32_t &r_size); +} -class GDMonoAssembly; -class GDMonoClass; -class GDMonoField; -class GDMonoMethod; -class GDMonoProperty; - -class IMonoClassMember; - -#include "managed_type.h" - -#endif // GD_MONO_HEADER_H +#endif // RUNTIME_INTEROP_H diff --git a/modules/mono/glue/scene_tree_glue.cpp b/modules/mono/glue/scene_tree_glue.cpp deleted file mode 100644 index c60e7c4869..0000000000 --- a/modules/mono/glue/scene_tree_glue.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/*************************************************************************/ -/* scene_tree_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/object/class_db.h" -#include "core/string/string_name.h" -#include "core/variant/array.h" -#include "scene/main/node.h" -#include "scene/main/scene_tree.h" - -#include "../csharp_script.h" -#include "../mono_gd/gd_mono_marshal.h" -#include "../mono_gd/gd_mono_utils.h" - -Array *godot_icall_SceneTree_get_nodes_in_group_Generic(SceneTree *ptr, StringName *group, MonoReflectionType *refltype) { - List<Node *> nodes; - Array ret; - - // Retrieve all the nodes in the group - ptr->get_nodes_in_group(*group, &nodes); - - // No need to bother if the group is empty - if (!nodes.is_empty()) { - MonoType *elem_type = mono_reflection_type_get_type(refltype); - MonoClass *mono_class = mono_class_from_mono_type(elem_type); - GDMonoClass *klass = GDMono::get_singleton()->get_class(mono_class); - - if (klass == GDMonoUtils::get_class_native_base(klass)) { - // If we're trying to get native objects, just check the inheritance list - StringName native_class_name = GDMonoUtils::get_native_godot_class_name(klass); - for (int i = 0; i < nodes.size(); ++i) { - if (ClassDB::is_parent_class(nodes[i]->get_class(), native_class_name)) { - ret.push_back(nodes[i]); - } - } - } else { - // If we're trying to get csharpscript instances, get the mono object and compare the classes - for (int i = 0; i < nodes.size(); ++i) { - CSharpInstance *si = CAST_CSHARP_INSTANCE(nodes[i]->get_script_instance()); - - if (si != nullptr) { - MonoObject *obj = si->get_mono_object(); - if (obj != nullptr && mono_object_get_class(obj) == mono_class) { - ret.push_back(nodes[i]); - } - } - } - } - } - - return memnew(Array(ret)); -} - -void godot_register_scene_tree_icalls() { - GDMonoUtils::add_internal_call("Godot.SceneTree::godot_icall_SceneTree_get_nodes_in_group_Generic", godot_icall_SceneTree_get_nodes_in_group_Generic); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/string_glue.cpp b/modules/mono/glue/string_glue.cpp deleted file mode 100644 index fc6b13ceb3..0000000000 --- a/modules/mono/glue/string_glue.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/*************************************************************************/ -/* string_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/string/ustring.h" -#include "core/templates/vector.h" -#include "core/variant/variant.h" - -#include "../mono_gd/gd_mono_marshal.h" - -MonoArray *godot_icall_String_md5_buffer(MonoString *p_str) { - Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_buffer(); - // TODO Check possible Array/Vector<uint8_t> problem? - return GDMonoMarshal::Array_to_mono_array(Variant(ret)); -} - -MonoString *godot_icall_String_md5_text(MonoString *p_str) { - String ret = GDMonoMarshal::mono_string_to_godot(p_str).md5_text(); - return GDMonoMarshal::mono_string_from_godot(ret); -} - -int godot_icall_String_rfind(MonoString *p_str, MonoString *p_what, int p_from) { - String what = GDMonoMarshal::mono_string_to_godot(p_what); - return GDMonoMarshal::mono_string_to_godot(p_str).rfind(what, p_from); -} - -int godot_icall_String_rfindn(MonoString *p_str, MonoString *p_what, int p_from) { - String what = GDMonoMarshal::mono_string_to_godot(p_what); - return GDMonoMarshal::mono_string_to_godot(p_str).rfindn(what, p_from); -} - -MonoArray *godot_icall_String_sha256_buffer(MonoString *p_str) { - Vector<uint8_t> ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_buffer(); - return GDMonoMarshal::Array_to_mono_array(Variant(ret)); -} - -MonoString *godot_icall_String_sha256_text(MonoString *p_str) { - String ret = GDMonoMarshal::mono_string_to_godot(p_str).sha256_text(); - return GDMonoMarshal::mono_string_from_godot(ret); -} - -MonoString *godot_icall_String_simplify_path(MonoString *p_str) { - String ret = GDMonoMarshal::mono_string_to_godot(p_str).simplify_path(); - return GDMonoMarshal::mono_string_from_godot(ret); -} - -void godot_register_string_icalls() { - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_buffer", godot_icall_String_md5_buffer); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_md5_text", godot_icall_String_md5_text); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfind", godot_icall_String_rfind); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_rfindn", godot_icall_String_rfindn); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_buffer", godot_icall_String_sha256_buffer); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_sha256_text", godot_icall_String_sha256_text); - GDMonoUtils::add_internal_call("Godot.StringExtensions::godot_icall_String_simplify_path", godot_icall_String_simplify_path); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/glue/string_name_glue.cpp b/modules/mono/glue/string_name_glue.cpp deleted file mode 100644 index 46d15316ba..0000000000 --- a/modules/mono/glue/string_name_glue.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/*************************************************************************/ -/* string_name_glue.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifdef MONO_GLUE_ENABLED - -#include "core/string/string_name.h" -#include "core/string/ustring.h" - -#include "../mono_gd/gd_mono_marshal.h" - -StringName *godot_icall_StringName_Ctor(MonoString *p_path) { - return memnew(StringName(GDMonoMarshal::mono_string_to_godot(p_path))); -} - -void godot_icall_StringName_Dtor(StringName *p_ptr) { - ERR_FAIL_NULL(p_ptr); - memdelete(p_ptr); -} - -MonoString *godot_icall_StringName_operator_String(StringName *p_np) { - return GDMonoMarshal::mono_string_from_godot(p_np->operator String()); -} - -MonoBoolean godot_icall_StringName_is_empty(StringName *p_ptr) { - return (MonoBoolean)(*p_ptr == StringName()); -} - -void godot_register_string_name_icalls() { - GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_Ctor", godot_icall_StringName_Ctor); - GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_Dtor", godot_icall_StringName_Dtor); - GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_operator_String", godot_icall_StringName_operator_String); - GDMonoUtils::add_internal_call("Godot.StringName::godot_icall_StringName_is_empty", godot_icall_StringName_is_empty); -} - -#endif // MONO_GLUE_ENABLED diff --git a/modules/mono/godotsharp_defs.h b/modules/mono/godotsharp_defs.h index e5f1abe8d7..a81a52e4b8 100644 --- a/modules/mono/godotsharp_defs.h +++ b/modules/mono/godotsharp_defs.h @@ -33,14 +33,10 @@ #define BINDINGS_NAMESPACE "Godot" #define BINDINGS_NAMESPACE_COLLECTIONS BINDINGS_NAMESPACE ".Collections" -#define BINDINGS_GLOBAL_SCOPE_CLASS "GD" -#define BINDINGS_PTR_FIELD "ptr" -#define BINDINGS_NATIVE_NAME_FIELD "nativeName" #define API_SOLUTION_NAME "GodotSharp" #define CORE_API_ASSEMBLY_NAME "GodotSharp" #define EDITOR_API_ASSEMBLY_NAME "GodotSharpEditor" #define TOOLS_ASM_NAME "GodotTools" -#define TOOLS_PROJECT_EDITOR_ASM_NAME "GodotTools.ProjectEditor" #define BINDINGS_CLASS_NATIVECALLS "NativeCalls" #define BINDINGS_CLASS_NATIVECALLS_EDITOR "EditorNativeCalls" diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index f17b24e399..71576c2f80 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -96,8 +96,6 @@ class _GodotSharpDirs { public: String res_data_dir; String res_metadata_dir; - String res_assemblies_base_dir; - String res_assemblies_dir; String res_config_dir; String res_temp_dir; String res_temp_assemblies_base_dir; @@ -105,15 +103,14 @@ public: String mono_user_dir; String mono_logs_dir; + String api_assemblies_base_dir; + String api_assemblies_dir; + #ifdef TOOLS_ENABLED String mono_solutions_dir; String build_logs_dir; - String sln_filepath; - String csproj_filepath; - String data_editor_tools_dir; - String data_editor_prebuilt_api_dir; #else // Equivalent of res_assemblies_dir, but in the data directory rather than in 'res://'. // Only defined on export templates. Used when exporting assemblies outside of PCKs. @@ -131,8 +128,6 @@ private: _GodotSharpDirs() { res_data_dir = ProjectSettings::get_singleton()->get_project_data_path().plus_file("mono"); res_metadata_dir = res_data_dir.plus_file("metadata"); - res_assemblies_base_dir = res_data_dir.plus_file("assemblies"); - res_assemblies_dir = res_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config()); res_config_dir = res_data_dir.plus_file("etc").plus_file("mono"); // TODO use paths from csproj @@ -140,6 +135,8 @@ private: res_temp_assemblies_base_dir = res_temp_dir.plus_file("bin"); res_temp_assemblies_dir = res_temp_assemblies_base_dir.plus_file(_get_expected_build_config()); + api_assemblies_base_dir = res_data_dir.plus_file("assemblies"); + #ifdef JAVASCRIPT_ENABLED mono_user_dir = "user://"; #else @@ -151,16 +148,7 @@ private: mono_solutions_dir = mono_user_dir.plus_file("solutions"); build_logs_dir = mono_user_dir.plus_file("build_logs"); - String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - if (appname_safe.is_empty()) { - appname_safe = "UnnamedProject"; - } - String base_path = ProjectSettings::get_singleton()->globalize_path("res://"); - - sln_filepath = base_path.plus_file(appname_safe + ".sln"); - csproj_filepath = base_path.plus_file(appname_safe + ".csproj"); #endif String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); @@ -169,7 +157,7 @@ private: String data_dir_root = exe_dir.plus_file("GodotSharp"); data_editor_tools_dir = data_dir_root.plus_file("Tools"); - data_editor_prebuilt_api_dir = data_dir_root.plus_file("Api"); + api_assemblies_base_dir = data_dir_root.plus_file("Api"); String data_mono_root_dir = data_dir_root.plus_file("Mono"); data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); @@ -189,8 +177,8 @@ private: data_editor_tools_dir = exe_dir.plus_file("../Resources/GodotSharp/Tools"); } - if (!DirAccess::exists(data_editor_prebuilt_api_dir)) { - data_editor_prebuilt_api_dir = exe_dir.plus_file("../Resources/GodotSharp/Api"); + if (!DirAccess::exists(api_assemblies_base_dir)) { + api_assemblies_base_dir = exe_dir.plus_file("../Resources/GodotSharp/Api"); } if (!DirAccess::exists(data_mono_root_dir)) { @@ -234,6 +222,12 @@ private: #endif #endif + +#ifdef TOOLS_ENABLED + api_assemblies_dir = api_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config()); +#else + api_assemblies_dir = data_dir_root; +#endif } public: @@ -251,14 +245,6 @@ String get_res_metadata_dir() { return _GodotSharpDirs::get_singleton().res_metadata_dir; } -String get_res_assemblies_base_dir() { - return _GodotSharpDirs::get_singleton().res_assemblies_base_dir; -} - -String get_res_assemblies_dir() { - return _GodotSharpDirs::get_singleton().res_assemblies_dir; -} - String get_res_config_dir() { return _GodotSharpDirs::get_singleton().res_config_dir; } @@ -275,6 +261,14 @@ String get_res_temp_assemblies_dir() { return _GodotSharpDirs::get_singleton().res_temp_assemblies_dir; } +String get_api_assemblies_dir() { + return _GodotSharpDirs::get_singleton().api_assemblies_dir; +} + +String get_api_assemblies_base_dir() { + return _GodotSharpDirs::get_singleton().api_assemblies_base_dir; +} + String get_mono_user_dir() { return _GodotSharpDirs::get_singleton().mono_user_dir; } @@ -292,21 +286,9 @@ String get_build_logs_dir() { return _GodotSharpDirs::get_singleton().build_logs_dir; } -String get_project_sln_path() { - return _GodotSharpDirs::get_singleton().sln_filepath; -} - -String get_project_csproj_path() { - return _GodotSharpDirs::get_singleton().csproj_filepath; -} - String get_data_editor_tools_dir() { return _GodotSharpDirs::get_singleton().data_editor_tools_dir; } - -String get_data_editor_prebuilt_api_dir() { - return _GodotSharpDirs::get_singleton().data_editor_prebuilt_api_dir; -} #else String get_data_game_assemblies_dir() { return _GodotSharpDirs::get_singleton().data_game_assemblies_dir; diff --git a/modules/mono/godotsharp_dirs.h b/modules/mono/godotsharp_dirs.h index da25e0778f..03e62ffd82 100644 --- a/modules/mono/godotsharp_dirs.h +++ b/modules/mono/godotsharp_dirs.h @@ -37,13 +37,14 @@ namespace GodotSharpDirs { String get_res_data_dir(); String get_res_metadata_dir(); -String get_res_assemblies_base_dir(); -String get_res_assemblies_dir(); String get_res_config_dir(); String get_res_temp_dir(); String get_res_temp_assemblies_base_dir(); String get_res_temp_assemblies_dir(); +String get_api_assemblies_dir(); +String get_api_assemblies_base_dir(); + String get_mono_user_dir(); String get_mono_logs_dir(); @@ -51,11 +52,7 @@ String get_mono_logs_dir(); String get_mono_solutions_dir(); String get_build_logs_dir(); -String get_project_sln_path(); -String get_project_csproj_path(); - String get_data_editor_tools_dir(); -String get_data_editor_prebuilt_api_dir(); #else String get_data_game_assemblies_dir(); #endif diff --git a/modules/mono/interop_types.h b/modules/mono/interop_types.h new file mode 100644 index 0000000000..6942a91559 --- /dev/null +++ b/modules/mono/interop_types.h @@ -0,0 +1,208 @@ +/*************************************************************************/ +/* interop_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 INTEROP_TYPES_H +#define INTEROP_TYPES_H + +#include "core/math/math_defs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdbool.h> +#include <stdint.h> + +// This is taken from the old GDNative, which was removed. + +#define GODOT_VARIANT_SIZE (sizeof(real_t) * 4 + sizeof(int64_t)) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VARIANT_SIZE]; +} godot_variant; + +#define GODOT_ARRAY_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_ARRAY_SIZE]; +} godot_array; + +#define GODOT_DICTIONARY_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_DICTIONARY_SIZE]; +} godot_dictionary; + +#define GODOT_STRING_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_STRING_SIZE]; +} godot_string; + +#define GODOT_STRING_NAME_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_STRING_NAME_SIZE]; +} godot_string_name; + +#define GODOT_PACKED_ARRAY_SIZE (2 * sizeof(void *)) + +typedef struct { + uint8_t _dont_touch_that[GODOT_PACKED_ARRAY_SIZE]; +} godot_packed_array; + +#define GODOT_VECTOR2_SIZE (sizeof(real_t) * 2) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR2_SIZE]; +} godot_vector2; + +#define GODOT_VECTOR2I_SIZE (sizeof(int32_t) * 2) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR2I_SIZE]; +} godot_vector2i; + +#define GODOT_RECT2_SIZE (sizeof(real_t) * 4) + +typedef struct godot_rect2 { + uint8_t _dont_touch_that[GODOT_RECT2_SIZE]; +} godot_rect2; + +#define GODOT_RECT2I_SIZE (sizeof(int32_t) * 4) + +typedef struct godot_rect2i { + uint8_t _dont_touch_that[GODOT_RECT2I_SIZE]; +} godot_rect2i; + +#define GODOT_VECTOR3_SIZE (sizeof(real_t) * 3) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR3_SIZE]; +} godot_vector3; + +#define GODOT_VECTOR3I_SIZE (sizeof(int32_t) * 3) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR3I_SIZE]; +} godot_vector3i; + +#define GODOT_TRANSFORM2D_SIZE (sizeof(real_t) * 6) + +typedef struct { + uint8_t _dont_touch_that[GODOT_TRANSFORM2D_SIZE]; +} godot_transform2d; + +#define GODOT_VECTOR4_SIZE (sizeof(real_t) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR4_SIZE]; +} godot_vector4; + +#define GODOT_VECTOR4I_SIZE (sizeof(int32_t) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_VECTOR4I_SIZE]; +} godot_vector4i; + +#define GODOT_PLANE_SIZE (sizeof(real_t) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_PLANE_SIZE]; +} godot_plane; + +#define GODOT_QUATERNION_SIZE (sizeof(real_t) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_QUATERNION_SIZE]; +} godot_quaternion; + +#define GODOT_AABB_SIZE (sizeof(real_t) * 6) + +typedef struct { + uint8_t _dont_touch_that[GODOT_AABB_SIZE]; +} godot_aabb; + +#define GODOT_BASIS_SIZE (sizeof(real_t) * 9) + +typedef struct { + uint8_t _dont_touch_that[GODOT_BASIS_SIZE]; +} godot_basis; + +#define GODOT_TRANSFORM3D_SIZE (sizeof(real_t) * 12) + +typedef struct { + uint8_t _dont_touch_that[GODOT_TRANSFORM3D_SIZE]; +} godot_transform3d; + +#define GODOT_PROJECTION_SIZE (sizeof(real_t) * 4 * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_PROJECTION_SIZE]; +} godot_projection; + +// Colors should always use 32-bit floats, so don't use real_t here. +#define GODOT_COLOR_SIZE (sizeof(float) * 4) + +typedef struct { + uint8_t _dont_touch_that[GODOT_COLOR_SIZE]; +} godot_color; + +#define GODOT_NODE_PATH_SIZE sizeof(void *) + +typedef struct { + uint8_t _dont_touch_that[GODOT_NODE_PATH_SIZE]; +} godot_node_path; + +#define GODOT_RID_SIZE sizeof(uint64_t) + +typedef struct { + uint8_t _dont_touch_that[GODOT_RID_SIZE]; +} godot_rid; + +// Alignment hardcoded in `core/variant/callable.h`. +#define GODOT_CALLABLE_SIZE (16) + +typedef struct { + uint8_t _dont_touch_that[GODOT_CALLABLE_SIZE]; +} godot_callable; + +// Alignment hardcoded in `core/variant/callable.h`. +#define GODOT_SIGNAL_SIZE (16) + +typedef struct { + uint8_t _dont_touch_that[GODOT_SIGNAL_SIZE]; +} godot_signal; + +#ifdef __cplusplus +} +#endif + +#endif // INTEROP_TYPES_H diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp index c159bb9eea..9305dc645a 100644 --- a/modules/mono/managed_callable.cpp +++ b/modules/mono/managed_callable.cpp @@ -31,8 +31,7 @@ #include "managed_callable.h" #include "csharp_script.h" -#include "mono_gd/gd_mono_marshal.h" -#include "mono_gd/gd_mono_utils.h" +#include "mono_gd/gd_mono_cache.h" #ifdef GD_MONO_HOT_RELOAD SelfList<ManagedCallable>::List ManagedCallable::instances; @@ -44,18 +43,16 @@ bool ManagedCallable::compare_equal(const CallableCustom *p_a, const CallableCus const ManagedCallable *a = static_cast<const ManagedCallable *>(p_a); const ManagedCallable *b = static_cast<const ManagedCallable *>(p_b); - MonoDelegate *delegate_a = (MonoDelegate *)a->delegate_handle.get_target(); - MonoDelegate *delegate_b = (MonoDelegate *)b->delegate_handle.get_target(); - - if (!delegate_a || !delegate_b) { - if (!delegate_a && !delegate_b) { + if (!a->delegate_handle.value || !b->delegate_handle.value) { + if (!a->delegate_handle.value && !b->delegate_handle.value) { return true; } return false; } // Call Delegate's 'Equals' - return GDMonoUtils::mono_delegate_equal(delegate_a, delegate_b); + return GDMonoCache::managed_callbacks.DelegateUtils_DelegateEquals( + a->delegate_handle, b->delegate_handle); } bool ManagedCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -66,8 +63,7 @@ bool ManagedCallable::compare_less(const CallableCustom *p_a, const CallableCust } uint32_t ManagedCallable::hash() const { - uint32_t hash = delegate_invoke->get_name().hash(); - return hash_murmur3_one_64(delegate_handle.handle, hash); + return hash_murmur3_one_64((uint64_t)delegate_handle.value); } String ManagedCallable::get_as_text() const { @@ -91,41 +87,24 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better r_return_value = Variant(); -#ifdef GD_MONO_HOT_RELOAD - // Lost during hot-reload - ERR_FAIL_NULL(delegate_invoke); - ERR_FAIL_COND(delegate_handle.is_released()); -#endif - - ERR_FAIL_COND(delegate_invoke->get_parameters_count() < p_argcount); - - MonoObject *delegate = delegate_handle.get_target(); + ERR_FAIL_COND(delegate_handle.value == nullptr); - MonoException *exc = nullptr; - MonoObject *ret = delegate_invoke->invoke(delegate, p_arguments, &exc); + GDMonoCache::managed_callbacks.DelegateUtils_InvokeWithVariantArgs( + delegate_handle, p_arguments, p_argcount, &r_return_value); - if (exc) { - GDMonoUtils::set_pending_exception(exc); - } else { - r_return_value = GDMonoMarshal::mono_object_to_variant(ret); - r_call_error.error = Callable::CallError::CALL_OK; - } + r_call_error.error = Callable::CallError::CALL_OK; } -void ManagedCallable::set_delegate(MonoDelegate *p_delegate) { - delegate_handle = MonoGCHandleData::new_strong_handle((MonoObject *)p_delegate); - MonoMethod *delegate_invoke_raw = mono_get_delegate_invoke(mono_object_get_class((MonoObject *)p_delegate)); - const StringName &delegate_invoke_name = CSharpLanguage::get_singleton()->get_string_names().delegate_invoke_method_name; - delegate_invoke = memnew(GDMonoMethod(delegate_invoke_name, delegate_invoke_raw)); // TODO: Use pooling for this GDMonoMethod instances +void ManagedCallable::release_delegate_handle() { + if (delegate_handle.value) { + GDMonoCache::managed_callbacks.GCHandleBridge_FreeGCHandle(delegate_handle); + delegate_handle = { nullptr }; + } } -ManagedCallable::ManagedCallable(MonoDelegate *p_delegate) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_delegate == nullptr); -#endif - - set_delegate(p_delegate); - +// Why you do this clang-format... +/* clang-format off */ +ManagedCallable::ManagedCallable(GCHandleIntPtr p_delegate_handle) : delegate_handle(p_delegate_handle) { #ifdef GD_MONO_HOT_RELOAD { MutexLock lock(instances_mutex); @@ -133,6 +112,7 @@ ManagedCallable::ManagedCallable(MonoDelegate *p_delegate) { } #endif } +/* clang-format on */ ManagedCallable::~ManagedCallable() { #ifdef GD_MONO_HOT_RELOAD @@ -143,5 +123,5 @@ ManagedCallable::~ManagedCallable() { } #endif - delegate_handle.release(); + release_delegate_handle(); } diff --git a/modules/mono/managed_callable.h b/modules/mono/managed_callable.h index 11bee6cf60..aa3344f4d5 100644 --- a/modules/mono/managed_callable.h +++ b/modules/mono/managed_callable.h @@ -31,19 +31,15 @@ #ifndef MANAGED_CALLABLE_H #define MANAGED_CALLABLE_H -#include <mono/metadata/object.h> - #include "core/os/mutex.h" #include "core/templates/self_list.h" #include "core/variant/callable.h" #include "mono_gc_handle.h" -#include "mono_gd/gd_mono_method.h" class ManagedCallable : public CallableCustom { friend class CSharpLanguage; - MonoGCHandleData delegate_handle; - GDMonoMethod *delegate_invoke = nullptr; + GCHandleIntPtr delegate_handle; #ifdef GD_MONO_HOT_RELOAD SelfList<ManagedCallable> self_instance = this; @@ -60,9 +56,7 @@ public: 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; - _FORCE_INLINE_ MonoDelegate *get_delegate() { return (MonoDelegate *)delegate_handle.get_target(); } - - void set_delegate(MonoDelegate *p_delegate); + _FORCE_INLINE_ GCHandleIntPtr get_delegate() const { return delegate_handle; } static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); static bool compare_less(const CallableCustom *p_a, const CallableCustom *p_b); @@ -70,7 +64,9 @@ public: static constexpr CompareEqualFunc compare_equal_func_ptr = &ManagedCallable::compare_equal; static constexpr CompareEqualFunc compare_less_func_ptr = &ManagedCallable::compare_less; - ManagedCallable(MonoDelegate *p_delegate); + void release_delegate_handle(); + + ManagedCallable(GCHandleIntPtr p_delegate_handle); ~ManagedCallable(); }; diff --git a/modules/mono/mono_gc_handle.cpp b/modules/mono/mono_gc_handle.cpp index f3dafa6ecf..9cf0a641b9 100644 --- a/modules/mono/mono_gc_handle.cpp +++ b/modules/mono/mono_gc_handle.cpp @@ -31,34 +31,20 @@ #include "mono_gc_handle.h" #include "mono_gd/gd_mono.h" +#include "mono_gd/gd_mono_cache.h" void MonoGCHandleData::release() { #ifdef DEBUG_ENABLED - CRASH_COND(handle && GDMono::get_singleton() == nullptr); + CRASH_COND(handle.value && GDMono::get_singleton() == nullptr); #endif - if (handle && GDMono::get_singleton()->is_runtime_initialized()) { - GDMonoUtils::free_gchandle(handle); - handle = 0; + if (handle.value && GDMonoCache::godot_api_cache_updated && + GDMono::get_singleton()->is_runtime_initialized()) { + free_gchandle(handle); + handle.value = nullptr; } } - -MonoGCHandleData MonoGCHandleData::new_strong_handle(MonoObject *p_object) { - return MonoGCHandleData(GDMonoUtils::new_strong_gchandle(p_object), gdmono::GCHandleType::STRONG_HANDLE); -} - -MonoGCHandleData MonoGCHandleData::new_strong_handle_pinned(MonoObject *p_object) { - return MonoGCHandleData(GDMonoUtils::new_strong_gchandle_pinned(p_object), gdmono::GCHandleType::STRONG_HANDLE); -} - -MonoGCHandleData MonoGCHandleData::new_weak_handle(MonoObject *p_object) { - return MonoGCHandleData(GDMonoUtils::new_weak_gchandle(p_object), gdmono::GCHandleType::WEAK_HANDLE); -} - -Ref<MonoGCHandleRef> MonoGCHandleRef::create_strong(MonoObject *p_object) { - return memnew(MonoGCHandleRef(MonoGCHandleData::new_strong_handle(p_object))); -} - -Ref<MonoGCHandleRef> MonoGCHandleRef::create_weak(MonoObject *p_object) { - return memnew(MonoGCHandleRef(MonoGCHandleData::new_weak_handle(p_object))); +void MonoGCHandleData::free_gchandle(GCHandleIntPtr p_gchandle) { + CRASH_COND(!GDMonoCache::godot_api_cache_updated); + GDMonoCache::managed_callbacks.GCHandleBridge_FreeGCHandle(p_gchandle); } diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h index e2aff1d19d..4e4c13fee6 100644 --- a/modules/mono/mono_gc_handle.h +++ b/modules/mono/mono_gc_handle.h @@ -31,8 +31,6 @@ #ifndef MONO_GC_HANDLE_H #define MONO_GC_HANDLE_H -#include <mono/jit/jit.h> - #include "core/object/ref_counted.h" namespace gdmono { @@ -44,18 +42,32 @@ enum class GCHandleType : char { }; } +extern "C" { +struct GCHandleIntPtr { + void *value; + + _FORCE_INLINE_ bool operator==(const GCHandleIntPtr &p_other) { return value == p_other.value; } + _FORCE_INLINE_ bool operator!=(const GCHandleIntPtr &p_other) { return value != p_other.value; } + + GCHandleIntPtr() = delete; +}; +} + +static_assert(sizeof(GCHandleIntPtr) == sizeof(void *)); + // Manual release of the GC handle must be done when using this struct struct MonoGCHandleData { - uint32_t handle = 0; + GCHandleIntPtr handle = { nullptr }; gdmono::GCHandleType type = gdmono::GCHandleType::NIL; - _FORCE_INLINE_ bool is_released() const { return !handle; } + _FORCE_INLINE_ bool is_released() const { return !handle.value; } _FORCE_INLINE_ bool is_weak() const { return type == gdmono::GCHandleType::WEAK_HANDLE; } - - _FORCE_INLINE_ MonoObject *get_target() const { return handle ? mono_gchandle_get_target(handle) : nullptr; } + _FORCE_INLINE_ GCHandleIntPtr get_intptr() const { return handle; } void release(); + static void free_gchandle(GCHandleIntPtr p_gchandle); + void operator=(const MonoGCHandleData &p_other) { #ifdef DEBUG_ENABLED CRASH_COND(!is_released()); @@ -68,40 +80,10 @@ struct MonoGCHandleData { MonoGCHandleData() {} - MonoGCHandleData(uint32_t p_handle, gdmono::GCHandleType p_type) : + MonoGCHandleData(GCHandleIntPtr p_handle, gdmono::GCHandleType p_type) : handle(p_handle), type(p_type) { } - - static MonoGCHandleData new_strong_handle(MonoObject *p_object); - static MonoGCHandleData new_strong_handle_pinned(MonoObject *p_object); - static MonoGCHandleData new_weak_handle(MonoObject *p_object); -}; - -class MonoGCHandleRef : public RefCounted { - GDCLASS(MonoGCHandleRef, RefCounted); - - MonoGCHandleData data; - -public: - static Ref<MonoGCHandleRef> create_strong(MonoObject *p_object); - static Ref<MonoGCHandleRef> create_weak(MonoObject *p_object); - - _FORCE_INLINE_ bool is_released() const { return data.is_released(); } - _FORCE_INLINE_ bool is_weak() const { return data.is_weak(); } - - _FORCE_INLINE_ MonoObject *get_target() const { return data.get_target(); } - - void release() { data.release(); } - - _FORCE_INLINE_ void set_handle(uint32_t p_handle, gdmono::GCHandleType p_handle_type) { - data = MonoGCHandleData(p_handle, p_handle_type); - } - - MonoGCHandleRef(const MonoGCHandleData &p_gc_handle_data) : - data(p_gc_handle_data) { - } - ~MonoGCHandleRef() { release(); } }; #endif // MONO_GC_HANDLE_H diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index d3d3bb2bef..f746d63ce5 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -30,13 +30,6 @@ #include "gd_mono.h" -#include <mono/metadata/environment.h> -#include <mono/metadata/exception.h> -#include <mono/metadata/mono-config.h> -#include <mono/metadata/mono-debug.h> -#include <mono/metadata/mono-gc.h> -#include <mono/metadata/profiler.h> - #include "core/config/project_settings.h" #include "core/debugger/engine_debugger.h" #include "core/io/dir_access.h" @@ -45,1077 +38,515 @@ #include "core/os/thread.h" #include "../csharp_script.h" +#include "../glue/runtime_interop.h" #include "../godotsharp_dirs.h" #include "../utils/path_utils.h" #include "gd_mono_cache.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_utils.h" +#ifdef TOOLS_ENABLED +#include <nethost.h> +#endif + +#include <coreclr_delegates.h> +#include <hostfxr.h> +#ifdef UNIX_ENABLED +#include <dlfcn.h> +#endif + +// TODO mobile +#if 0 #ifdef ANDROID_ENABLED -#include "android_mono_config.h" #include "support/android_support.h" #elif defined(IOS_ENABLED) #include "support/ios_support.h" #endif - -#if defined(TOOL_ENABLED) && defined(GD_MONO_SINGLE_APPDOMAIN) -// This will no longer be the case if we replace appdomains with AssemblyLoadContext -#error "Editor build requires support for multiple appdomains" -#endif - -#if defined(GD_MONO_HOT_RELOAD) && defined(GD_MONO_SINGLE_APPDOMAIN) -#error "Hot reloading requires multiple appdomains" #endif -// TODO: -// This has turned into a gigantic mess. There's too much going on here. Too much #ifdef as well. -// It's just painful to read... It needs to be re-structured. Please, clean this up, future me. - GDMono *GDMono::singleton = nullptr; namespace { - -#if defined(JAVASCRIPT_ENABLED) -extern "C" { -void mono_wasm_load_runtime(const char *managed_path, int enable_debugging); -} -#endif - -#if !defined(JAVASCRIPT_ENABLED) - -void gd_mono_setup_runtime_main_args() { - CharString execpath = OS::get_singleton()->get_executable_path().utf8(); - - List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); - - List<CharString> cmdline_args_utf8; - Vector<char *> main_args; - main_args.resize(cmdline_args.size() + 1); - - main_args.write[0] = execpath.ptrw(); - - int i = 1; - for (const String &E : cmdline_args) { - CharString &stored = cmdline_args_utf8.push_back(E.utf8())->get(); - main_args.write[i] = stored.ptrw(); - i++; - } - - mono_runtime_set_main_args(main_args.size(), main_args.ptrw()); -} - -void gd_mono_profiler_init() { - String profiler_args = GLOBAL_DEF("mono/profiler/args", "log:calls,alloc,sample,output=output.mlpd"); - bool profiler_enabled = GLOBAL_DEF("mono/profiler/enabled", false); - if (profiler_enabled) { - mono_profiler_load(profiler_args.utf8()); - return; - } - - const String env_var_name = "MONO_ENV_OPTIONS"; - if (OS::get_singleton()->has_environment(env_var_name)) { - const String mono_env_ops = OS::get_singleton()->get_environment(env_var_name); - // Usually MONO_ENV_OPTIONS looks like: --profile=jb:prof=timeline,ctl=remote,host=127.0.0.1:55467 - const String prefix = "--profile="; - if (mono_env_ops.begins_with(prefix)) { - const String ops = mono_env_ops.substr(prefix.length(), mono_env_ops.length()); - mono_profiler_load(ops.utf8()); - } - } -} - -void gd_mono_debug_init() { - CharString da_args = OS::get_singleton()->get_environment("GODOT_MONO_DEBUGGER_AGENT").utf8(); - - if (da_args.length()) { - OS::get_singleton()->set_environment("GODOT_MONO_DEBUGGER_AGENT", String()); - } - -#ifdef TOOLS_ENABLED - int da_port = GLOBAL_DEF("mono/debugger_agent/port", 23685); - bool da_suspend = GLOBAL_DEF("mono/debugger_agent/wait_for_debugger", false); - int da_timeout = GLOBAL_DEF("mono/debugger_agent/wait_timeout", 3000); - - if (Engine::get_singleton()->is_editor_hint() || - ProjectSettings::get_singleton()->get_resource_path().is_empty() || - Engine::get_singleton()->is_project_manager_hint()) { - if (da_args.size() == 0) { - return; - } - } - - if (da_args.length() == 0) { - da_args = String("--debugger-agent=transport=dt_socket,address=127.0.0.1:" + itos(da_port) + - ",embedding=1,server=y,suspend=" + (da_suspend ? "y,timeout=" + itos(da_timeout) : "n")) - .utf8(); - } +hostfxr_initialize_for_dotnet_command_line_fn hostfxr_initialize_for_dotnet_command_line = nullptr; +hostfxr_initialize_for_runtime_config_fn hostfxr_initialize_for_runtime_config = nullptr; +hostfxr_get_runtime_delegate_fn hostfxr_get_runtime_delegate = nullptr; +hostfxr_close_fn hostfxr_close = nullptr; + +#ifdef _WIN32 +static_assert(sizeof(char_t) == sizeof(char16_t)); +using HostFxrCharString = Char16String; +#define HOSTFXR_STR(m_str) L##m_str #else - if (da_args.length() == 0) { - return; // Exported games don't use the project settings to setup the debugger agent - } +static_assert(sizeof(char_t) == sizeof(char)); +using HostFxrCharString = CharString; +#define HOSTFXR_STR(m_str) m_str #endif - // Debugging enabled - - mono_debug_init(MONO_DEBUG_FORMAT_MONO); - - // --debugger-agent=help - const char *options[] = { - "--soft-breakpoints", - da_args.get_data() - }; - mono_jit_parse_options(2, (char **)options); -} - -#endif // !defined(JAVASCRIPT_ENABLED) - -#if defined(JAVASCRIPT_ENABLED) -MonoDomain *gd_initialize_mono_runtime() { - const char *vfs_prefix = "managed"; - int enable_debugging = 0; - - // TODO: Provide a way to enable debugging on WASM release builds. -#ifdef DEBUG_ENABLED - enable_debugging = 1; +HostFxrCharString str_to_hostfxr(const String &p_str) { +#ifdef _WIN32 + return p_str.utf16(); +#else + return p_str.utf8(); #endif - - mono_wasm_load_runtime(vfs_prefix, enable_debugging); - - return mono_get_root_domain(); } -#else -MonoDomain *gd_initialize_mono_runtime() { - gd_mono_debug_init(); -#if defined(IOS_ENABLED) || defined(ANDROID_ENABLED) - // I don't know whether this actually matters or not - const char *runtime_version = "mobile"; +#ifdef TOOLS_ENABLED +String str_from_hostfxr(const char_t *p_buffer) { +#ifdef _WIN32 + return String::utf16((const char16_t *)p_buffer); #else - const char *runtime_version = "v4.0.30319"; + return String::utf8((const char *)p_buffer); #endif - - return mono_jit_init_version("GodotEngine.RootDomain", runtime_version); } #endif -} // namespace - -void GDMono::add_mono_shared_libs_dir_to_path() { - // TODO: Replace this with a mono_dl_fallback - - // By default Mono seems to search shared libraries in the following directories: - // Current working directory, @executable_path@ and PATH - // The parent directory of the image file (assembly where the dllimport method is declared) - // @executable_path@/../lib - // @executable_path@/../Libraries (__MACH__ only) - // This does not work when embedding Mono unless we use the same directory structure. - // To fix this we append the directory containing our shared libraries to PATH. - -#if defined(WINDOWS_ENABLED) || defined(UNIX_ENABLED) - String path_var("PATH"); - String path_value = OS::get_singleton()->get_environment(path_var); - -#ifdef WINDOWS_ENABLED - path_value += ';'; +const char_t *get_data(const HostFxrCharString &p_char_str) { + return (const char_t *)p_char_str.get_data(); +} - String bundled_bin_dir = GodotSharpDirs::get_data_mono_bin_dir(); #ifdef TOOLS_ENABLED - if (DirAccess::exists(bundled_bin_dir)) { - path_value += bundled_bin_dir; - } else { - path_value += mono_reg_info.bin_dir; - } -#else - if (DirAccess::exists(bundled_bin_dir)) { - path_value += bundled_bin_dir; - } -#endif // TOOLS_ENABLED +String find_hostfxr(size_t p_known_buffet_size, get_hostfxr_parameters *p_get_hostfxr_params) { + // Pre-allocate a large buffer for the path to hostfxr + Vector<char_t> buffer; + buffer.resize(p_known_buffet_size); -#else - path_value += ':'; + int rc = get_hostfxr_path(buffer.ptrw(), &p_known_buffet_size, p_get_hostfxr_params); - String bundled_lib_dir = GodotSharpDirs::get_data_mono_lib_dir(); - if (DirAccess::exists(bundled_lib_dir)) { - path_value += bundled_lib_dir; - } else { - // TODO: Do we need to add the lib dir when using the system installed Mono on Unix platforms? - } -#endif // WINDOWS_ENABLED + ERR_FAIL_COND_V_MSG(rc != 0, String(), "get_hostfxr_path failed with code: " + itos(rc)); - OS::get_singleton()->set_environment(path_var, path_value); -#endif // WINDOWS_ENABLED || UNIX_ENABLED + return str_from_hostfxr(buffer.ptr()); } +#endif -void GDMono::determine_mono_dirs(String &r_assembly_rootdir, String &r_config_dir) { - String bundled_assembly_rootdir = GodotSharpDirs::get_data_mono_lib_dir(); - String bundled_config_dir = GodotSharpDirs::get_data_mono_etc_dir(); - +String find_hostfxr() { #ifdef TOOLS_ENABLED - -#if defined(WINDOWS_ENABLED) - mono_reg_info = MonoRegUtils::find_mono(); - - if (mono_reg_info.assembly_dir.length() && DirAccess::exists(mono_reg_info.assembly_dir)) { - r_assembly_rootdir = mono_reg_info.assembly_dir; - } - - if (mono_reg_info.config_dir.length() && DirAccess::exists(mono_reg_info.config_dir)) { - r_config_dir = mono_reg_info.config_dir; - } -#elif defined(MACOS_ENABLED) - const char *c_assembly_rootdir = mono_assembly_getrootdir(); - const char *c_config_dir = mono_get_config_dir(); - - if (!c_assembly_rootdir || !c_config_dir || !DirAccess::exists(c_assembly_rootdir) || !DirAccess::exists(c_config_dir)) { - Vector<const char *> locations; - locations.push_back("/Library/Frameworks/Mono.framework/Versions/Current/"); - locations.push_back("/usr/local/var/homebrew/linked/mono/"); - - for (int i = 0; i < locations.size(); i++) { - String hint_assembly_rootdir = path::join(locations[i], "lib"); - String hint_mscorlib_path = path::join(hint_assembly_rootdir, "mono", "4.5", "mscorlib.dll"); - String hint_config_dir = path::join(locations[i], "etc"); - - if (FileAccess::exists(hint_mscorlib_path) && DirAccess::exists(hint_config_dir)) { - r_assembly_rootdir = hint_assembly_rootdir; - r_config_dir = hint_config_dir; - break; + const int CoreHostLibMissingFailure = 0x80008083; + const int HostApiBufferTooSmall = 0x80008098; + + size_t buffer_size = 0; + int rc = get_hostfxr_path(nullptr, &buffer_size, nullptr); + + if (rc == HostApiBufferTooSmall) { + return find_hostfxr(buffer_size, nullptr); + } + + if (rc == CoreHostLibMissingFailure) { + // Apparently `get_hostfxr_path` doesn't look for dotnet in `PATH`? (I suppose it needs the + // `DOTNET_ROOT` environment variable). If it fails, we try to find the dotnet executable + // in `PATH` ourselves and pass its location as `dotnet_root` to `get_hostfxr_path`. + String dotnet_exe = path::find_executable("dotnet"); + + if (!dotnet_exe.is_empty()) { + // The file found in PATH may be a symlink + dotnet_exe = path::abspath(path::realpath(dotnet_exe)); + + // TODO: + // Sometimes, the symlink may not point to the dotnet executable in the dotnet root. + // That's the case with snaps. The snap install should have been found with the + // previous `get_hostfxr_path`, but it would still be better to do this properly + // and use something like `dotnet --list-sdks/runtimes` to find the actual location. + // This way we could also check if the proper sdk or runtime is installed. This would + // allow us to fail gracefully and show some helpful information in the editor. + + HostFxrCharString dotnet_root = str_to_hostfxr(dotnet_exe.get_base_dir()); + + get_hostfxr_parameters get_hostfxr_parameters = { + sizeof(get_hostfxr_parameters), + nullptr, + get_data(dotnet_root) + }; + + buffer_size = 0; + rc = get_hostfxr_path(nullptr, &buffer_size, &get_hostfxr_parameters); + if (rc == HostApiBufferTooSmall) { + return find_hostfxr(buffer_size, &get_hostfxr_parameters); } } } -#endif - if (DirAccess::exists(bundled_assembly_rootdir)) { - r_assembly_rootdir = bundled_assembly_rootdir; + if (rc == CoreHostLibMissingFailure) { + ERR_PRINT(String() + ".NET: One of the dependent libraries is missing. " + + "Typically when the `hostfxr`, `hostpolicy` or `coreclr` dynamic " + + "libraries are not present in the expected locations."); } - if (DirAccess::exists(bundled_config_dir)) { - r_config_dir = bundled_config_dir; - } - -#ifdef WINDOWS_ENABLED - if (r_assembly_rootdir.is_empty() || r_config_dir.is_empty()) { - ERR_PRINT("Cannot find Mono in the registry."); - // Assertion: if they are not set, then they weren't found in the registry - CRASH_COND(mono_reg_info.assembly_dir.length() > 0 || mono_reg_info.config_dir.length() > 0); - } -#endif // WINDOWS_ENABLED - + return String(); #else - // Export templates always use the bundled directories - r_assembly_rootdir = bundled_assembly_rootdir; - r_config_dir = bundled_config_dir; -#endif -} - -void GDMono::initialize() { - ERR_FAIL_NULL(Engine::get_singleton()); - - print_verbose("Mono: Initializing module..."); - - char *runtime_build_info = mono_get_runtime_build_info(); - print_verbose("Mono JIT compiler version " + String(runtime_build_info)); - mono_free(runtime_build_info); - - _init_godot_api_hashes(); - _init_exception_policy(); - - GDMonoLog::get_singleton()->initialize(); - -#if !defined(JAVASCRIPT_ENABLED) - String assembly_rootdir; - String config_dir; - determine_mono_dirs(assembly_rootdir, config_dir); - - // Leak if we call mono_set_dirs more than once - mono_set_dirs(assembly_rootdir.length() ? assembly_rootdir.utf8().get_data() : nullptr, - config_dir.length() ? config_dir.utf8().get_data() : nullptr); - add_mono_shared_libs_dir_to_path(); -#endif - -#ifdef ANDROID_ENABLED - mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data()); +#if defined(WINDOWS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .plus_file("hostfxr.dll"); +#elif defined(MACOS_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .plus_file("libhostfxr.dylib"); +#elif defined(UNIX_ENABLED) + String probe_path = GodotSharpDirs::get_api_assemblies_dir() + .plus_file("libhostfxr.so"); #else - mono_config_parse(nullptr); +#error "Platform not supported (yet?)" #endif -#if defined(ANDROID_ENABLED) - gdmono::android::support::initialize(); -#elif defined(IOS_ENABLED) - gdmono::ios::support::initialize(); -#endif + if (FileAccess::exists(probe_path)) { + return probe_path; + } - GDMonoAssembly::initialize(); + return String(); -#if !defined(JAVASCRIPT_ENABLED) - gd_mono_profiler_init(); #endif +} - mono_install_unhandled_exception_hook(&unhandled_exception_hook, nullptr); - -#ifndef TOOLS_ENABLED - // Exported games that don't use C# must still work. They likely don't ship with mscorlib. - // We only initialize the Mono runtime if we can find mscorlib. Otherwise it would crash. - if (GDMonoAssembly::find_assembly("mscorlib.dll").is_empty()) { - print_verbose("Mono: Skipping runtime initialization because 'mscorlib.dll' could not be found"); - return; - } -#endif +bool load_hostfxr(void *&r_hostfxr_dll_handle) { + String hostfxr_path = find_hostfxr(); -#if !defined(NO_MONO_THREADS_SUSPEND_WORKAROUND) - // FIXME: Temporary workaround. See: https://github.com/godotengine/godot/issues/29812 - if (!OS::get_singleton()->has_environment("MONO_THREADS_SUSPEND")) { - OS::get_singleton()->set_environment("MONO_THREADS_SUSPEND", "preemptive"); + if (hostfxr_path.is_empty()) { + return false; } -#endif - // NOTE: Internal calls must be registered after the Mono runtime initialization. - // Otherwise registration fails with the error: 'assertion 'hash != nullptr' failed'. + print_verbose("Found hostfxr: " + hostfxr_path); - root_domain = gd_initialize_mono_runtime(); - ERR_FAIL_NULL_MSG(root_domain, "Mono: Failed to initialize runtime."); + Error err = OS::get_singleton()->open_dynamic_library(hostfxr_path, r_hostfxr_dll_handle); - GDMonoUtils::set_main_thread(GDMonoUtils::get_current_thread()); - -#if !defined(JAVASCRIPT_ENABLED) - gd_mono_setup_runtime_main_args(); // Required for System.Environment.GetCommandLineArgs -#endif + if (err != OK) { + return false; + } - runtime_initialized = true; + void *lib = r_hostfxr_dll_handle; - print_verbose("Mono: Runtime initialized"); + void *symbol = nullptr; -#if defined(ANDROID_ENABLED) - gdmono::android::support::register_internal_calls(); -#endif + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_dotnet_command_line", symbol); + ERR_FAIL_COND_V(err != OK, false); + hostfxr_initialize_for_dotnet_command_line = (hostfxr_initialize_for_dotnet_command_line_fn)symbol; - // mscorlib assembly MUST be present at initialization - bool corlib_loaded = _load_corlib_assembly(); - ERR_FAIL_COND_MSG(!corlib_loaded, "Mono: Failed to load mscorlib assembly."); + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_initialize_for_runtime_config", symbol); + ERR_FAIL_COND_V(err != OK, false); + hostfxr_initialize_for_runtime_config = (hostfxr_initialize_for_runtime_config_fn)symbol; -#ifndef GD_MONO_SINGLE_APPDOMAIN - Error domain_load_err = _load_scripts_domain(); - ERR_FAIL_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain."); -#else - scripts_domain = root_domain; -#endif + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_get_runtime_delegate", symbol); + ERR_FAIL_COND_V(err != OK, false); + hostfxr_get_runtime_delegate = (hostfxr_get_runtime_delegate_fn)symbol; - _register_internal_calls(); + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "hostfxr_close", symbol); + ERR_FAIL_COND_V(err != OK, false); + hostfxr_close = (hostfxr_close_fn)symbol; - print_verbose("Mono: INITIALIZED"); + return (hostfxr_initialize_for_runtime_config && + hostfxr_get_runtime_delegate && + hostfxr_close); } -void GDMono::initialize_load_assemblies() { -#ifndef MONO_GLUE_ENABLED - CRASH_NOW_MSG("Mono: This binary was built with 'mono_glue=no'; cannot load assemblies."); -#endif - - // Load assemblies. The API and tools assemblies are required, - // the application is aborted if these assemblies cannot be loaded. - - _load_api_assemblies(); - -#if defined(TOOLS_ENABLED) - bool tool_assemblies_loaded = _load_tools_assemblies(); - CRASH_COND_MSG(!tool_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies."); - - if (Engine::get_singleton()->is_project_manager_hint()) { - return; - } -#endif - - // Load the project's main assembly. This doesn't necessarily need to succeed. - // The game may not be using .NET at all, or if the project does use .NET and - // we're running in the editor, it may just happen to be it wasn't built yet. - if (!_load_project_assembly()) { - if (OS::get_singleton()->is_stdout_verbose()) { - print_error("Mono: Failed to load project assembly"); - } - } -} - -bool GDMono::_are_api_assemblies_out_of_sync() { - bool out_of_sync = core_api_assembly.assembly && (core_api_assembly.out_of_sync || !GDMonoCache::cached_data.godot_api_cache_updated); #ifdef TOOLS_ENABLED - if (!out_of_sync) { - out_of_sync = editor_api_assembly.assembly && editor_api_assembly.out_of_sync; +load_assembly_and_get_function_pointer_fn initialize_hostfxr_for_config(const char_t *p_config_path) { + hostfxr_handle cxt = nullptr; + int rc = hostfxr_initialize_for_runtime_config(p_config_path, nullptr, &cxt); + if (rc != 0 || cxt == nullptr) { + hostfxr_close(cxt); + ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_runtime_config failed with code: " + itos(rc)); } -#endif - return out_of_sync; -} -namespace GodotSharpBindings { -#ifdef MONO_GLUE_ENABLED + void *load_assembly_and_get_function_pointer = nullptr; -uint64_t get_core_api_hash(); -#ifdef TOOLS_ENABLED -uint64_t get_editor_api_hash(); -#endif -uint32_t get_bindings_version(); -uint32_t get_cs_glue_version(); - -void register_generated_icalls(); + rc = hostfxr_get_runtime_delegate(cxt, + hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer); + if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) { + ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc)); + } -#else + hostfxr_close(cxt); -uint64_t get_core_api_hash() { - GD_UNREACHABLE(); -} -#ifdef TOOLS_ENABLED -uint64_t get_editor_api_hash() { - GD_UNREACHABLE(); -} -#endif -uint32_t get_bindings_version() { - GD_UNREACHABLE(); -} - -uint32_t get_cs_glue_version() { - GD_UNREACHABLE(); + return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer; } +#else +load_assembly_and_get_function_pointer_fn initialize_hostfxr_self_contained( + const char_t *p_main_assembly_path) { + hostfxr_handle cxt = nullptr; -void register_generated_icalls() { - /* Fine, just do nothing */ -} + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); -#endif // MONO_GLUE_ENABLED -} // namespace GodotSharpBindings + List<HostFxrCharString> argv_store; + Vector<const char_t *> argv; + argv.resize(cmdline_args.size() + 1); -void GDMono::_register_internal_calls() { - GodotSharpBindings::register_generated_icalls(); -} + argv.write[0] = p_main_assembly_path; -void GDMono::_init_godot_api_hashes() { -#if defined(MONO_GLUE_ENABLED) && defined(DEBUG_METHODS_ENABLED) - if (get_api_core_hash() != GodotSharpBindings::get_core_api_hash()) { - ERR_PRINT("Mono: Core API hash mismatch."); + int i = 1; + for (const String &E : cmdline_args) { + HostFxrCharString &stored = argv_store.push_back(str_to_hostfxr(E))->get(); + argv.write[i] = stored.ptr(); + i++; } -#ifdef TOOLS_ENABLED - if (get_api_editor_hash() != GodotSharpBindings::get_editor_api_hash()) { - ERR_PRINT("Mono: Editor API hash mismatch."); + int rc = hostfxr_initialize_for_dotnet_command_line(argv.size(), argv.ptrw(), nullptr, &cxt); + if (rc != 0 || cxt == nullptr) { + hostfxr_close(cxt); + ERR_FAIL_V_MSG(nullptr, "hostfxr_initialize_for_dotnet_command_line failed with code: " + itos(rc)); } -#endif // TOOLS_ENABLED -#endif // MONO_GLUE_ENABLED && DEBUG_METHODS_ENABLED -} -void GDMono::_init_exception_policy() { - PropertyInfo exc_policy_prop = PropertyInfo(Variant::INT, "mono/runtime/unhandled_exception_policy", PROPERTY_HINT_ENUM, - vformat("Terminate Application:%s,Log Error:%s", (int)POLICY_TERMINATE_APP, (int)POLICY_LOG_ERROR)); - unhandled_exception_policy = (UnhandledExceptionPolicy)(int)GLOBAL_DEF(exc_policy_prop.name, (int)POLICY_TERMINATE_APP); - ProjectSettings::get_singleton()->set_custom_property_info(exc_policy_prop.name, exc_policy_prop); + void *load_assembly_and_get_function_pointer = nullptr; - if (Engine::get_singleton()->is_editor_hint()) { - // Unhandled exceptions should not terminate the editor - unhandled_exception_policy = POLICY_LOG_ERROR; + rc = hostfxr_get_runtime_delegate(cxt, + hdt_load_assembly_and_get_function_pointer, &load_assembly_and_get_function_pointer); + if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) { + ERR_FAIL_V_MSG(nullptr, "hostfxr_get_runtime_delegate failed with code: " + itos(rc)); } -} - -void GDMono::add_assembly(int32_t p_domain_id, GDMonoAssembly *p_assembly) { - assemblies[p_domain_id][p_assembly->get_name()] = p_assembly; -} -GDMonoAssembly *GDMono::get_loaded_assembly(const String &p_name) { - if (p_name == "mscorlib" && corlib_assembly) { - return corlib_assembly; - } + hostfxr_close(cxt); - MonoDomain *domain = mono_domain_get(); - int32_t domain_id = domain ? mono_domain_get_id(domain) : 0; - GDMonoAssembly **result = assemblies[domain_id].getptr(p_name); - return result ? *result : nullptr; + return (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer; } - -bool GDMono::load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly) { -#ifdef DEBUG_ENABLED - CRASH_COND(!r_assembly); #endif - MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); - bool result = load_assembly(p_name, aname, r_assembly, p_refonly); - mono_assembly_name_free(aname); - mono_free(aname); - - return result; -} - -bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly) { -#ifdef DEBUG_ENABLED - CRASH_COND(!r_assembly); +#ifdef TOOLS_ENABLED +using godot_plugins_initialize_fn = bool (*)(void *, bool, gdmono::PluginCallbacks *, GDMonoCache::ManagedCallbacks *, const void **, int32_t); +#else +using godot_plugins_initialize_fn = bool (*)(void *, GDMonoCache::ManagedCallbacks *, const void **, int32_t); #endif - return load_assembly(p_name, p_aname, r_assembly, p_refonly, GDMonoAssembly::get_default_search_dirs()); -} +#ifdef TOOLS_ENABLED +godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) { + godot_plugins_initialize_fn godot_plugins_initialize = nullptr; -bool GDMono::load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs) { -#ifdef DEBUG_ENABLED - CRASH_COND(!r_assembly); -#endif + HostFxrCharString godot_plugins_path = str_to_hostfxr( + GodotSharpDirs::get_api_assemblies_dir().plus_file("GodotPlugins.dll")); - print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); + HostFxrCharString config_path = str_to_hostfxr( + GodotSharpDirs::get_api_assemblies_dir().plus_file("GodotPlugins.runtimeconfig.json")); - GDMonoAssembly *assembly = GDMonoAssembly::load(p_name, p_aname, p_refonly, p_search_dirs); + load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = + initialize_hostfxr_for_config(get_data(config_path)); + ERR_FAIL_NULL_V(load_assembly_and_get_function_pointer, nullptr); - if (!assembly) { - return false; - } + r_runtime_initialized = true; - *r_assembly = assembly; + print_verbose(".NET: hostfxr initialized"); - print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); + int rc = load_assembly_and_get_function_pointer(get_data(godot_plugins_path), + HOSTFXR_STR("GodotPlugins.Main, GodotPlugins"), + HOSTFXR_STR("InitializeFromEngine"), + UNMANAGEDCALLERSONLY_METHOD, + nullptr, + (void **)&godot_plugins_initialize); + ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer"); - return true; + return godot_plugins_initialize; } +#else +static String get_assembly_name() { + String assembly_name = ProjectSettings::get_singleton()->get_setting("dotnet/project/assembly_name"); -bool GDMono::load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly) { - CRASH_COND(!r_assembly); - - print_verbose("Mono: Loading assembly " + p_name + (p_refonly ? " (refonly)" : "") + "..."); - - GDMonoAssembly *assembly = GDMonoAssembly::load_from(p_name, p_path, p_refonly); - - if (!assembly) { - return false; + if (assembly_name.is_empty()) { + assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); } - *r_assembly = assembly; - - print_verbose("Mono: Assembly " + p_name + (p_refonly ? " (refonly)" : "") + " loaded from path: " + (*r_assembly)->get_path()); - - return true; + return assembly_name; } -ApiAssemblyInfo::Version ApiAssemblyInfo::Version::get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, ApiAssemblyInfo::Type p_api_type) { - ApiAssemblyInfo::Version api_assembly_version; +godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime_initialized) { + godot_plugins_initialize_fn godot_plugins_initialize = nullptr; - const char *nativecalls_name = p_api_type == ApiAssemblyInfo::API_CORE - ? BINDINGS_CLASS_NATIVECALLS - : BINDINGS_CLASS_NATIVECALLS_EDITOR; + String assembly_name = get_assembly_name(); - GDMonoClass *nativecalls_klass = p_api_assembly->get_class(BINDINGS_NAMESPACE, nativecalls_name); + HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir() + .plus_file(assembly_name + ".dll")); - if (nativecalls_klass) { - GDMonoField *api_hash_field = nativecalls_klass->get_field("godot_api_hash"); - if (api_hash_field) { - api_assembly_version.godot_api_hash = GDMonoMarshal::unbox<uint64_t>(api_hash_field->get_value(nullptr)); - } + load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = + initialize_hostfxr_self_contained(get_data(assembly_path)); + ERR_FAIL_NULL_V(load_assembly_and_get_function_pointer, nullptr); - GDMonoField *binds_ver_field = nativecalls_klass->get_field("bindings_version"); - if (binds_ver_field) { - api_assembly_version.bindings_version = GDMonoMarshal::unbox<uint32_t>(binds_ver_field->get_value(nullptr)); - } + r_runtime_initialized = true; - GDMonoField *cs_glue_ver_field = nativecalls_klass->get_field("cs_glue_version"); - if (cs_glue_ver_field) { - api_assembly_version.cs_glue_version = GDMonoMarshal::unbox<uint32_t>(cs_glue_ver_field->get_value(nullptr)); - } - } + print_verbose(".NET: hostfxr initialized"); - return api_assembly_version; -} + int rc = load_assembly_and_get_function_pointer(get_data(assembly_path), + str_to_hostfxr("GodotPlugins.Game.Main, " + assembly_name), + HOSTFXR_STR("InitializeFromGameProject"), + UNMANAGEDCALLERSONLY_METHOD, + nullptr, + (void **)&godot_plugins_initialize); + ERR_FAIL_COND_V_MSG(rc != 0, nullptr, ".NET: Failed to get GodotPlugins initialization function pointer"); -String ApiAssemblyInfo::to_string(ApiAssemblyInfo::Type p_type) { - return p_type == ApiAssemblyInfo::API_CORE ? "API_CORE" : "API_EDITOR"; + return godot_plugins_initialize; } -bool GDMono::_load_corlib_assembly() { - if (corlib_assembly) { - return true; - } - - bool success = load_assembly("mscorlib", &corlib_assembly); - - if (success) { - GDMonoCache::update_corlib_cache(); - } - - return success; -} +godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) { + String assembly_name = get_assembly_name(); -#ifdef TOOLS_ENABLED -bool GDMono::copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config) { - String src_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); - String dst_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config); - - String assembly_name = p_api_type == ApiAssemblyInfo::API_CORE ? CORE_API_ASSEMBLY_NAME : EDITOR_API_ASSEMBLY_NAME; +#if defined(WINDOWS_ENABLED) + String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".dll"); +#elif defined(MACOS_ENABLED) + String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".dylib"); +#elif defined(UNIX_ENABLED) + String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".so"); +#else +#error "Platform not supported (yet?)" +#endif - // Create destination directory if needed - if (!DirAccess::exists(dst_dir)) { - Ref<DirAccess> da = DirAccess::create_for_path(dst_dir); - Error err = da->make_dir_recursive(dst_dir); + if (FileAccess::exists(native_aot_so_path)) { + Error err = OS::get_singleton()->open_dynamic_library(native_aot_so_path, r_aot_dll_handle); if (err != OK) { - ERR_PRINT("Failed to create destination directory for the API assemblies. Error: " + itos(err) + "."); - return false; + return nullptr; } - } - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + void *lib = r_aot_dll_handle; - String xml_file = assembly_name + ".xml"; - if (da->copy(src_dir.plus_file(xml_file), dst_dir.plus_file(xml_file)) != OK) { - WARN_PRINT("Failed to copy '" + xml_file + "'."); - } + void *symbol = nullptr; - String pdb_file = assembly_name + ".pdb"; - if (da->copy(src_dir.plus_file(pdb_file), dst_dir.plus_file(pdb_file)) != OK) { - WARN_PRINT("Failed to copy '" + pdb_file + "'."); + err = OS::get_singleton()->get_dynamic_library_symbol_handle(lib, "godotsharp_game_main_init", symbol); + ERR_FAIL_COND_V(err != OK, nullptr); + return (godot_plugins_initialize_fn)symbol; } - String assembly_file = assembly_name + ".dll"; - if (da->copy(src_dir.plus_file(assembly_file), dst_dir.plus_file(assembly_file)) != OK) { - ERR_PRINT("Failed to copy '" + assembly_file + "'."); - return false; - } - - return true; + return nullptr; } +#endif -static bool try_get_cached_api_hash_for(const String &p_api_assemblies_dir, bool &r_out_of_sync) { - String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - if (!FileAccess::exists(core_api_assembly_path) || !FileAccess::exists(editor_api_assembly_path)) { - return false; - } - - String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg"); +} // namespace - if (!FileAccess::exists(cached_api_hash_path)) { +static bool _on_core_api_assembly_loaded() { + if (!GDMonoCache::godot_api_cache_updated) { return false; } - Ref<ConfigFile> cfg; - cfg.instantiate(); - Error cfg_err = cfg->load(cached_api_hash_path); - ERR_FAIL_COND_V(cfg_err != OK, false); - - // Checking the modified time is good enough - if (FileAccess::get_modified_time(core_api_assembly_path) != (uint64_t)cfg->get_value("core", "modified_time") || - FileAccess::get_modified_time(editor_api_assembly_path) != (uint64_t)cfg->get_value("editor", "modified_time")) { - return false; - } + bool debug; +#ifdef DEBUG_ENABLED + debug = true; +#else + debug = false; +#endif - r_out_of_sync = GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("core", "bindings_version") || - GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("core", "cs_glue_version") || - GodotSharpBindings::get_bindings_version() != (uint32_t)cfg->get_value("editor", "bindings_version") || - GodotSharpBindings::get_cs_glue_version() != (uint32_t)cfg->get_value("editor", "cs_glue_version") || - GodotSharpBindings::get_core_api_hash() != (uint64_t)cfg->get_value("core", "api_hash") || - GodotSharpBindings::get_editor_api_hash() != (uint64_t)cfg->get_value("editor", "api_hash"); + GDMonoCache::managed_callbacks.GD_OnCoreApiAssemblyLoaded(debug); return true; } -static void create_cached_api_hash_for(const String &p_api_assemblies_dir) { - String core_api_assembly_path = p_api_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String editor_api_assembly_path = p_api_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - String cached_api_hash_path = p_api_assemblies_dir.plus_file("api_hash_cache.cfg"); - - Ref<ConfigFile> cfg; - cfg.instantiate(); - - cfg->set_value("core", "modified_time", FileAccess::get_modified_time(core_api_assembly_path)); - cfg->set_value("editor", "modified_time", FileAccess::get_modified_time(editor_api_assembly_path)); - - cfg->set_value("core", "bindings_version", GodotSharpBindings::get_bindings_version()); - cfg->set_value("core", "cs_glue_version", GodotSharpBindings::get_cs_glue_version()); - cfg->set_value("editor", "bindings_version", GodotSharpBindings::get_bindings_version()); - cfg->set_value("editor", "cs_glue_version", GodotSharpBindings::get_cs_glue_version()); - - // This assumes the prebuilt api assemblies we copied to the project are not out of sync - cfg->set_value("core", "api_hash", GodotSharpBindings::get_core_api_hash()); - cfg->set_value("editor", "api_hash", GodotSharpBindings::get_editor_api_hash()); - - Error err = cfg->save(cached_api_hash_path); - ERR_FAIL_COND(err != OK); -} - -bool GDMono::_temp_domain_load_are_assemblies_out_of_sync(const String &p_config) { - MonoDomain *temp_domain = GDMonoUtils::create_domain("GodotEngine.Domain.CheckApiAssemblies"); - ERR_FAIL_NULL_V(temp_domain, "Failed to create temporary domain to check API assemblies"); - _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(temp_domain); - - _GDMONO_SCOPE_DOMAIN_(temp_domain); - - GDMono::LoadedApiAssembly temp_core_api_assembly; - GDMono::LoadedApiAssembly temp_editor_api_assembly; - - if (!_try_load_api_assemblies(temp_core_api_assembly, temp_editor_api_assembly, - p_config, /* refonly: */ true, /* loaded_callback: */ nullptr)) { - return temp_core_api_assembly.out_of_sync || temp_editor_api_assembly.out_of_sync; - } - - return true; // Failed to load, assume they're outdated assemblies -} - -String GDMono::update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync, const bool *p_editor_api_out_of_sync) { -#define FAIL_REASON(m_out_of_sync, m_prebuilt_exists) \ - ( \ - (m_out_of_sync ? String("The assembly is invalidated ") : String("The assembly was not found ")) + \ - (m_prebuilt_exists ? String("and the prebuilt assemblies are missing.") : String("and we failed to copy the prebuilt assemblies."))) +void GDMono::initialize() { + print_verbose(".NET: Initializing module..."); - String dst_assemblies_dir = GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config); + _init_godot_api_hashes(); - String core_assembly_path = dst_assemblies_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String editor_assembly_path = dst_assemblies_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); + godot_plugins_initialize_fn godot_plugins_initialize = nullptr; - bool api_assemblies_out_of_sync = false; + if (!load_hostfxr(hostfxr_dll_handle)) { +#if !defined(TOOLS_ENABLED) + godot_plugins_initialize = try_load_native_aot_library(hostfxr_dll_handle); - if (p_core_api_out_of_sync && p_editor_api_out_of_sync) { - api_assemblies_out_of_sync = p_core_api_out_of_sync || p_editor_api_out_of_sync; - } else if (FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) { - // Determine if they're out of sync - if (!try_get_cached_api_hash_for(dst_assemblies_dir, api_assemblies_out_of_sync)) { - api_assemblies_out_of_sync = _temp_domain_load_are_assemblies_out_of_sync(p_config); + if (godot_plugins_initialize != nullptr) { + is_native_aot = true; + } else { + ERR_FAIL_MSG(".NET: Failed to load hostfxr"); } +#else + ERR_FAIL_MSG(".NET: Failed to load hostfxr"); +#endif } - // Note: Even if only one of the assemblies if missing or out of sync, we update both - - if (!api_assemblies_out_of_sync && FileAccess::exists(core_assembly_path) && FileAccess::exists(editor_assembly_path)) { - return String(); // No update needed - } - - print_verbose("Updating '" + p_config + "' API assemblies"); - - String prebuilt_api_dir = GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); - String prebuilt_core_dll_path = prebuilt_api_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - String prebuilt_editor_dll_path = prebuilt_api_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - if (!FileAccess::exists(prebuilt_core_dll_path) || !FileAccess::exists(prebuilt_editor_dll_path)) { - return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ false); + if (!is_native_aot) { + godot_plugins_initialize = initialize_hostfxr_and_godot_plugins(runtime_initialized); + ERR_FAIL_NULL(godot_plugins_initialize); } - // Copy the prebuilt Api - if (!copy_prebuilt_api_assembly(ApiAssemblyInfo::API_CORE, p_config) || - !copy_prebuilt_api_assembly(ApiAssemblyInfo::API_EDITOR, p_config)) { - return FAIL_REASON(api_assemblies_out_of_sync, /* prebuilt_exists: */ true); - } + int32_t interop_funcs_size = 0; + const void **interop_funcs = godotsharp::get_runtime_interop_funcs(interop_funcs_size); - // Cache the api hash of the assemblies we just copied - create_cached_api_hash_for(dst_assemblies_dir); + GDMonoCache::ManagedCallbacks managed_callbacks{}; - return String(); // Updated successfully + void *godot_dll_handle = nullptr; -#undef FAIL_REASON -} +#if defined(UNIX_ENABLED) && !defined(MACOS_ENABLED) && !defined(IOS_ENABLED) + // Managed code can access it on its own on other platforms + godot_dll_handle = dlopen(nullptr, RTLD_NOW); #endif -bool GDMono::_load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) { - if (r_loaded_api_assembly.assembly) { - return true; - } - #ifdef TOOLS_ENABLED - // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date - - // If running the project manager, load it from the prebuilt API directory - String assembly_dir = !Engine::get_singleton()->is_project_manager_hint() - ? GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) - : GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); - - String assembly_path = assembly_dir.plus_file(CORE_API_ASSEMBLY_NAME ".dll"); - - bool success = FileAccess::exists(assembly_path) && - load_assembly_from(CORE_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly); + gdmono::PluginCallbacks plugin_callbacks_res; + bool init_ok = godot_plugins_initialize(godot_dll_handle, + Engine::get_singleton()->is_editor_hint(), + &plugin_callbacks_res, &managed_callbacks, + interop_funcs, interop_funcs_size); + ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed"); + + plugin_callbacks = plugin_callbacks_res; #else - bool success = load_assembly(CORE_API_ASSEMBLY_NAME, &r_loaded_api_assembly.assembly, p_refonly); + bool init_ok = godot_plugins_initialize(godot_dll_handle, &managed_callbacks, + interop_funcs, interop_funcs_size); + ERR_FAIL_COND_MSG(!init_ok, ".NET: GodotPlugins initialization failed"); #endif - if (success) { - ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_CORE); - r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_core_api_hash() != api_assembly_ver.godot_api_hash || - GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; - } else { - r_loaded_api_assembly.out_of_sync = false; - } + GDMonoCache::update_godot_api_cache(managed_callbacks); - return success; -} + print_verbose(".NET: GodotPlugins initialized"); -#ifdef TOOLS_ENABLED -bool GDMono::_load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly) { - if (r_loaded_api_assembly.assembly) { - return true; - } - - // For the editor and the editor player we want to load it from a specific path to make sure we can keep it up to date - - // If running the project manager, load it from the prebuilt API directory - String assembly_dir = !Engine::get_singleton()->is_project_manager_hint() - ? GodotSharpDirs::get_res_assemblies_base_dir().plus_file(p_config) - : GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file(p_config); - - String assembly_path = assembly_dir.plus_file(EDITOR_API_ASSEMBLY_NAME ".dll"); - - bool success = FileAccess::exists(assembly_path) && - load_assembly_from(EDITOR_API_ASSEMBLY_NAME, assembly_path, &r_loaded_api_assembly.assembly, p_refonly); - - if (success) { - ApiAssemblyInfo::Version api_assembly_ver = ApiAssemblyInfo::Version::get_from_loaded_assembly(r_loaded_api_assembly.assembly, ApiAssemblyInfo::API_EDITOR); - r_loaded_api_assembly.out_of_sync = GodotSharpBindings::get_editor_api_hash() != api_assembly_ver.godot_api_hash || - GodotSharpBindings::get_bindings_version() != api_assembly_ver.bindings_version || - GodotSharpBindings::get_cs_glue_version() != api_assembly_ver.cs_glue_version; - } else { - r_loaded_api_assembly.out_of_sync = false; - } - - return success; + _on_core_api_assembly_loaded(); } -#endif -bool GDMono::_try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly, - const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback) { - if (!_load_core_api_assembly(r_core_api_assembly, p_config, p_refonly)) { - if (OS::get_singleton()->is_stdout_verbose()) { - print_error("Mono: Failed to load Core API assembly"); - } - return false; +#ifdef TOOLS_ENABLED +void GDMono::initialize_load_assemblies() { + if (Engine::get_singleton()->is_project_manager_hint()) { + return; } -#ifdef TOOLS_ENABLED - if (!_load_editor_api_assembly(r_editor_api_assembly, p_config, p_refonly)) { + // Load the project's main assembly. This doesn't necessarily need to succeed. + // The game may not be using .NET at all, or if the project does use .NET and + // we're running in the editor, it may just happen to be it wasn't built yet. + if (!_load_project_assembly()) { if (OS::get_singleton()->is_stdout_verbose()) { - print_error("Mono: Failed to load Editor API assembly"); + print_error(".NET: Failed to load project assembly"); } - return false; } - - if (r_editor_api_assembly.out_of_sync) { - return false; - } -#endif - - // Check if the core API assembly is out of sync only after trying to load the - // editor API assembly. Otherwise, if both assemblies are out of sync, we would - // only update the former as we won't know the latter also needs to be updated. - if (r_core_api_assembly.out_of_sync) { - return false; - } - - if (p_callback) { - return p_callback(); - } - - return true; } - -bool GDMono::_on_core_api_assembly_loaded() { - GDMonoCache::update_godot_api_cache(); - - if (!GDMonoCache::cached_data.godot_api_cache_updated) { - return false; - } - - get_singleton()->_install_trace_listener(); - - return true; -} - -bool GDMono::_try_load_api_assemblies_preset() { - return _try_load_api_assemblies(core_api_assembly, editor_api_assembly, - get_expected_api_build_config(), /* refonly: */ false, _on_core_api_assembly_loaded); -} - -void GDMono::_load_api_assemblies() { - bool api_assemblies_loaded = _try_load_api_assemblies_preset(); - -#if defined(TOOLS_ENABLED) && !defined(GD_MONO_SINGLE_APPDOMAIN) - if (!api_assemblies_loaded) { - // The API assemblies are out of sync or some other error happened. Fine, try one more time, but - // this time update them from the prebuilt assemblies directory before trying to load them again. - - // Shouldn't happen. The project manager loads the prebuilt API assemblies - CRASH_COND_MSG(Engine::get_singleton()->is_project_manager_hint(), "Failed to load one of the prebuilt API assemblies."); - - // 1. Unload the scripts domain - Error domain_unload_err = _unload_scripts_domain(); - CRASH_COND_MSG(domain_unload_err != OK, "Mono: Failed to unload scripts domain."); - - // 2. Update the API assemblies - String update_error = update_api_assemblies_from_prebuilt("Debug", &core_api_assembly.out_of_sync, &editor_api_assembly.out_of_sync); - CRASH_COND_MSG(!update_error.is_empty(), update_error); - - // 3. Load the scripts domain again - Error domain_load_err = _load_scripts_domain(); - CRASH_COND_MSG(domain_load_err != OK, "Mono: Failed to load scripts domain."); - - // 4. Try loading the updated assemblies - api_assemblies_loaded = _try_load_api_assemblies_preset(); - } #endif - if (!api_assemblies_loaded) { - // welp... too bad - - if (_are_api_assemblies_out_of_sync()) { - if (core_api_assembly.out_of_sync) { - ERR_PRINT("The assembly '" CORE_API_ASSEMBLY_NAME "' is out of sync."); - } else if (!GDMonoCache::cached_data.godot_api_cache_updated) { - ERR_PRINT("The loaded assembly '" CORE_API_ASSEMBLY_NAME "' is in sync, but the cache update failed."); - } +void GDMono::_init_godot_api_hashes() { +#ifdef DEBUG_METHODS_ENABLED + get_api_core_hash(); #ifdef TOOLS_ENABLED - if (editor_api_assembly.out_of_sync) { - ERR_PRINT("The assembly '" EDITOR_API_ASSEMBLY_NAME "' is out of sync."); - } -#endif - - CRASH_NOW(); - } else { - CRASH_NOW_MSG("Failed to load one of the API assemblies."); - } - } + get_api_editor_hash(); +#endif // TOOLS_ENABLED +#endif // DEBUG_METHODS_ENABLED } #ifdef TOOLS_ENABLED -bool GDMono::_load_tools_assemblies() { - if (tools_assembly && tools_project_editor_assembly) { - return true; - } - - bool success = load_assembly(TOOLS_ASM_NAME, &tools_assembly) && - load_assembly(TOOLS_PROJECT_EDITOR_ASM_NAME, &tools_project_editor_assembly); - - return success; -} -#endif - bool GDMono::_load_project_assembly() { - if (project_assembly) { - return true; - } + String assembly_name = ProjectSettings::get_singleton()->get_setting("dotnet/project/assembly_name"); - String appname = ProjectSettings::get_singleton()->get("application/config/name"); - String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - if (appname_safe.is_empty()) { - appname_safe = "UnnamedProject"; + if (assembly_name.is_empty()) { + assembly_name = ProjectSettings::get_singleton()->get_safe_project_name(); } - bool success = load_assembly(appname_safe, &project_assembly); + String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() + .plus_file(assembly_name + ".dll"); + assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path); + + String loaded_assembly_path; + bool success = plugin_callbacks.LoadProjectAssemblyCallback(assembly_path.utf16(), &loaded_assembly_path); if (success) { - mono_assembly_set_main(project_assembly->get_assembly()); - CSharpLanguage::get_singleton()->lookup_scripts_in_assembly(project_assembly); + project_assembly_path = loaded_assembly_path.simplify_path(); + project_assembly_modified_time = FileAccess::get_modified_time(loaded_assembly_path); } return success; } - -void GDMono::_install_trace_listener() { -#ifdef DEBUG_ENABLED - // Install the trace listener now before the project assembly is loaded - GDMonoClass *debug_utils = get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, "DebuggingUtils"); - GDMonoMethod *install_func = debug_utils->get_method("InstallTraceListener"); - - MonoException *exc = nullptr; - install_func->invoke_raw(nullptr, nullptr, &exc); - if (exc) { - GDMonoUtils::debug_print_unhandled_exception(exc); - ERR_PRINT("Failed to install 'System.Diagnostics.Trace' listener."); - } -#endif -} - -#ifndef GD_MONO_SINGLE_APPDOMAIN -Error GDMono::_load_scripts_domain() { - ERR_FAIL_COND_V(scripts_domain != nullptr, ERR_BUG); - - print_verbose("Mono: Loading scripts domain..."); - - scripts_domain = GDMonoUtils::create_domain("GodotEngine.Domain.Scripts"); - - ERR_FAIL_NULL_V_MSG(scripts_domain, ERR_CANT_CREATE, "Mono: Could not create scripts app domain."); - - mono_domain_set(scripts_domain, true); - - return OK; -} - -Error GDMono::_unload_scripts_domain() { - ERR_FAIL_NULL_V(scripts_domain, ERR_BUG); - - print_verbose("Mono: Finalizing scripts domain..."); - - if (mono_domain_get() != root_domain) { - mono_domain_set(root_domain, true); - } - - finalizing_scripts_domain = true; - - if (!mono_domain_finalize(scripts_domain, 2000)) { - ERR_PRINT("Mono: Domain finalization timeout."); - } - - finalizing_scripts_domain = false; - - mono_gc_collect(mono_gc_max_generation()); - - GDMonoCache::clear_godot_api_cache(); - - _domain_assemblies_cleanup(mono_domain_get_id(scripts_domain)); - - core_api_assembly.assembly = nullptr; -#ifdef TOOLS_ENABLED - editor_api_assembly.assembly = nullptr; -#endif - - project_assembly = nullptr; -#ifdef TOOLS_ENABLED - tools_assembly = nullptr; - tools_project_editor_assembly = nullptr; -#endif - - MonoDomain *domain = scripts_domain; - scripts_domain = nullptr; - - print_verbose("Mono: Unloading scripts domain..."); - - MonoException *exc = nullptr; - mono_domain_try_unload(domain, (MonoObject **)&exc); - - if (exc) { - ERR_PRINT("Exception thrown when unloading scripts domain."); - GDMonoUtils::debug_unhandled_exception(exc); - return FAILED; - } - - return OK; -} #endif #ifdef GD_MONO_HOT_RELOAD -Error GDMono::reload_scripts_domain() { +Error GDMono::reload_project_assemblies() { ERR_FAIL_COND_V(!runtime_initialized, ERR_BUG); - if (scripts_domain) { - Error domain_unload_err = _unload_scripts_domain(); - ERR_FAIL_COND_V_MSG(domain_unload_err != OK, domain_unload_err, "Mono: Failed to unload scripts domain."); - } - - CSharpLanguage::get_singleton()->_on_scripts_domain_unloaded(); - - Error domain_load_err = _load_scripts_domain(); - ERR_FAIL_COND_V_MSG(domain_load_err != OK, domain_load_err, "Mono: Failed to load scripts domain."); + finalizing_scripts_domain = true; - // Load assemblies. The API and tools assemblies are required, - // the application is aborted if these assemblies cannot be loaded. + CSharpLanguage::get_singleton()->_on_scripts_domain_about_to_unload(); - _load_api_assemblies(); + if (!get_plugin_callbacks().UnloadProjectPluginCallback()) { + ERR_FAIL_V_MSG(Error::FAILED, ".NET: Failed to unload assemblies."); + } -#if defined(TOOLS_ENABLED) - bool tools_assemblies_loaded = _load_tools_assemblies(); - CRASH_COND_MSG(!tools_assemblies_loaded, "Mono: Failed to load '" TOOLS_ASM_NAME "' assemblies."); -#endif + finalizing_scripts_domain = false; // Load the project's main assembly. Here, during hot-reloading, we do // consider failing to load the project's main assembly to be an error. - // However, unlike the API and tools assemblies, the application can continue working. if (!_load_project_assembly()) { - print_error("Mono: Failed to load project assembly"); + print_error(".NET: Failed to load project assembly."); return ERR_CANT_OPEN; } @@ -1123,204 +554,38 @@ Error GDMono::reload_scripts_domain() { } #endif -#ifndef GD_MONO_SINGLE_APPDOMAIN -Error GDMono::finalize_and_unload_domain(MonoDomain *p_domain) { - CRASH_COND(p_domain == nullptr); - CRASH_COND(p_domain == GDMono::get_singleton()->get_scripts_domain()); // Should use _unload_scripts_domain() instead - - String domain_name = mono_domain_get_friendly_name(p_domain); - - print_verbose("Mono: Unloading domain '" + domain_name + "'..."); - - if (mono_domain_get() == p_domain) { - mono_domain_set(root_domain, true); - } - - if (!mono_domain_finalize(p_domain, 2000)) { - ERR_PRINT("Mono: Domain finalization timeout."); - } - - mono_gc_collect(mono_gc_max_generation()); - - _domain_assemblies_cleanup(mono_domain_get_id(p_domain)); - - MonoException *exc = nullptr; - mono_domain_try_unload(p_domain, (MonoObject **)&exc); - - if (exc) { - ERR_PRINT("Exception thrown when unloading domain '" + domain_name + "'."); - GDMonoUtils::debug_print_unhandled_exception(exc); - return FAILED; - } - - return OK; -} -#endif - -GDMonoClass *GDMono::get_class(MonoClass *p_raw_class) { - MonoImage *image = mono_class_get_image(p_raw_class); - - if (image == corlib_assembly->get_image()) { - return corlib_assembly->get_class(p_raw_class); - } - - int32_t domain_id = mono_domain_get_id(mono_domain_get()); - HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; - - for (const KeyValue<String, GDMonoAssembly *> &E : domain_assemblies) { - GDMonoAssembly *assembly = E.value; - if (assembly->get_image() == image) { - GDMonoClass *klass = assembly->get_class(p_raw_class); - if (klass) { - return klass; - } - } - } - - return nullptr; -} - -GDMonoClass *GDMono::get_class(const StringName &p_namespace, const StringName &p_name) { - GDMonoClass *klass = corlib_assembly->get_class(p_namespace, p_name); - if (klass) { - return klass; - } - - int32_t domain_id = mono_domain_get_id(mono_domain_get()); - HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[domain_id]; - - for (const KeyValue<String, GDMonoAssembly *> &E : domain_assemblies) { - GDMonoAssembly *assembly = E.value; - klass = assembly->get_class(p_namespace, p_name); - if (klass) { - return klass; - } - } - - return nullptr; -} - -void GDMono::_domain_assemblies_cleanup(int32_t p_domain_id) { - HashMap<String, GDMonoAssembly *> &domain_assemblies = assemblies[p_domain_id]; - - for (const KeyValue<String, GDMonoAssembly *> &E : domain_assemblies) { - memdelete(E.value); - } - - assemblies.erase(p_domain_id); -} - -void GDMono::unhandled_exception_hook(MonoObject *p_exc, void *) { - // This method will be called by the runtime when a thrown exception is not handled. - // It won't be called when we manually treat a thrown exception as unhandled. - // We assume the exception was already printed before calling this hook. - -#ifdef DEBUG_ENABLED - GDMonoUtils::debug_send_unhandled_exception_error((MonoException *)p_exc); - if (EngineDebugger::is_active()) { - EngineDebugger::get_singleton()->poll_events(false); - } -#endif - - exit(mono_environment_exitcode_get()); - - GD_UNREACHABLE(); -} - GDMono::GDMono() { singleton = this; - gdmono_log = memnew(GDMonoLog); - runtime_initialized = false; finalizing_scripts_domain = false; - root_domain = nullptr; - scripts_domain = nullptr; - - corlib_assembly = nullptr; - project_assembly = nullptr; -#ifdef TOOLS_ENABLED - tools_assembly = nullptr; - tools_project_editor_assembly = nullptr; -#endif - api_core_hash = 0; #ifdef TOOLS_ENABLED api_editor_hash = 0; #endif - - unhandled_exception_policy = POLICY_TERMINATE_APP; } GDMono::~GDMono() { - if (is_runtime_initialized()) { -#ifndef GD_MONO_SINGLE_APPDOMAIN - if (scripts_domain) { - Error err = _unload_scripts_domain(); - if (err != OK) { - ERR_PRINT("Mono: Failed to unload scripts domain."); - } - } -#else - CRASH_COND(scripts_domain != root_domain); - - print_verbose("Mono: Finalizing scripts domain..."); - - if (mono_domain_get() != root_domain) { - mono_domain_set(root_domain, true); - } - - finalizing_scripts_domain = true; - - if (!mono_domain_finalize(root_domain, 2000)) { - ERR_PRINT("Mono: Domain finalization timeout."); - } - - finalizing_scripts_domain = false; - - mono_gc_collect(mono_gc_max_generation()); - - GDMonoCache::clear_godot_api_cache(); - - _domain_assemblies_cleanup(mono_domain_get_id(root_domain)); - - core_api_assembly.assembly = nullptr; - - project_assembly = nullptr; - - root_domain = nullptr; - scripts_domain = nullptr; - - // Leave the rest to 'mono_jit_cleanup' -#endif - - for (const KeyValue<int32_t, HashMap<String, GDMonoAssembly *>> &E : assemblies) { - const HashMap<String, GDMonoAssembly *> &domain_assemblies = E.value; + finalizing_scripts_domain = true; - for (const KeyValue<String, GDMonoAssembly *> &F : domain_assemblies) { - memdelete(F.value); - } + if (is_runtime_initialized()) { + if (GDMonoCache::godot_api_cache_updated) { + GDMonoCache::managed_callbacks.DisposablesTracker_OnGodotShuttingDown(); } - assemblies.clear(); - - print_verbose("Mono: Runtime cleanup..."); - - mono_jit_cleanup(root_domain); - - print_verbose("Mono: Finalized"); + } - runtime_initialized = false; + if (hostfxr_dll_handle) { + OS::get_singleton()->close_dynamic_library(hostfxr_dll_handle); } + finalizing_scripts_domain = false; + runtime_initialized = false; + #if defined(ANDROID_ENABLED) gdmono::android::support::cleanup(); #endif - if (gdmono_log) { - memdelete(gdmono_log); - } - singleton = nullptr; } @@ -1328,62 +593,7 @@ namespace mono_bind { GodotSharp *GodotSharp::singleton = nullptr; -void GodotSharp::attach_thread() { - GDMonoUtils::attach_current_thread(); -} - -void GodotSharp::detach_thread() { - GDMonoUtils::detach_current_thread(); -} - -int32_t GodotSharp::get_domain_id() { - MonoDomain *domain = mono_domain_get(); - ERR_FAIL_NULL_V(domain, -1); - return mono_domain_get_id(domain); -} - -int32_t GodotSharp::get_scripts_domain_id() { - ERR_FAIL_NULL_V_MSG(GDMono::get_singleton(), - -1, "The Mono runtime is not initialized"); - MonoDomain *domain = GDMono::get_singleton()->get_scripts_domain(); - ERR_FAIL_NULL_V(domain, -1); - return mono_domain_get_id(domain); -} - -bool GodotSharp::is_scripts_domain_loaded() { - return GDMono::get_singleton() != nullptr && - GDMono::get_singleton()->is_runtime_initialized() && - GDMono::get_singleton()->get_scripts_domain() != nullptr; -} - -bool GodotSharp::_is_domain_finalizing_for_unload(int32_t p_domain_id) { - return is_domain_finalizing_for_unload(p_domain_id); -} - -bool GodotSharp::is_domain_finalizing_for_unload(int32_t p_domain_id) { - return is_domain_finalizing_for_unload(mono_domain_get_by_id(p_domain_id)); -} - -bool GodotSharp::is_domain_finalizing_for_unload(MonoDomain *p_domain) { - GDMono *gd_mono = GDMono::get_singleton(); - - ERR_FAIL_COND_V_MSG(!gd_mono || !gd_mono->is_runtime_initialized(), - false, "The Mono runtime is not initialized"); - - ERR_FAIL_NULL_V(p_domain, true); - - if (p_domain == gd_mono->get_scripts_domain() && gd_mono->is_finalizing_scripts_domain()) { - return true; - } - - return mono_domain_is_unloading(p_domain); -} - -bool GodotSharp::is_runtime_shutting_down() { - return mono_runtime_is_shutting_down(); -} - -bool GodotSharp::is_runtime_initialized() { +bool GodotSharp::_is_runtime_initialized() { return GDMono::get_singleton() != nullptr && GDMono::get_singleton()->is_runtime_initialized(); } @@ -1399,16 +609,7 @@ void GodotSharp::_reload_assemblies(bool p_soft_reload) { } void GodotSharp::_bind_methods() { - ClassDB::bind_method(D_METHOD("attach_thread"), &GodotSharp::attach_thread); - ClassDB::bind_method(D_METHOD("detach_thread"), &GodotSharp::detach_thread); - - ClassDB::bind_method(D_METHOD("get_domain_id"), &GodotSharp::get_domain_id); - ClassDB::bind_method(D_METHOD("get_scripts_domain_id"), &GodotSharp::get_scripts_domain_id); - ClassDB::bind_method(D_METHOD("is_scripts_domain_loaded"), &GodotSharp::is_scripts_domain_loaded); - ClassDB::bind_method(D_METHOD("is_domain_finalizing_for_unload", "domain_id"), &GodotSharp::_is_domain_finalizing_for_unload); - - ClassDB::bind_method(D_METHOD("is_runtime_shutting_down"), &GodotSharp::is_runtime_shutting_down); - ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &GodotSharp::is_runtime_initialized); + ClassDB::bind_method(D_METHOD("is_runtime_initialized"), &GodotSharp::_is_runtime_initialized); ClassDB::bind_method(D_METHOD("_reload_assemblies"), &GodotSharp::_reload_assemblies); } diff --git a/modules/mono/mono_gd/gd_mono.h b/modules/mono/mono_gd/gd_mono.h index 51fd0f8483..43811a4325 100644 --- a/modules/mono/mono_gd/gd_mono.h +++ b/modules/mono/mono_gd/gd_mono.h @@ -34,131 +34,54 @@ #include "core/io/config_file.h" #include "../godotsharp_defs.h" -#include "gd_mono_assembly.h" -#include "gd_mono_log.h" -#ifdef WINDOWS_ENABLED -#include "../utils/mono_reg_utils.h" +#ifdef WIN32 +#define GD_CLR_STDCALL __stdcall +#else +#define GD_CLR_STDCALL #endif -namespace ApiAssemblyInfo { -enum Type { - API_CORE, - API_EDITOR -}; - -struct Version { - uint64_t godot_api_hash = 0; - uint32_t bindings_version = 0; - uint32_t cs_glue_version = 0; - - bool operator==(const Version &p_other) const { - return godot_api_hash == p_other.godot_api_hash && - bindings_version == p_other.bindings_version && - cs_glue_version == p_other.cs_glue_version; - } - - Version() {} - - Version(uint64_t p_godot_api_hash, - uint32_t p_bindings_version, - uint32_t p_cs_glue_version) : - godot_api_hash(p_godot_api_hash), - bindings_version(p_bindings_version), - cs_glue_version(p_cs_glue_version) { - } +namespace gdmono { - static Version get_from_loaded_assembly(GDMonoAssembly *p_api_assembly, Type p_api_type); +#ifdef TOOLS_ENABLED +struct PluginCallbacks { + using FuncLoadProjectAssemblyCallback = bool(GD_CLR_STDCALL *)(const char16_t *, String *); + using FuncLoadToolsAssemblyCallback = Object *(GD_CLR_STDCALL *)(const char16_t *, const void **, int32_t); + using FuncUnloadProjectPluginCallback = bool(GD_CLR_STDCALL *)(); + FuncLoadProjectAssemblyCallback LoadProjectAssemblyCallback = nullptr; + FuncLoadToolsAssemblyCallback LoadToolsAssemblyCallback = nullptr; + FuncUnloadProjectPluginCallback UnloadProjectPluginCallback = nullptr; }; +#endif -String to_string(Type p_type); -} // namespace ApiAssemblyInfo - -class GDMono { -public: - enum UnhandledExceptionPolicy { - POLICY_TERMINATE_APP, - POLICY_LOG_ERROR - }; - - struct LoadedApiAssembly { - GDMonoAssembly *assembly = nullptr; - bool out_of_sync = false; +} // namespace gdmono - LoadedApiAssembly() {} - }; +#undef GD_CLR_STDCALL -private: +class GDMono { bool runtime_initialized; bool finalizing_scripts_domain; - UnhandledExceptionPolicy unhandled_exception_policy; + void *hostfxr_dll_handle = nullptr; + bool is_native_aot = false; - MonoDomain *root_domain = nullptr; - MonoDomain *scripts_domain = nullptr; + String project_assembly_path; + uint64_t project_assembly_modified_time = 0; - HashMap<int32_t, HashMap<String, GDMonoAssembly *>> assemblies; - - GDMonoAssembly *corlib_assembly = nullptr; - GDMonoAssembly *project_assembly = nullptr; #ifdef TOOLS_ENABLED - GDMonoAssembly *tools_assembly = nullptr; - GDMonoAssembly *tools_project_editor_assembly = nullptr; -#endif - - LoadedApiAssembly core_api_assembly; - LoadedApiAssembly editor_api_assembly; - - typedef bool (*CoreApiAssemblyLoadedCallback)(); - - bool _are_api_assemblies_out_of_sync(); - bool _temp_domain_load_are_assemblies_out_of_sync(const String &p_config); - - bool _load_core_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly); -#ifdef TOOLS_ENABLED - bool _load_editor_api_assembly(LoadedApiAssembly &r_loaded_api_assembly, const String &p_config, bool p_refonly); -#endif - - static bool _on_core_api_assembly_loaded(); - - bool _load_corlib_assembly(); -#ifdef TOOLS_ENABLED - bool _load_tools_assemblies(); -#endif bool _load_project_assembly(); - - bool _try_load_api_assemblies(LoadedApiAssembly &r_core_api_assembly, LoadedApiAssembly &r_editor_api_assembly, - const String &p_config, bool p_refonly, CoreApiAssemblyLoadedCallback p_callback); - bool _try_load_api_assemblies_preset(); - void _load_api_assemblies(); - - void _install_trace_listener(); - - void _register_internal_calls(); - -#ifndef GD_MONO_SINGLE_APPDOMAIN - Error _load_scripts_domain(); - Error _unload_scripts_domain(); #endif - void _domain_assemblies_cleanup(int32_t p_domain_id); - uint64_t api_core_hash; #ifdef TOOLS_ENABLED uint64_t api_editor_hash; #endif void _init_godot_api_hashes(); - void _init_exception_policy(); - - GDMonoLog *gdmono_log = nullptr; -#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) - MonoRegInfo mono_reg_info; +#ifdef TOOLS_ENABLED + gdmono::PluginCallbacks plugin_callbacks; #endif - void add_mono_shared_libs_dir_to_path(); - void determine_mono_dirs(String &r_assembly_rootdir, String &r_config_dir); - protected: static GDMono *singleton; @@ -192,107 +115,43 @@ public: #endif } -#ifdef TOOLS_ENABLED - bool copy_prebuilt_api_assembly(ApiAssemblyInfo::Type p_api_type, const String &p_config); - String update_api_assemblies_from_prebuilt(const String &p_config, const bool *p_core_api_out_of_sync = nullptr, const bool *p_editor_api_out_of_sync = nullptr); -#endif - - static GDMono *get_singleton() { return singleton; } - - [[noreturn]] static void unhandled_exception_hook(MonoObject *p_exc, void *p_user_data); - - UnhandledExceptionPolicy get_unhandled_exception_policy() const { return unhandled_exception_policy; } - - // Do not use these, unless you know what you're doing - void add_assembly(int32_t p_domain_id, GDMonoAssembly *p_assembly); - GDMonoAssembly *get_loaded_assembly(const String &p_name); - - _FORCE_INLINE_ bool is_runtime_initialized() const { return runtime_initialized && !mono_runtime_is_shutting_down() /* stays true after shutdown finished */; } + static GDMono *get_singleton() { + return singleton; + } - _FORCE_INLINE_ bool is_finalizing_scripts_domain() { return finalizing_scripts_domain; } + _FORCE_INLINE_ bool is_runtime_initialized() const { + return runtime_initialized; + } + _FORCE_INLINE_ bool is_finalizing_scripts_domain() { + return finalizing_scripts_domain; + } - _FORCE_INLINE_ MonoDomain *get_scripts_domain() { return scripts_domain; } + _FORCE_INLINE_ const String &get_project_assembly_path() const { + return project_assembly_path; + } + _FORCE_INLINE_ uint64_t get_project_assembly_modified_time() const { + return project_assembly_modified_time; + } - _FORCE_INLINE_ GDMonoAssembly *get_corlib_assembly() const { return corlib_assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_core_api_assembly() const { return core_api_assembly.assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_project_assembly() const { return project_assembly; } #ifdef TOOLS_ENABLED - _FORCE_INLINE_ GDMonoAssembly *get_editor_api_assembly() const { return editor_api_assembly.assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_tools_assembly() const { return tools_assembly; } - _FORCE_INLINE_ GDMonoAssembly *get_tools_project_editor_assembly() const { return tools_project_editor_assembly; } -#endif - -#if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) - const MonoRegInfo &get_mono_reg_info() { return mono_reg_info; } + const gdmono::PluginCallbacks &get_plugin_callbacks() { + return plugin_callbacks; + } #endif - GDMonoClass *get_class(MonoClass *p_raw_class); - GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name); - #ifdef GD_MONO_HOT_RELOAD - Error reload_scripts_domain(); + Error reload_project_assemblies(); #endif - bool load_assembly(const String &p_name, GDMonoAssembly **r_assembly, bool p_refonly = false); - bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly = false); - bool load_assembly(const String &p_name, MonoAssemblyName *p_aname, GDMonoAssembly **r_assembly, bool p_refonly, const Vector<String> &p_search_dirs); - bool load_assembly_from(const String &p_name, const String &p_path, GDMonoAssembly **r_assembly, bool p_refonly = false); - - Error finalize_and_unload_domain(MonoDomain *p_domain); - void initialize(); +#ifdef TOOLS_ENABLED void initialize_load_assemblies(); +#endif GDMono(); ~GDMono(); }; -namespace gdmono { - -class ScopeDomain { - MonoDomain *prev_domain = nullptr; - -public: - ScopeDomain(MonoDomain *p_domain) { - prev_domain = mono_domain_get(); - if (prev_domain != p_domain) { - mono_domain_set(p_domain, false); - } else { - prev_domain = nullptr; - } - } - - ~ScopeDomain() { - if (prev_domain) { - mono_domain_set(prev_domain, false); - } - } -}; - -class ScopeExitDomainUnload { - MonoDomain *domain = nullptr; - -public: - ScopeExitDomainUnload(MonoDomain *p_domain) : - domain(p_domain) { - } - - ~ScopeExitDomainUnload() { - if (domain) { - GDMono::get_singleton()->finalize_and_unload_domain(domain); - } - } -}; -} // namespace gdmono - -#define _GDMONO_SCOPE_DOMAIN_(m_mono_domain) \ - gdmono::ScopeDomain __gdmono__scope__domain__(m_mono_domain); \ - (void)__gdmono__scope__domain__; - -#define _GDMONO_SCOPE_EXIT_DOMAIN_UNLOAD_(m_mono_domain) \ - gdmono::ScopeExitDomainUnload __gdmono__scope__exit__domain__unload__(m_mono_domain); \ - (void)__gdmono__scope__exit__domain__unload__; - namespace mono_bind { class GodotSharp : public Object { @@ -300,9 +159,8 @@ class GodotSharp : public Object { friend class GDMono; - bool _is_domain_finalizing_for_unload(int32_t p_domain_id); - void _reload_assemblies(bool p_soft_reload); + bool _is_runtime_initialized(); protected: static GodotSharp *singleton; @@ -311,20 +169,6 @@ protected: public: static GodotSharp *get_singleton() { return singleton; } - void attach_thread(); - void detach_thread(); - - int32_t get_domain_id(); - int32_t get_scripts_domain_id(); - - bool is_scripts_domain_loaded(); - - bool is_domain_finalizing_for_unload(int32_t p_domain_id); - bool is_domain_finalizing_for_unload(MonoDomain *p_domain); - - bool is_runtime_shutting_down(); - bool is_runtime_initialized(); - GodotSharp(); ~GodotSharp(); }; diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp deleted file mode 100644 index 42c6b6305f..0000000000 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ /dev/null @@ -1,482 +0,0 @@ -/*************************************************************************/ -/* gd_mono_assembly.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_assembly.h" - -#include <mono/metadata/mono-debug.h> -#include <mono/metadata/tokentype.h> - -#include "core/config/project_settings.h" -#include "core/io/file_access.h" -#include "core/io/file_access_pack.h" -#include "core/os/os.h" -#include "core/templates/list.h" - -#include "../godotsharp_dirs.h" -#include "gd_mono_cache.h" -#include "gd_mono_class.h" - -Vector<String> GDMonoAssembly::search_dirs; - -void GDMonoAssembly::fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config, const String &p_custom_bcl_dir) { - String framework_dir; - - if (!p_custom_bcl_dir.is_empty()) { - framework_dir = p_custom_bcl_dir; - } else if (mono_assembly_getrootdir()) { - framework_dir = String::utf8(mono_assembly_getrootdir()).plus_file("mono").plus_file("4.5"); - } - - if (!framework_dir.is_empty()) { - r_search_dirs.push_back(framework_dir); - r_search_dirs.push_back(framework_dir.plus_file("Facades")); - } - -#if !defined(TOOLS_ENABLED) - String data_game_assemblies_dir = GodotSharpDirs::get_data_game_assemblies_dir(); - if (!data_game_assemblies_dir.is_empty()) { - r_search_dirs.push_back(data_game_assemblies_dir); - } -#endif - - if (p_custom_config.length()) { - r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(p_custom_config)); - } else { - r_search_dirs.push_back(GodotSharpDirs::get_res_temp_assemblies_dir()); - } - - if (p_custom_config.is_empty()) { - r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_dir()); - } else { - String api_config = p_custom_config == "ExportRelease" ? "Release" : "Debug"; - r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir().plus_file(api_config)); - } - - r_search_dirs.push_back(GodotSharpDirs::get_res_assemblies_base_dir()); - r_search_dirs.push_back(OS::get_singleton()->get_resource_dir()); - r_search_dirs.push_back(OS::get_singleton()->get_executable_path().get_base_dir()); - -#ifdef TOOLS_ENABLED - r_search_dirs.push_back(GodotSharpDirs::get_data_editor_tools_dir()); - - // For GodotTools to find the api assemblies - r_search_dirs.push_back(GodotSharpDirs::get_data_editor_prebuilt_api_dir().plus_file("Debug")); -#endif -} - -// This is how these assembly loading hooks work: -// -// - The 'search' hook checks if the assembly has already been loaded, to avoid loading again. -// - The 'preload' hook does the actual loading and is only called if the -// 'search' hook didn't find the assembly in the list of loaded assemblies. -// - The 'load' hook is called after the assembly has been loaded. Its job is to add the -// assembly to the list of loaded assemblies so that the 'search' hook can look it up. - -void GDMonoAssembly::assembly_load_hook(MonoAssembly *assembly, [[maybe_unused]] void *user_data) { - String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); - - MonoImage *image = mono_assembly_get_image(assembly); - - GDMonoAssembly *gdassembly = memnew(GDMonoAssembly(name, image, assembly)); - -#ifdef GD_MONO_HOT_RELOAD - String path = String::utf8(mono_image_get_filename(image)); - if (FileAccess::exists(path)) { - gdassembly->modified_time = FileAccess::get_modified_time(path); - } -#endif - - MonoDomain *domain = mono_domain_get(); - GDMono::get_singleton()->add_assembly(domain ? mono_domain_get_id(domain) : 0, gdassembly); -} - -MonoAssembly *GDMonoAssembly::assembly_search_hook(MonoAssemblyName *aname, void *user_data) { - return GDMonoAssembly::_search_hook(aname, user_data, false); -} - -MonoAssembly *GDMonoAssembly::assembly_refonly_search_hook(MonoAssemblyName *aname, void *user_data) { - return GDMonoAssembly::_search_hook(aname, user_data, true); -} - -MonoAssembly *GDMonoAssembly::assembly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data) { - return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, false); -} - -MonoAssembly *GDMonoAssembly::assembly_refonly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data) { - return GDMonoAssembly::_preload_hook(aname, assemblies_path, user_data, true); -} - -MonoAssembly *GDMonoAssembly::_search_hook(MonoAssemblyName *aname, [[maybe_unused]] void *user_data, bool refonly) { - String name = String::utf8(mono_assembly_name_get_name(aname)); - bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); - - GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name); - if (loaded_asm) { - return loaded_asm->get_assembly(); - } - - return nullptr; -} - -MonoAssembly *GDMonoAssembly::_preload_hook(MonoAssemblyName *aname, char **, [[maybe_unused]] void *user_data, bool refonly) { - String name = String::utf8(mono_assembly_name_get_name(aname)); - return _load_assembly_search(name, aname, refonly, search_dirs); -} - -MonoAssembly *GDMonoAssembly::_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) { - MonoAssembly *res = nullptr; - String path; - - bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe"); - - for (int i = 0; i < p_search_dirs.size(); i++) { - const String &search_dir = p_search_dirs[i]; - - if (has_extension) { - path = search_dir.plus_file(p_name); - if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly, p_aname); - if (res != nullptr) { - return res; - } - } - } else { - path = search_dir.plus_file(p_name + ".dll"); - if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly, p_aname); - if (res != nullptr) { - return res; - } - } - - path = search_dir.plus_file(p_name + ".exe"); - if (FileAccess::exists(path)) { - res = _real_load_assembly_from(path, p_refonly, p_aname); - if (res != nullptr) { - return res; - } - } - } - } - - return nullptr; -} - -String GDMonoAssembly::find_assembly(const String &p_name) { - String path; - - bool has_extension = p_name.ends_with(".dll") || p_name.ends_with(".exe"); - - for (int i = 0; i < search_dirs.size(); i++) { - const String &search_dir = search_dirs[i]; - - if (has_extension) { - path = search_dir.plus_file(p_name); - if (FileAccess::exists(path)) { - return path; - } - } else { - path = search_dir.plus_file(p_name + ".dll"); - if (FileAccess::exists(path)) { - return path; - } - - path = search_dir.plus_file(p_name + ".exe"); - if (FileAccess::exists(path)) { - return path; - } - } - } - - return String(); -} - -void GDMonoAssembly::initialize() { - fill_search_dirs(search_dirs); - - mono_install_assembly_search_hook(&assembly_search_hook, nullptr); - mono_install_assembly_refonly_search_hook(&assembly_refonly_search_hook, nullptr); - mono_install_assembly_preload_hook(&assembly_preload_hook, nullptr); - mono_install_assembly_refonly_preload_hook(&assembly_refonly_preload_hook, nullptr); - mono_install_assembly_load_hook(&assembly_load_hook, nullptr); -} - -MonoAssembly *GDMonoAssembly::_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname) { - Vector<uint8_t> data = FileAccess::get_file_as_array(p_path); - ERR_FAIL_COND_V_MSG(data.is_empty(), nullptr, "Could read the assembly in the specified location"); - - String image_filename; - -#ifdef ANDROID_ENABLED - if (p_path.begins_with("res://")) { - image_filename = p_path.substr(6, p_path.length()); - } else { - image_filename = ProjectSettings::get_singleton()->globalize_path(p_path); - } -#else - // FIXME: globalize_path does not work on exported games - image_filename = ProjectSettings::get_singleton()->globalize_path(p_path); -#endif - - MonoImageOpenStatus status = MONO_IMAGE_OK; - - MonoImage *image = mono_image_open_from_data_with_name( - (char *)&data[0], data.size(), - true, &status, p_refonly, - image_filename.utf8()); - - ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !image, nullptr, "Failed to open assembly image from memory: '" + p_path + "'."); - - if (p_aname != nullptr) { - // Check assembly version - const MonoTableInfo *table = mono_image_get_table_info(image, MONO_TABLE_ASSEMBLY); - - ERR_FAIL_NULL_V(table, nullptr); - - if (mono_table_info_get_rows(table)) { - uint32_t cols[MONO_ASSEMBLY_SIZE]; - mono_metadata_decode_row(table, 0, cols, MONO_ASSEMBLY_SIZE); - - // Not sure about .NET's policy. We will only ensure major and minor are equal, and ignore build and revision. - uint16_t major = cols[MONO_ASSEMBLY_MAJOR_VERSION]; - uint16_t minor = cols[MONO_ASSEMBLY_MINOR_VERSION]; - - uint16_t required_minor; - uint16_t required_major = mono_assembly_name_get_version(p_aname, &required_minor, nullptr, nullptr); - - if (required_major != 0) { - if (major != required_major && minor != required_minor) { - mono_image_close(image); - return nullptr; - } - } - } - } - -#ifdef DEBUG_ENABLED - Vector<uint8_t> pdb_data; - String pdb_path(p_path + ".pdb"); - - if (!FileAccess::exists(pdb_path)) { - pdb_path = p_path.get_basename() + ".pdb"; // without .dll - - if (!FileAccess::exists(pdb_path)) { - goto no_pdb; - } - } - - pdb_data = FileAccess::get_file_as_array(pdb_path); - - // mono_debug_close_image doesn't seem to be needed - mono_debug_open_image_from_memory(image, &pdb_data[0], pdb_data.size()); - -no_pdb: - -#endif - - bool need_manual_load_hook = mono_image_get_assembly(image) != nullptr; // Re-using an existing image with an assembly loaded - - status = MONO_IMAGE_OK; - - MonoAssembly *assembly = mono_assembly_load_from_full(image, image_filename.utf8().get_data(), &status, p_refonly); - - ERR_FAIL_COND_V_MSG(status != MONO_IMAGE_OK || !assembly, nullptr, "Failed to load assembly for image"); - - if (need_manual_load_hook) { - // For some reason if an assembly survived domain reloading (maybe because it's referenced somewhere else), - // the mono internal search hook don't detect it, yet mono_image_open_from_data_with_name re-uses the image - // and assembly, and mono_assembly_load_from_full doesn't call the load hook. We need to call it manually. - String name = String::utf8(mono_assembly_name_get_name(mono_assembly_get_name(assembly))); - bool has_extension = name.ends_with(".dll") || name.ends_with(".exe"); - GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(has_extension ? name.get_basename() : name); - if (!loaded_asm) { - assembly_load_hook(assembly, nullptr); - } - } - - // Decrement refcount which was previously incremented by mono_image_open_from_data_with_name - mono_image_close(image); - - return assembly; -} - -void GDMonoAssembly::unload() { - ERR_FAIL_NULL(image); // Should not be called if already unloaded - - for (const KeyValue<MonoClass *, GDMonoClass *> &E : cached_raw) { - memdelete(E.value); - } - - cached_classes.clear(); - cached_raw.clear(); - - assembly = nullptr; - image = nullptr; -} - -String GDMonoAssembly::get_path() const { - return String::utf8(mono_image_get_filename(image)); -} - -bool GDMonoAssembly::has_attribute(GDMonoClass *p_attr_class) { -#ifdef DEBUG_ENABLED - ERR_FAIL_NULL_V(p_attr_class, false); -#endif - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoAssembly::get_attribute(GDMonoClass *p_attr_class) { -#ifdef DEBUG_ENABLED - ERR_FAIL_NULL_V(p_attr_class, nullptr); -#endif - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoAssembly::fetch_attributes() { - ERR_FAIL_COND(attributes != nullptr); - - attributes = mono_custom_attrs_from_assembly(assembly); - attrs_fetched = true; -} - -GDMonoClass *GDMonoAssembly::get_class(const StringName &p_namespace, const StringName &p_name) { - ERR_FAIL_NULL_V(image, nullptr); - - ClassKey key(p_namespace, p_name); - - GDMonoClass **match = cached_classes.getptr(key); - - if (match) { - return *match; - } - - MonoClass *mono_class = mono_class_from_name(image, String(p_namespace).utf8(), String(p_name).utf8()); - - if (!mono_class) { - return nullptr; - } - - GDMonoClass *wrapped_class = memnew(GDMonoClass(p_namespace, p_name, mono_class, this)); - - cached_classes[key] = wrapped_class; - cached_raw[mono_class] = wrapped_class; - - return wrapped_class; -} - -GDMonoClass *GDMonoAssembly::get_class(MonoClass *p_mono_class) { - ERR_FAIL_NULL_V(image, nullptr); - - HashMap<MonoClass *, GDMonoClass *>::Iterator match = cached_raw.find(p_mono_class); - - if (match) { - return match->value; - } - - StringName namespace_name = String::utf8(mono_class_get_namespace(p_mono_class)); - StringName class_name = String::utf8(mono_class_get_name(p_mono_class)); - - GDMonoClass *wrapped_class = memnew(GDMonoClass(namespace_name, class_name, p_mono_class, this)); - - cached_classes[ClassKey(namespace_name, class_name)] = wrapped_class; - cached_raw[p_mono_class] = wrapped_class; - - return wrapped_class; -} - -GDMonoAssembly *GDMonoAssembly::load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs) { - if (GDMono::get_singleton()->get_corlib_assembly() && (p_name == "mscorlib" || p_name == "mscorlib.dll")) { - return GDMono::get_singleton()->get_corlib_assembly(); - } - - // We need to manually call the search hook in this case, as it won't be called in the next step - MonoAssembly *assembly = mono_assembly_invoke_search_hook(p_aname); - - if (!assembly) { - assembly = _load_assembly_search(p_name, p_aname, p_refonly, p_search_dirs); - if (!assembly) { - return nullptr; - } - } - - GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); - ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?"); - ERR_FAIL_COND_V(loaded_asm->get_assembly() != assembly, nullptr); - - return loaded_asm; -} - -GDMonoAssembly *GDMonoAssembly::load_from(const String &p_name, const String &p_path, bool p_refonly) { - if (p_name == "mscorlib" || p_name == "mscorlib.dll") { - return GDMono::get_singleton()->get_corlib_assembly(); - } - - // We need to manually call the search hook in this case, as it won't be called in the next step - MonoAssemblyName *aname = mono_assembly_name_new(p_name.utf8()); - MonoAssembly *assembly = mono_assembly_invoke_search_hook(aname); - mono_assembly_name_free(aname); - mono_free(aname); - - if (!assembly) { - assembly = _real_load_assembly_from(p_path, p_refonly); - if (!assembly) { - return nullptr; - } - } - - GDMonoAssembly *loaded_asm = GDMono::get_singleton()->get_loaded_assembly(p_name); - ERR_FAIL_NULL_V_MSG(loaded_asm, nullptr, "Loaded assembly missing from table. Did we not receive the load hook?"); - - return loaded_asm; -} - -GDMonoAssembly::~GDMonoAssembly() { - if (image) { - unload(); - } -} diff --git a/modules/mono/mono_gd/gd_mono_assembly.h b/modules/mono/mono_gd/gd_mono_assembly.h deleted file mode 100644 index 0a3ae6c4fe..0000000000 --- a/modules/mono/mono_gd/gd_mono_assembly.h +++ /dev/null @@ -1,138 +0,0 @@ -/*************************************************************************/ -/* gd_mono_assembly.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_ASSEMBLY_H -#define GD_MONO_ASSEMBLY_H - -#include <mono/jit/jit.h> -#include <mono/metadata/assembly.h> - -#include "core/string/ustring.h" -#include "core/templates/hash_map.h" -#include "core/templates/rb_map.h" -#include "gd_mono_utils.h" - -class GDMonoAssembly { - struct ClassKey { - struct Hasher { - static _FORCE_INLINE_ uint32_t hash(const ClassKey &p_key) { - uint32_t hash = 0; - - GDMonoUtils::hash_combine(hash, p_key.namespace_name.hash()); - GDMonoUtils::hash_combine(hash, p_key.class_name.hash()); - - return hash; - } - }; - - _FORCE_INLINE_ bool operator==(const ClassKey &p_a) const { - return p_a.class_name == class_name && p_a.namespace_name == namespace_name; - } - - ClassKey() {} - - ClassKey(const StringName &p_namespace_name, const StringName &p_class_name) { - namespace_name = p_namespace_name; - class_name = p_class_name; - } - - StringName namespace_name; - StringName class_name; - }; - - String name; - MonoImage *image = nullptr; - MonoAssembly *assembly = nullptr; - - bool attrs_fetched = false; - MonoCustomAttrInfo *attributes = nullptr; - -#ifdef GD_MONO_HOT_RELOAD - uint64_t modified_time = 0; -#endif - - HashMap<ClassKey, GDMonoClass *, ClassKey::Hasher> cached_classes; - HashMap<MonoClass *, GDMonoClass *> cached_raw; - - static Vector<String> search_dirs; - - static void assembly_load_hook(MonoAssembly *assembly, void *user_data); - static MonoAssembly *assembly_search_hook(MonoAssemblyName *aname, void *user_data); - static MonoAssembly *assembly_refonly_search_hook(MonoAssemblyName *aname, void *user_data); - static MonoAssembly *assembly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data); - static MonoAssembly *assembly_refonly_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data); - - static MonoAssembly *_search_hook(MonoAssemblyName *aname, void *user_data, bool refonly); - static MonoAssembly *_preload_hook(MonoAssemblyName *aname, char **assemblies_path, void *user_data, bool refonly); - - static MonoAssembly *_real_load_assembly_from(const String &p_path, bool p_refonly, MonoAssemblyName *p_aname = nullptr); - static MonoAssembly *_load_assembly_search(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs); - - friend class GDMono; - static void initialize(); - -public: - void unload(); - - _FORCE_INLINE_ MonoImage *get_image() const { return image; } - _FORCE_INLINE_ MonoAssembly *get_assembly() const { return assembly; } - _FORCE_INLINE_ String get_name() const { return name; } - -#ifdef GD_MONO_HOT_RELOAD - _FORCE_INLINE_ uint64_t get_modified_time() const { return modified_time; } -#endif - - String get_path() const; - - bool has_attribute(GDMonoClass *p_attr_class); - MonoObject *get_attribute(GDMonoClass *p_attr_class); - - void fetch_attributes(); - - GDMonoClass *get_class(const StringName &p_namespace, const StringName &p_name); - GDMonoClass *get_class(MonoClass *p_mono_class); - - static String find_assembly(const String &p_name); - - static void fill_search_dirs(Vector<String> &r_search_dirs, const String &p_custom_config = String(), const String &p_custom_bcl_dir = String()); - static const Vector<String> &get_default_search_dirs() { return search_dirs; } - - static GDMonoAssembly *load(const String &p_name, MonoAssemblyName *p_aname, bool p_refonly, const Vector<String> &p_search_dirs); - static GDMonoAssembly *load_from(const String &p_name, const String &p_path, bool p_refonly); - - GDMonoAssembly(const String &p_name, MonoImage *p_image, MonoAssembly *p_assembly) : - name(p_name), - image(p_image), - assembly(p_assembly) { - } - ~GDMonoAssembly(); -}; - -#endif // GD_MONO_ASSEMBLY_H diff --git a/modules/mono/mono_gd/gd_mono_cache.cpp b/modules/mono/mono_gd/gd_mono_cache.cpp index 69d8c7edc9..bfd803f326 100644 --- a/modules/mono/mono_gd/gd_mono_cache.cpp +++ b/modules/mono/mono_gd/gd_mono_cache.cpp @@ -30,310 +30,66 @@ #include "gd_mono_cache.h" -#include "gd_mono.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_method.h" -#include "gd_mono_utils.h" +#include "core/error/error_macros.h" namespace GDMonoCache { -CachedData cached_data; +ManagedCallbacks managed_callbacks; +bool godot_api_cache_updated = false; -#define CACHE_AND_CHECK(m_var, m_val) \ - { \ - CRASH_COND(m_var != nullptr); \ - m_var = m_val; \ - ERR_FAIL_COND_MSG(m_var == nullptr, "Mono Cache: Member " #m_var " is null."); \ - } - -#define CACHE_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(cached_data.class_##m_class, m_val) -#define CACHE_NS_CLASS_AND_CHECK(m_ns, m_class, m_val) CACHE_AND_CHECK(cached_data.class_##m_ns##_##m_class, m_val) -#define CACHE_RAW_MONO_CLASS_AND_CHECK(m_class, m_val) CACHE_AND_CHECK(cached_data.rawclass_##m_class, m_val) -#define CACHE_FIELD_AND_CHECK(m_class, m_field, m_val) CACHE_AND_CHECK(cached_data.field_##m_class##_##m_field, m_val) -#define CACHE_METHOD_AND_CHECK(m_class, m_method, m_val) CACHE_AND_CHECK(cached_data.method_##m_class##_##m_method, m_val) -#define CACHE_PROPERTY_AND_CHECK(m_class, m_property, m_val) CACHE_AND_CHECK(cached_data.property_##m_class##_##m_property, m_val) +void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks) { + int checked_count = 0; -#define CACHE_METHOD_THUNK_AND_CHECK_IMPL(m_var, m_val) \ - { \ - CRASH_COND(!m_var.is_null()); \ - ERR_FAIL_COND_MSG(m_val == nullptr, "Mono Cache: Method for member " #m_var " is null."); \ - m_var.set_from_method(m_val); \ - ERR_FAIL_COND_MSG(m_var.is_null(), "Mono Cache: Member " #m_var " is null."); \ +#define CHECK_CALLBACK_NOT_NULL_IMPL(m_var, m_class, m_method) \ + { \ + ERR_FAIL_COND_MSG(m_var == nullptr, \ + "Mono Cache: Managed callback for '" #m_class "_" #m_method "' is null."); \ + checked_count += 1; \ } -#define CACHE_METHOD_THUNK_AND_CHECK(m_class, m_method, m_val) CACHE_METHOD_THUNK_AND_CHECK_IMPL(cached_data.methodthunk_##m_class##_##m_method, m_val) - -void CachedData::clear_corlib_cache() { - corlib_cache_updated = false; - - class_MonoObject = nullptr; - class_bool = nullptr; - class_int8_t = nullptr; - class_int16_t = nullptr; - class_int32_t = nullptr; - class_int64_t = nullptr; - class_uint8_t = nullptr; - class_uint16_t = nullptr; - class_uint32_t = nullptr; - class_uint64_t = nullptr; - class_float = nullptr; - class_double = nullptr; - class_String = nullptr; - class_IntPtr = nullptr; - - class_System_Collections_IEnumerable = nullptr; - class_System_Collections_ICollection = nullptr; - class_System_Collections_IDictionary = nullptr; - -#ifdef DEBUG_ENABLED - class_System_Diagnostics_StackTrace = nullptr; - methodthunk_System_Diagnostics_StackTrace_GetFrames.nullify(); - method_System_Diagnostics_StackTrace_ctor_bool = nullptr; - method_System_Diagnostics_StackTrace_ctor_Exception_bool = nullptr; -#endif - - class_KeyNotFoundException = nullptr; -} - -void CachedData::clear_godot_api_cache() { - godot_api_cache_updated = false; - - rawclass_Dictionary = nullptr; - - class_Vector2 = nullptr; - class_Vector2i = nullptr; - class_Rect2 = nullptr; - class_Rect2i = nullptr; - class_Transform2D = nullptr; - class_Vector3 = nullptr; - class_Vector3i = nullptr; - class_Vector4 = nullptr; - class_Vector4i = nullptr; - class_Basis = nullptr; - class_Quaternion = nullptr; - class_Transform3D = nullptr; - class_Projection = nullptr; - class_AABB = nullptr; - class_Color = nullptr; - class_Plane = nullptr; - class_StringName = nullptr; - class_NodePath = nullptr; - class_RID = nullptr; - class_GodotObject = nullptr; - class_GodotResource = nullptr; - class_Node = nullptr; - class_Control = nullptr; - class_Node3D = nullptr; - class_WeakRef = nullptr; - class_Callable = nullptr; - class_SignalInfo = nullptr; - class_Array = nullptr; - class_Dictionary = nullptr; - class_MarshalUtils = nullptr; - class_ISerializationListener = nullptr; - -#ifdef DEBUG_ENABLED - class_DebuggingUtils = nullptr; - methodthunk_DebuggingUtils_GetStackFrameInfo.nullify(); -#endif - - class_ExportAttribute = nullptr; - field_ExportAttribute_hint = nullptr; - field_ExportAttribute_hintString = nullptr; - class_SignalAttribute = nullptr; - class_ToolAttribute = nullptr; - class_RPCAttribute = nullptr; - property_RPCAttribute_Mode = nullptr; - property_RPCAttribute_CallLocal = nullptr; - property_RPCAttribute_TransferMode = nullptr; - property_RPCAttribute_TransferChannel = nullptr; - class_GodotMethodAttribute = nullptr; - field_GodotMethodAttribute_methodName = nullptr; - class_ScriptPathAttribute = nullptr; - field_ScriptPathAttribute_path = nullptr; - class_AssemblyHasScriptsAttribute = nullptr; - field_AssemblyHasScriptsAttribute_requiresLookup = nullptr; - field_AssemblyHasScriptsAttribute_scriptTypes = nullptr; - - field_GodotObject_ptr = nullptr; - field_StringName_ptr = nullptr; - field_NodePath_ptr = nullptr; - field_Image_ptr = nullptr; - field_RID_ptr = nullptr; - - methodthunk_GodotObject_Dispose.nullify(); - methodthunk_Array_GetPtr.nullify(); - methodthunk_Dictionary_GetPtr.nullify(); - methodthunk_SignalAwaiter_SignalCallback.nullify(); - methodthunk_GodotTaskScheduler_Activate.nullify(); - - methodthunk_Delegate_Equals.nullify(); - - methodthunk_DelegateUtils_TrySerializeDelegate.nullify(); - methodthunk_DelegateUtils_TryDeserializeDelegate.nullify(); - - // Start of MarshalUtils methods - - methodthunk_MarshalUtils_TypeIsGenericArray.nullify(); - methodthunk_MarshalUtils_TypeIsGenericDictionary.nullify(); - methodthunk_MarshalUtils_TypeIsSystemGenericList.nullify(); - methodthunk_MarshalUtils_TypeIsSystemGenericDictionary.nullify(); - methodthunk_MarshalUtils_TypeIsGenericIEnumerable.nullify(); - methodthunk_MarshalUtils_TypeIsGenericICollection.nullify(); - methodthunk_MarshalUtils_TypeIsGenericIDictionary.nullify(); - methodthunk_MarshalUtils_TypeHasFlagsAttribute.nullify(); - - methodthunk_MarshalUtils_GetGenericTypeDefinition.nullify(); - - methodthunk_MarshalUtils_ArrayGetElementType.nullify(); - methodthunk_MarshalUtils_DictionaryGetKeyValueTypes.nullify(); - - methodthunk_MarshalUtils_MakeGenericArrayType.nullify(); - methodthunk_MarshalUtils_MakeGenericDictionaryType.nullify(); - - // End of MarshalUtils methods - - task_scheduler_handle = Ref<MonoGCHandleRef>(); -} - -#define GODOT_API_CLASS(m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, #m_class)) -#define GODOT_API_NS_CLASS(m_ns, m_class) (GDMono::get_singleton()->get_core_api_assembly()->get_class(m_ns, #m_class)) - -void update_corlib_cache() { - CACHE_CLASS_AND_CHECK(MonoObject, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_object_class())); - CACHE_CLASS_AND_CHECK(bool, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_boolean_class())); - CACHE_CLASS_AND_CHECK(int8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_sbyte_class())); - CACHE_CLASS_AND_CHECK(int16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int16_class())); - CACHE_CLASS_AND_CHECK(int32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int32_class())); - CACHE_CLASS_AND_CHECK(int64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_int64_class())); - CACHE_CLASS_AND_CHECK(uint8_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_byte_class())); - CACHE_CLASS_AND_CHECK(uint16_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint16_class())); - CACHE_CLASS_AND_CHECK(uint32_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint32_class())); - CACHE_CLASS_AND_CHECK(uint64_t, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_uint64_class())); - CACHE_CLASS_AND_CHECK(float, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_single_class())); - CACHE_CLASS_AND_CHECK(double, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_double_class())); - CACHE_CLASS_AND_CHECK(String, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_string_class())); - CACHE_CLASS_AND_CHECK(IntPtr, GDMono::get_singleton()->get_corlib_assembly()->get_class(mono_get_intptr_class())); - - CACHE_CLASS_AND_CHECK(System_Collections_IEnumerable, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IEnumerable")); - CACHE_CLASS_AND_CHECK(System_Collections_ICollection, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "ICollection")); - CACHE_CLASS_AND_CHECK(System_Collections_IDictionary, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections", "IDictionary")); - -#ifdef DEBUG_ENABLED - CACHE_CLASS_AND_CHECK(System_Diagnostics_StackTrace, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Diagnostics", "StackTrace")); - CACHE_METHOD_THUNK_AND_CHECK(System_Diagnostics_StackTrace, GetFrames, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method("GetFrames")); - CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(bool)", true)); - CACHE_METHOD_AND_CHECK(System_Diagnostics_StackTrace, ctor_Exception_bool, CACHED_CLASS(System_Diagnostics_StackTrace)->get_method_with_desc("System.Diagnostics.StackTrace:.ctor(System.Exception,bool)", true)); -#endif - - CACHE_METHOD_THUNK_AND_CHECK(Delegate, Equals, GDMono::get_singleton()->get_corlib_assembly()->get_class("System", "Delegate")->get_method_with_desc("System.Delegate:Equals(object)", true)); - - CACHE_CLASS_AND_CHECK(KeyNotFoundException, GDMono::get_singleton()->get_corlib_assembly()->get_class("System.Collections.Generic", "KeyNotFoundException")); - - cached_data.corlib_cache_updated = true; -} - -void update_godot_api_cache() { - CACHE_CLASS_AND_CHECK(Vector2, GODOT_API_CLASS(Vector2)); - CACHE_CLASS_AND_CHECK(Vector2i, GODOT_API_CLASS(Vector2i)); - CACHE_CLASS_AND_CHECK(Rect2, GODOT_API_CLASS(Rect2)); - CACHE_CLASS_AND_CHECK(Rect2i, GODOT_API_CLASS(Rect2i)); - CACHE_CLASS_AND_CHECK(Transform2D, GODOT_API_CLASS(Transform2D)); - CACHE_CLASS_AND_CHECK(Vector3, GODOT_API_CLASS(Vector3)); - CACHE_CLASS_AND_CHECK(Vector3i, GODOT_API_CLASS(Vector3i)); - CACHE_CLASS_AND_CHECK(Vector4, GODOT_API_CLASS(Vector4)); - CACHE_CLASS_AND_CHECK(Vector4i, GODOT_API_CLASS(Vector4i)); - CACHE_CLASS_AND_CHECK(Basis, GODOT_API_CLASS(Basis)); - CACHE_CLASS_AND_CHECK(Quaternion, GODOT_API_CLASS(Quaternion)); - CACHE_CLASS_AND_CHECK(Transform3D, GODOT_API_CLASS(Transform3D)); - CACHE_CLASS_AND_CHECK(Projection, GODOT_API_CLASS(Projection)); - CACHE_CLASS_AND_CHECK(AABB, GODOT_API_CLASS(AABB)); - CACHE_CLASS_AND_CHECK(Color, GODOT_API_CLASS(Color)); - CACHE_CLASS_AND_CHECK(Plane, GODOT_API_CLASS(Plane)); - CACHE_CLASS_AND_CHECK(StringName, GODOT_API_CLASS(StringName)); - CACHE_CLASS_AND_CHECK(NodePath, GODOT_API_CLASS(NodePath)); - CACHE_CLASS_AND_CHECK(RID, GODOT_API_CLASS(RID)); - CACHE_CLASS_AND_CHECK(GodotObject, GODOT_API_CLASS(Object)); - CACHE_CLASS_AND_CHECK(GodotResource, GODOT_API_CLASS(Resource)); - CACHE_CLASS_AND_CHECK(Node, GODOT_API_CLASS(Node)); - CACHE_CLASS_AND_CHECK(Control, GODOT_API_CLASS(Control)); - CACHE_CLASS_AND_CHECK(Node3D, GODOT_API_CLASS(Node3D)); - CACHE_CLASS_AND_CHECK(WeakRef, GODOT_API_CLASS(WeakRef)); - CACHE_CLASS_AND_CHECK(Callable, GODOT_API_CLASS(Callable)); - CACHE_CLASS_AND_CHECK(SignalInfo, GODOT_API_CLASS(SignalInfo)); - CACHE_CLASS_AND_CHECK(Array, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Array)); - CACHE_CLASS_AND_CHECK(Dictionary, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)); - CACHE_CLASS_AND_CHECK(MarshalUtils, GODOT_API_CLASS(MarshalUtils)); - CACHE_CLASS_AND_CHECK(ISerializationListener, GODOT_API_CLASS(ISerializationListener)); - -#ifdef DEBUG_ENABLED - CACHE_CLASS_AND_CHECK(DebuggingUtils, GODOT_API_CLASS(DebuggingUtils)); -#endif - - // Attributes - CACHE_CLASS_AND_CHECK(ExportAttribute, GODOT_API_CLASS(ExportAttribute)); - CACHE_FIELD_AND_CHECK(ExportAttribute, hint, CACHED_CLASS(ExportAttribute)->get_field("hint")); - CACHE_FIELD_AND_CHECK(ExportAttribute, hintString, CACHED_CLASS(ExportAttribute)->get_field("hintString")); - CACHE_CLASS_AND_CHECK(SignalAttribute, GODOT_API_CLASS(SignalAttribute)); - CACHE_CLASS_AND_CHECK(ToolAttribute, GODOT_API_CLASS(ToolAttribute)); - CACHE_CLASS_AND_CHECK(RPCAttribute, GODOT_API_CLASS(RPCAttribute)); - CACHE_PROPERTY_AND_CHECK(RPCAttribute, Mode, CACHED_CLASS(RPCAttribute)->get_property("Mode")); - CACHE_PROPERTY_AND_CHECK(RPCAttribute, CallLocal, CACHED_CLASS(RPCAttribute)->get_property("CallLocal")); - CACHE_PROPERTY_AND_CHECK(RPCAttribute, TransferMode, CACHED_CLASS(RPCAttribute)->get_property("TransferMode")); - CACHE_PROPERTY_AND_CHECK(RPCAttribute, TransferChannel, CACHED_CLASS(RPCAttribute)->get_property("TransferChannel")); - CACHE_CLASS_AND_CHECK(GodotMethodAttribute, GODOT_API_CLASS(GodotMethodAttribute)); - CACHE_FIELD_AND_CHECK(GodotMethodAttribute, methodName, CACHED_CLASS(GodotMethodAttribute)->get_field("methodName")); - CACHE_CLASS_AND_CHECK(ScriptPathAttribute, GODOT_API_CLASS(ScriptPathAttribute)); - CACHE_FIELD_AND_CHECK(ScriptPathAttribute, path, CACHED_CLASS(ScriptPathAttribute)->get_field("path")); - CACHE_CLASS_AND_CHECK(AssemblyHasScriptsAttribute, GODOT_API_CLASS(AssemblyHasScriptsAttribute)); - CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, requiresLookup, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("requiresLookup")); - CACHE_FIELD_AND_CHECK(AssemblyHasScriptsAttribute, scriptTypes, CACHED_CLASS(AssemblyHasScriptsAttribute)->get_field("scriptTypes")); - - CACHE_FIELD_AND_CHECK(GodotObject, ptr, CACHED_CLASS(GodotObject)->get_field(BINDINGS_PTR_FIELD)); - CACHE_FIELD_AND_CHECK(StringName, ptr, CACHED_CLASS(StringName)->get_field(BINDINGS_PTR_FIELD)); - CACHE_FIELD_AND_CHECK(NodePath, ptr, CACHED_CLASS(NodePath)->get_field(BINDINGS_PTR_FIELD)); - CACHE_FIELD_AND_CHECK(RID, ptr, CACHED_CLASS(RID)->get_field(BINDINGS_PTR_FIELD)); - - CACHE_METHOD_THUNK_AND_CHECK(GodotObject, Dispose, CACHED_CLASS(GodotObject)->get_method("Dispose", 0)); - CACHE_METHOD_THUNK_AND_CHECK(Array, GetPtr, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Array)->get_method("GetPtr", 0)); - CACHE_METHOD_THUNK_AND_CHECK(Dictionary, GetPtr, GODOT_API_NS_CLASS(BINDINGS_NAMESPACE_COLLECTIONS, Dictionary)->get_method("GetPtr", 0)); - CACHE_METHOD_THUNK_AND_CHECK(SignalAwaiter, SignalCallback, GODOT_API_CLASS(SignalAwaiter)->get_method("SignalCallback", 1)); - CACHE_METHOD_THUNK_AND_CHECK(GodotTaskScheduler, Activate, GODOT_API_CLASS(GodotTaskScheduler)->get_method("Activate", 0)); - - CACHE_METHOD_THUNK_AND_CHECK(DelegateUtils, TrySerializeDelegate, GODOT_API_CLASS(DelegateUtils)->get_method("TrySerializeDelegate", 2)); - CACHE_METHOD_THUNK_AND_CHECK(DelegateUtils, TryDeserializeDelegate, GODOT_API_CLASS(DelegateUtils)->get_method("TryDeserializeDelegate", 2)); - - // Start of MarshalUtils methods - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericArray, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericArray", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericDictionary", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsSystemGenericList, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsSystemGenericList", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsSystemGenericDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsSystemGenericDictionary", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericIEnumerable, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericIEnumerable", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericICollection, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericICollection", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeIsGenericIDictionary, GODOT_API_CLASS(MarshalUtils)->get_method("TypeIsGenericIDictionary", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, TypeHasFlagsAttribute, GODOT_API_CLASS(MarshalUtils)->get_method("TypeHasFlagsAttribute", 1)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, GetGenericTypeDefinition, GODOT_API_CLASS(MarshalUtils)->get_method("GetGenericTypeDefinition", 2)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, ArrayGetElementType, GODOT_API_CLASS(MarshalUtils)->get_method("ArrayGetElementType", 2)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, DictionaryGetKeyValueTypes, GODOT_API_CLASS(MarshalUtils)->get_method("DictionaryGetKeyValueTypes", 3)); - - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericArrayType, GODOT_API_CLASS(MarshalUtils)->get_method("MakeGenericArrayType", 1)); - CACHE_METHOD_THUNK_AND_CHECK(MarshalUtils, MakeGenericDictionaryType, GODOT_API_CLASS(MarshalUtils)->get_method("MakeGenericDictionaryType", 2)); - - // End of MarshalUtils methods - -#ifdef DEBUG_ENABLED - CACHE_METHOD_THUNK_AND_CHECK(DebuggingUtils, GetStackFrameInfo, GODOT_API_CLASS(DebuggingUtils)->get_method("GetStackFrameInfo", 4)); -#endif - - // TODO Move to CSharpLanguage::init() and do handle disposal - MonoObject *task_scheduler = mono_object_new(mono_domain_get(), GODOT_API_CLASS(GodotTaskScheduler)->get_mono_ptr()); - GDMonoUtils::runtime_object_init(task_scheduler, GODOT_API_CLASS(GodotTaskScheduler)); - cached_data.task_scheduler_handle = MonoGCHandleRef::create_strong(task_scheduler); +#define CHECK_CALLBACK_NOT_NULL(m_class, m_method) CHECK_CALLBACK_NOT_NULL_IMPL(p_managed_callbacks.m_class##_##m_method, m_class, m_method) + + CHECK_CALLBACK_NOT_NULL(SignalAwaiter, SignalCallback); + CHECK_CALLBACK_NOT_NULL(DelegateUtils, InvokeWithVariantArgs); + CHECK_CALLBACK_NOT_NULL(DelegateUtils, DelegateEquals); + CHECK_CALLBACK_NOT_NULL(DelegateUtils, TrySerializeDelegateWithGCHandle); + CHECK_CALLBACK_NOT_NULL(DelegateUtils, TryDeserializeDelegateWithGCHandle); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, FrameCallback); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectBinding); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, CreateManagedForGodotObjectScriptInstance); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetScriptNativeName); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SetGodotObjectPtr); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RaiseEventSignal); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, ScriptIsOrInherits); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, AddScriptBridge); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetOrCreateScriptBridgeForPath); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, RemoveScriptBridge); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, TryReloadRegisteredScriptWithClass); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, UpdateScriptClassInfo); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, SwapGCHandleForType); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyInfoList); + CHECK_CALLBACK_NOT_NULL(ScriptManagerBridge, GetPropertyDefaultValues); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Call); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Set); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, Get); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallDispose); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, CallToString); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, HasMethodUnknownParams); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, SerializeState); + CHECK_CALLBACK_NOT_NULL(CSharpInstanceBridge, DeserializeState); + CHECK_CALLBACK_NOT_NULL(GCHandleBridge, FreeGCHandle); + CHECK_CALLBACK_NOT_NULL(DebuggingUtils, GetCurrentStackInfo); + CHECK_CALLBACK_NOT_NULL(DisposablesTracker, OnGodotShuttingDown); + CHECK_CALLBACK_NOT_NULL(GD, OnCoreApiAssemblyLoaded); + + managed_callbacks = p_managed_callbacks; + + // It's easy to forget to add new callbacks here, so this should help + if (checked_count * sizeof(void *) != sizeof(ManagedCallbacks)) { + int missing_count = (sizeof(ManagedCallbacks) / sizeof(void *)) - checked_count; + WARN_PRINT("The presence of " + itos(missing_count) + " callback(s) was not validated"); + } - cached_data.godot_api_cache_updated = true; + godot_api_cache_updated = true; } } // namespace GDMonoCache diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h index e9cc26899e..ca3a6c95a7 100644 --- a/modules/mono/mono_gd/gd_mono_cache.h +++ b/modules/mono/mono_gd/gd_mono_cache.h @@ -31,174 +31,120 @@ #ifndef GD_MONO_CACHE_H #define GD_MONO_CACHE_H -#include "gd_mono_header.h" -#include "gd_mono_method_thunk.h" +#include <stdint.h> + +#include "../csharp_script.h" +#include "../interop_types.h" +#include "../mono_gc_handle.h" +#include "core/object/object.h" +#include "core/string/string_name.h" +#include "core/string/ustring.h" +#include "core/variant/callable.h" +#include "core/variant/dictionary.h" +#include "core/variant/variant.h" + +class CSharpScript; namespace GDMonoCache { -struct CachedData { - // ----------------------------------------------- - // corlib classes - - // Let's use the no-namespace format for these too - GDMonoClass *class_MonoObject = nullptr; // object - GDMonoClass *class_bool = nullptr; // bool - GDMonoClass *class_int8_t = nullptr; // sbyte - GDMonoClass *class_int16_t = nullptr; // short - GDMonoClass *class_int32_t = nullptr; // int - GDMonoClass *class_int64_t = nullptr; // long - GDMonoClass *class_uint8_t = nullptr; // byte - GDMonoClass *class_uint16_t = nullptr; // ushort - GDMonoClass *class_uint32_t = nullptr; // uint - GDMonoClass *class_uint64_t = nullptr; // ulong - GDMonoClass *class_float = nullptr; // float - GDMonoClass *class_double = nullptr; // double - GDMonoClass *class_String = nullptr; // string - GDMonoClass *class_IntPtr = nullptr; // System.IntPtr - - GDMonoClass *class_System_Collections_IEnumerable = nullptr; - GDMonoClass *class_System_Collections_ICollection = nullptr; - GDMonoClass *class_System_Collections_IDictionary = nullptr; - -#ifdef DEBUG_ENABLED - GDMonoClass *class_System_Diagnostics_StackTrace = nullptr; - GDMonoMethodThunkR<MonoArray *, MonoObject *> methodthunk_System_Diagnostics_StackTrace_GetFrames; - GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_bool = nullptr; - GDMonoMethod *method_System_Diagnostics_StackTrace_ctor_Exception_bool = nullptr; +#ifdef WIN32 +#define GD_CLR_STDCALL __stdcall +#else +#define GD_CLR_STDCALL #endif - GDMonoClass *class_KeyNotFoundException = nullptr; - - MonoClass *rawclass_Dictionary = nullptr; - // ----------------------------------------------- - - GDMonoClass *class_Vector2 = nullptr; - GDMonoClass *class_Vector2i = nullptr; - GDMonoClass *class_Rect2 = nullptr; - GDMonoClass *class_Rect2i = nullptr; - GDMonoClass *class_Transform2D = nullptr; - GDMonoClass *class_Vector3 = nullptr; - GDMonoClass *class_Vector3i = nullptr; - GDMonoClass *class_Vector4 = nullptr; - GDMonoClass *class_Vector4i = nullptr; - GDMonoClass *class_Basis = nullptr; - GDMonoClass *class_Quaternion = nullptr; - GDMonoClass *class_Transform3D = nullptr; - GDMonoClass *class_Projection = nullptr; - GDMonoClass *class_AABB = nullptr; - GDMonoClass *class_Color = nullptr; - GDMonoClass *class_Plane = nullptr; - GDMonoClass *class_StringName = nullptr; - GDMonoClass *class_NodePath = nullptr; - GDMonoClass *class_RID = nullptr; - GDMonoClass *class_GodotObject = nullptr; - GDMonoClass *class_GodotResource = nullptr; - GDMonoClass *class_Node = nullptr; - GDMonoClass *class_Control = nullptr; - GDMonoClass *class_Node3D = nullptr; - GDMonoClass *class_WeakRef = nullptr; - GDMonoClass *class_Callable = nullptr; - GDMonoClass *class_SignalInfo = nullptr; - GDMonoClass *class_Array = nullptr; - GDMonoClass *class_Dictionary = nullptr; - GDMonoClass *class_MarshalUtils = nullptr; - GDMonoClass *class_ISerializationListener = nullptr; - -#ifdef DEBUG_ENABLED - GDMonoClass *class_DebuggingUtils = nullptr; - GDMonoMethodThunk<MonoObject *, MonoString **, int *, MonoString **> methodthunk_DebuggingUtils_GetStackFrameInfo; -#endif +struct godotsharp_property_info { + godot_string_name name; // Not owned + godot_string hint_string; + Variant::Type type; + PropertyHint hint; + PropertyUsageFlags usage; + bool exported; +}; - GDMonoClass *class_ExportAttribute = nullptr; - GDMonoField *field_ExportAttribute_hint = nullptr; - GDMonoField *field_ExportAttribute_hintString = nullptr; - GDMonoClass *class_SignalAttribute = nullptr; - GDMonoClass *class_ToolAttribute = nullptr; - GDMonoClass *class_RPCAttribute = nullptr; - GDMonoProperty *property_RPCAttribute_Mode = nullptr; - GDMonoProperty *property_RPCAttribute_CallLocal = nullptr; - GDMonoProperty *property_RPCAttribute_TransferMode = nullptr; - GDMonoProperty *property_RPCAttribute_TransferChannel = nullptr; - GDMonoClass *class_GodotMethodAttribute = nullptr; - GDMonoField *field_GodotMethodAttribute_methodName = nullptr; - GDMonoClass *class_ScriptPathAttribute = nullptr; - GDMonoField *field_ScriptPathAttribute_path = nullptr; - GDMonoClass *class_AssemblyHasScriptsAttribute = nullptr; - GDMonoField *field_AssemblyHasScriptsAttribute_requiresLookup = nullptr; - GDMonoField *field_AssemblyHasScriptsAttribute_scriptTypes = nullptr; - - GDMonoField *field_GodotObject_ptr = nullptr; - GDMonoField *field_StringName_ptr = nullptr; - GDMonoField *field_NodePath_ptr = nullptr; - GDMonoField *field_Image_ptr = nullptr; - GDMonoField *field_RID_ptr = nullptr; - - GDMonoMethodThunk<MonoObject *> methodthunk_GodotObject_Dispose; - GDMonoMethodThunkR<Array *, MonoObject *> methodthunk_Array_GetPtr; - GDMonoMethodThunkR<Dictionary *, MonoObject *> methodthunk_Dictionary_GetPtr; - GDMonoMethodThunk<MonoObject *, MonoArray *> methodthunk_SignalAwaiter_SignalCallback; - GDMonoMethodThunk<MonoObject *> methodthunk_GodotTaskScheduler_Activate; - - GDMonoMethodThunkR<MonoBoolean, MonoObject *, MonoObject *> methodthunk_Delegate_Equals; - - GDMonoMethodThunkR<MonoBoolean, MonoDelegate *, MonoObject *> methodthunk_DelegateUtils_TrySerializeDelegate; - GDMonoMethodThunkR<MonoBoolean, MonoObject *, MonoDelegate **> methodthunk_DelegateUtils_TryDeserializeDelegate; - - // Start of MarshalUtils methods - - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericArray; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericDictionary; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsSystemGenericList; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsSystemGenericDictionary; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericIEnumerable; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericICollection; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeIsGenericIDictionary; - GDMonoMethodThunkR<MonoBoolean, MonoReflectionType *> methodthunk_MarshalUtils_TypeHasFlagsAttribute; - - GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **> methodthunk_MarshalUtils_GetGenericTypeDefinition; - - GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **> methodthunk_MarshalUtils_ArrayGetElementType; - GDMonoMethodThunk<MonoReflectionType *, MonoReflectionType **, MonoReflectionType **> methodthunk_MarshalUtils_DictionaryGetKeyValueTypes; - - GDMonoMethodThunkR<MonoReflectionType *, MonoReflectionType *> methodthunk_MarshalUtils_MakeGenericArrayType; - GDMonoMethodThunkR<MonoReflectionType *, MonoReflectionType *, MonoReflectionType *> methodthunk_MarshalUtils_MakeGenericDictionaryType; - - // End of MarshalUtils methods - - Ref<MonoGCHandleRef> task_scheduler_handle; - - bool corlib_cache_updated; - bool godot_api_cache_updated; - - void clear_corlib_cache(); - void clear_godot_api_cache(); - - CachedData() { - clear_corlib_cache(); - clear_godot_api_cache(); - } +struct godotsharp_property_def_val_pair { + godot_string_name name; // Not owned + godot_variant value; }; -extern CachedData cached_data; +struct ManagedCallbacks { + using Callback_ScriptManagerBridge_GetPropertyInfoList_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, const String *, godotsharp_property_info *p_props, int32_t p_count); + using Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add = void(GD_CLR_STDCALL *)(CSharpScript *p_script, godotsharp_property_def_val_pair *p_def_vals, int32_t p_count); + + using FuncSignalAwaiter_SignalCallback = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, int32_t, bool *); + using FuncDelegateUtils_InvokeWithVariantArgs = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Variant **, uint32_t, const Variant *); + using FuncDelegateUtils_DelegateEquals = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr); + using FuncDelegateUtils_TrySerializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const Array *); + using FuncDelegateUtils_TryDeserializeDelegateWithGCHandle = bool(GD_CLR_STDCALL *)(const Array *, GCHandleIntPtr *); + using FuncScriptManagerBridge_FrameCallback = void(GD_CLR_STDCALL *)(); + using FuncScriptManagerBridge_CreateManagedForGodotObjectBinding = GCHandleIntPtr(GD_CLR_STDCALL *)(const StringName *, Object *); + using FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance = bool(GD_CLR_STDCALL *)(const CSharpScript *, Object *, const Variant **, int32_t); + using FuncScriptManagerBridge_GetScriptNativeName = void(GD_CLR_STDCALL *)(const CSharpScript *, StringName *); + using FuncScriptManagerBridge_SetGodotObjectPtr = void(GD_CLR_STDCALL *)(GCHandleIntPtr, Object *); + using FuncScriptManagerBridge_RaiseEventSignal = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, bool *); + using FuncScriptManagerBridge_ScriptIsOrInherits = bool(GD_CLR_STDCALL *)(const CSharpScript *, const CSharpScript *); + using FuncScriptManagerBridge_AddScriptBridge = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *); + using FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath = void(GD_CLR_STDCALL *)(const String *, Ref<CSharpScript> *); + using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *); + using FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass = bool(GD_CLR_STDCALL *)(const CSharpScript *); + using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Array *, Dictionary *, Dictionary *, Ref<CSharpScript> *); + using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool); + using FuncScriptManagerBridge_GetPropertyInfoList = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyInfoList_Add); + using FuncScriptManagerBridge_GetPropertyDefaultValues = void(GD_CLR_STDCALL *)(CSharpScript *, Callback_ScriptManagerBridge_GetPropertyDefaultValues_Add); + using FuncCSharpInstanceBridge_Call = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, Callable::CallError *, Variant *); + using FuncCSharpInstanceBridge_Set = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant *); + using FuncCSharpInstanceBridge_Get = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, Variant *); + using FuncCSharpInstanceBridge_CallDispose = void(GD_CLR_STDCALL *)(GCHandleIntPtr, bool); + using FuncCSharpInstanceBridge_CallToString = void(GD_CLR_STDCALL *)(GCHandleIntPtr, String *, bool *); + using FuncCSharpInstanceBridge_HasMethodUnknownParams = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *); + using FuncCSharpInstanceBridge_SerializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *); + using FuncCSharpInstanceBridge_DeserializeState = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const Dictionary *, const Dictionary *); + using FuncGCHandleBridge_FreeGCHandle = void(GD_CLR_STDCALL *)(GCHandleIntPtr); + using FuncDebuggingUtils_GetCurrentStackInfo = void(GD_CLR_STDCALL *)(Vector<ScriptLanguage::StackInfo> *); + using FuncDisposablesTracker_OnGodotShuttingDown = void(GD_CLR_STDCALL *)(); + using FuncGD_OnCoreApiAssemblyLoaded = void(GD_CLR_STDCALL *)(bool); + + FuncSignalAwaiter_SignalCallback SignalAwaiter_SignalCallback; + FuncDelegateUtils_InvokeWithVariantArgs DelegateUtils_InvokeWithVariantArgs; + FuncDelegateUtils_DelegateEquals DelegateUtils_DelegateEquals; + FuncDelegateUtils_TrySerializeDelegateWithGCHandle DelegateUtils_TrySerializeDelegateWithGCHandle; + FuncDelegateUtils_TryDeserializeDelegateWithGCHandle DelegateUtils_TryDeserializeDelegateWithGCHandle; + FuncScriptManagerBridge_FrameCallback ScriptManagerBridge_FrameCallback; + FuncScriptManagerBridge_CreateManagedForGodotObjectBinding ScriptManagerBridge_CreateManagedForGodotObjectBinding; + FuncScriptManagerBridge_CreateManagedForGodotObjectScriptInstance ScriptManagerBridge_CreateManagedForGodotObjectScriptInstance; + FuncScriptManagerBridge_GetScriptNativeName ScriptManagerBridge_GetScriptNativeName; + FuncScriptManagerBridge_SetGodotObjectPtr ScriptManagerBridge_SetGodotObjectPtr; + FuncScriptManagerBridge_RaiseEventSignal ScriptManagerBridge_RaiseEventSignal; + FuncScriptManagerBridge_ScriptIsOrInherits ScriptManagerBridge_ScriptIsOrInherits; + FuncScriptManagerBridge_AddScriptBridge ScriptManagerBridge_AddScriptBridge; + FuncScriptManagerBridge_GetOrCreateScriptBridgeForPath ScriptManagerBridge_GetOrCreateScriptBridgeForPath; + FuncScriptManagerBridge_RemoveScriptBridge ScriptManagerBridge_RemoveScriptBridge; + FuncScriptManagerBridge_TryReloadRegisteredScriptWithClass ScriptManagerBridge_TryReloadRegisteredScriptWithClass; + FuncScriptManagerBridge_UpdateScriptClassInfo ScriptManagerBridge_UpdateScriptClassInfo; + FuncScriptManagerBridge_SwapGCHandleForType ScriptManagerBridge_SwapGCHandleForType; + FuncScriptManagerBridge_GetPropertyInfoList ScriptManagerBridge_GetPropertyInfoList; + FuncScriptManagerBridge_GetPropertyDefaultValues ScriptManagerBridge_GetPropertyDefaultValues; + FuncCSharpInstanceBridge_Call CSharpInstanceBridge_Call; + FuncCSharpInstanceBridge_Set CSharpInstanceBridge_Set; + FuncCSharpInstanceBridge_Get CSharpInstanceBridge_Get; + FuncCSharpInstanceBridge_CallDispose CSharpInstanceBridge_CallDispose; + FuncCSharpInstanceBridge_CallToString CSharpInstanceBridge_CallToString; + FuncCSharpInstanceBridge_HasMethodUnknownParams CSharpInstanceBridge_HasMethodUnknownParams; + FuncCSharpInstanceBridge_SerializeState CSharpInstanceBridge_SerializeState; + FuncCSharpInstanceBridge_DeserializeState CSharpInstanceBridge_DeserializeState; + FuncGCHandleBridge_FreeGCHandle GCHandleBridge_FreeGCHandle; + FuncDebuggingUtils_GetCurrentStackInfo DebuggingUtils_GetCurrentStackInfo; + FuncDisposablesTracker_OnGodotShuttingDown DisposablesTracker_OnGodotShuttingDown; + FuncGD_OnCoreApiAssemblyLoaded GD_OnCoreApiAssemblyLoaded; +}; -void update_corlib_cache(); -void update_godot_api_cache(); +extern ManagedCallbacks managed_callbacks; +extern bool godot_api_cache_updated; -inline void clear_corlib_cache() { - cached_data.clear_corlib_cache(); -} +void update_godot_api_cache(const ManagedCallbacks &p_managed_callbacks); -inline void clear_godot_api_cache() { - cached_data.clear_godot_api_cache(); -} } // namespace GDMonoCache -#define CACHED_CLASS(m_class) (GDMonoCache::cached_data.class_##m_class) -#define CACHED_CLASS_RAW(m_class) (GDMonoCache::cached_data.class_##m_class->get_mono_ptr()) -#define CACHED_RAW_MONO_CLASS(m_class) (GDMonoCache::cached_data.rawclass_##m_class) -#define CACHED_FIELD(m_class, m_field) (GDMonoCache::cached_data.field_##m_class##_##m_field) -#define CACHED_METHOD(m_class, m_method) (GDMonoCache::cached_data.method_##m_class##_##m_method) -#define CACHED_METHOD_THUNK(m_class, m_method) (GDMonoCache::cached_data.methodthunk_##m_class##_##m_method) -#define CACHED_PROPERTY(m_class, m_property) (GDMonoCache::cached_data.property_##m_class##_##m_property) +#undef GD_CLR_STDCALL #endif // GD_MONO_CACHE_H diff --git a/modules/mono/mono_gd/gd_mono_class.cpp b/modules/mono/mono_gd/gd_mono_class.cpp deleted file mode 100644 index 51c5aa3542..0000000000 --- a/modules/mono/mono_gd/gd_mono_class.cpp +++ /dev/null @@ -1,576 +0,0 @@ -/*************************************************************************/ -/* gd_mono_class.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_class.h" - -#include <mono/metadata/attrdefs.h> -#include <mono/metadata/debug-helpers.h> - -#include "gd_mono_assembly.h" -#include "gd_mono_cache.h" -#include "gd_mono_marshal.h" - -String GDMonoClass::get_full_name(MonoClass *p_mono_class) { - // mono_type_get_full_name is not exposed to embedders, but this seems to do the job - MonoReflectionType *type_obj = mono_type_get_object(mono_domain_get(), get_mono_type(p_mono_class)); - - MonoException *exc = nullptr; - MonoString *str = GDMonoUtils::object_to_string((MonoObject *)type_obj, &exc); - UNHANDLED_EXCEPTION(exc); - - return GDMonoMarshal::mono_string_to_godot(str); -} - -MonoType *GDMonoClass::get_mono_type(MonoClass *p_mono_class) { - return mono_class_get_type(p_mono_class); -} - -String GDMonoClass::get_full_name() const { - return get_full_name(mono_class); -} - -String GDMonoClass::get_type_desc() const { - return GDMonoUtils::get_type_desc(get_mono_type()); -} - -MonoType *GDMonoClass::get_mono_type() const { - // Careful, you cannot compare two MonoType*. - // There is mono_metadata_type_equal, how is this different from comparing two MonoClass*? - return get_mono_type(mono_class); -} - -uint32_t GDMonoClass::get_flags() const { - return mono_class_get_flags(mono_class); -} - -bool GDMonoClass::is_static() const { - uint32_t static_class_flags = MONO_TYPE_ATTR_ABSTRACT | MONO_TYPE_ATTR_SEALED; - return (get_flags() & static_class_flags) == static_class_flags; -} - -bool GDMonoClass::is_assignable_from(GDMonoClass *p_from) const { - return mono_class_is_assignable_from(mono_class, p_from->mono_class); -} - -StringName GDMonoClass::get_namespace() const { - GDMonoClass *nesting_class = get_nesting_class(); - if (!nesting_class) { - return namespace_name; - } - return nesting_class->get_namespace(); -} - -String GDMonoClass::get_name_for_lookup() const { - GDMonoClass *nesting_class = get_nesting_class(); - if (!nesting_class) { - return class_name; - } - return nesting_class->get_name_for_lookup() + "/" + class_name; -} - -GDMonoClass *GDMonoClass::get_parent_class() const { - MonoClass *parent_mono_class = mono_class_get_parent(mono_class); - return parent_mono_class ? GDMono::get_singleton()->get_class(parent_mono_class) : nullptr; -} - -GDMonoClass *GDMonoClass::get_nesting_class() const { - MonoClass *nesting_type = mono_class_get_nesting_type(mono_class); - return nesting_type ? GDMono::get_singleton()->get_class(nesting_type) : nullptr; -} - -#ifdef TOOLS_ENABLED -Vector<MonoClassField *> GDMonoClass::get_enum_fields() { - bool class_is_enum = mono_class_is_enum(mono_class); - ERR_FAIL_COND_V(!class_is_enum, Vector<MonoClassField *>()); - - Vector<MonoClassField *> enum_fields; - - void *iter = nullptr; - MonoClassField *raw_field = nullptr; - while ((raw_field = mono_class_get_fields(get_mono_ptr(), &iter)) != nullptr) { - uint32_t field_flags = mono_field_get_flags(raw_field); - - // Enums have an instance field named value__ which holds the value of the enum. - // Enum constants are static, so we will use this to ignore the value__ field. - if (field_flags & MONO_FIELD_ATTR_PUBLIC && field_flags & MONO_FIELD_ATTR_STATIC) { - enum_fields.push_back(raw_field); - } - } - - return enum_fields; -} -#endif - -bool GDMonoClass::has_attribute(GDMonoClass *p_attr_class) { -#ifdef DEBUG_ENABLED - ERR_FAIL_NULL_V(p_attr_class, false); -#endif - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoClass::get_attribute(GDMonoClass *p_attr_class) { -#ifdef DEBUG_ENABLED - ERR_FAIL_NULL_V(p_attr_class, nullptr); -#endif - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoClass::fetch_attributes() { - ERR_FAIL_COND(attributes != nullptr); - - attributes = mono_custom_attrs_from_class(get_mono_ptr()); - attrs_fetched = true; -} - -void GDMonoClass::fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base) { - CRASH_COND(!CACHED_CLASS(GodotObject)->is_assignable_from(this)); - - if (methods_fetched) { - return; - } - - void *iter = nullptr; - MonoMethod *raw_method = nullptr; - while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != nullptr) { - StringName name = String::utf8(mono_method_get_name(raw_method)); - - // get_method implicitly fetches methods and adds them to this->methods - GDMonoMethod *method = get_method(raw_method, name); - ERR_CONTINUE(!method); - - if (method->get_name() != name) { -#ifdef DEBUG_ENABLED - String fullname = method->get_ret_type_full_name() + " " + name + "(" + method->get_signature_desc(true) + ")"; - WARN_PRINT("Method '" + fullname + "' is hidden by Godot API method. Should be '" + - method->get_full_name_no_class() + "'. In class '" + namespace_name + "." + class_name + "'."); -#endif - continue; - } - -#ifdef DEBUG_ENABLED - // For debug builds, we also fetched from native base classes as well before if this is not a native base class. - // This allows us to warn the user here if they are using snake_case by mistake. - - if (p_native_base != this) { - GDMonoClass *native_top = p_native_base; - while (native_top) { - GDMonoMethod *m = native_top->get_method(name, method->get_parameters_count()); - - if (m && m->get_name() != name) { - // found - String fullname = m->get_ret_type_full_name() + " " + name + "(" + m->get_signature_desc(true) + ")"; - WARN_PRINT("Method '" + fullname + "' should be '" + m->get_full_name_no_class() + - "'. In class '" + namespace_name + "." + class_name + "'."); - break; - } - - if (native_top == CACHED_CLASS(GodotObject)) { - break; - } - - native_top = native_top->get_parent_class(); - } - } -#endif - - uint32_t flags = mono_method_get_flags(method->mono_method, nullptr); - - if (!(flags & MONO_METHOD_ATTR_VIRTUAL)) { - continue; - } - - // Virtual method of Godot Object derived type, let's try to find GodotMethod attribute - - GDMonoClass *top = p_native_base; - - while (top) { - GDMonoMethod *base_method = top->get_method(name, method->get_parameters_count()); - - if (base_method && base_method->has_attribute(CACHED_CLASS(GodotMethodAttribute))) { - // Found base method with GodotMethod attribute. - // We get the original API method name from this attribute. - // This name must point to the virtual method. - - MonoObject *attr = base_method->get_attribute(CACHED_CLASS(GodotMethodAttribute)); - - StringName godot_method_name = CACHED_FIELD(GodotMethodAttribute, methodName)->get_string_value(attr); -#ifdef DEBUG_ENABLED - CRASH_COND(godot_method_name == StringName()); -#endif - MethodKey key = MethodKey(godot_method_name, method->get_parameters_count()); - GDMonoMethod **existing_method = methods.getptr(key); - if (existing_method) { - memdelete(*existing_method); // Must delete old one - } - methods.insert(key, method); - - break; - } - - if (top == CACHED_CLASS(GodotObject)) { - break; - } - - top = top->get_parent_class(); - } - } - - methods_fetched = true; -} - -GDMonoMethod *GDMonoClass::get_fetched_method_unknown_params(const StringName &p_name) { - ERR_FAIL_COND_V(!methods_fetched, nullptr); - - for (const KeyValue<MethodKey, GDMonoMethod *> &E : methods) { - if (E.key.name == p_name) { - return E.value; - } - } - - return nullptr; -} - -bool GDMonoClass::has_fetched_method_unknown_params(const StringName &p_name) { - return get_fetched_method_unknown_params(p_name) != nullptr; -} - -bool GDMonoClass::implements_interface(GDMonoClass *p_interface) { - return mono_class_implements_interface(mono_class, p_interface->get_mono_ptr()); -} - -bool GDMonoClass::has_public_parameterless_ctor() { - GDMonoMethod *ctor = get_method(".ctor", 0); - return ctor && ctor->get_visibility() == IMonoClassMember::PUBLIC; -} - -GDMonoMethod *GDMonoClass::get_method(const StringName &p_name, uint16_t p_params_count) { - MethodKey key = MethodKey(p_name, p_params_count); - - GDMonoMethod **match = methods.getptr(key); - - if (match) { - return *match; - } - - if (methods_fetched) { - return nullptr; - } - - MonoMethod *raw_method = mono_class_get_method_from_name(mono_class, String(p_name).utf8().get_data(), p_params_count); - - if (raw_method) { - GDMonoMethod *method = memnew(GDMonoMethod(p_name, raw_method)); - methods.insert(key, method); - - return method; - } - - return nullptr; -} - -GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method) { - MonoMethodSignature *sig = mono_method_signature(p_raw_method); - - int params_count = mono_signature_get_param_count(sig); - StringName method_name = String::utf8(mono_method_get_name(p_raw_method)); - - return get_method(p_raw_method, method_name, params_count); -} - -GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name) { - MonoMethodSignature *sig = mono_method_signature(p_raw_method); - int params_count = mono_signature_get_param_count(sig); - return get_method(p_raw_method, p_name, params_count); -} - -GDMonoMethod *GDMonoClass::get_method(MonoMethod *p_raw_method, const StringName &p_name, uint16_t p_params_count) { - ERR_FAIL_NULL_V(p_raw_method, nullptr); - - MethodKey key = MethodKey(p_name, p_params_count); - - GDMonoMethod **match = methods.getptr(key); - - if (match) { - return *match; - } - - GDMonoMethod *method = memnew(GDMonoMethod(p_name, p_raw_method)); - methods.insert(key, method); - - return method; -} - -GDMonoMethod *GDMonoClass::get_method_with_desc(const String &p_description, bool p_include_namespace) { - MonoMethodDesc *desc = mono_method_desc_new(p_description.utf8().get_data(), p_include_namespace); - MonoMethod *method = mono_method_desc_search_in_class(desc, mono_class); - mono_method_desc_free(desc); - - if (!method) { - return nullptr; - } - - ERR_FAIL_COND_V(mono_method_get_class(method) != mono_class, nullptr); - - return get_method(method); -} - -GDMonoField *GDMonoClass::get_field(const StringName &p_name) { - HashMap<StringName, GDMonoField *>::Iterator result = fields.find(p_name); - - if (result) { - return result->value; - } - - if (fields_fetched) { - return nullptr; - } - - MonoClassField *raw_field = mono_class_get_field_from_name(mono_class, String(p_name).utf8().get_data()); - - if (raw_field) { - GDMonoField *field = memnew(GDMonoField(raw_field, this)); - fields.insert(p_name, field); - - return field; - } - - return nullptr; -} - -const Vector<GDMonoField *> &GDMonoClass::get_all_fields() { - if (fields_fetched) { - return fields_list; - } - - void *iter = nullptr; - MonoClassField *raw_field = nullptr; - while ((raw_field = mono_class_get_fields(mono_class, &iter)) != nullptr) { - StringName name = String::utf8(mono_field_get_name(raw_field)); - - HashMap<StringName, GDMonoField *>::Iterator match = fields.find(name); - - if (match) { - fields_list.push_back(match->value); - } else { - GDMonoField *field = memnew(GDMonoField(raw_field, this)); - fields.insert(name, field); - fields_list.push_back(field); - } - } - - fields_fetched = true; - - return fields_list; -} - -GDMonoProperty *GDMonoClass::get_property(const StringName &p_name) { - HashMap<StringName, GDMonoProperty *>::Iterator result = properties.find(p_name); - - if (result) { - return result->value; - } - - if (properties_fetched) { - return nullptr; - } - - MonoProperty *raw_property = mono_class_get_property_from_name(mono_class, String(p_name).utf8().get_data()); - - if (raw_property) { - GDMonoProperty *property = memnew(GDMonoProperty(raw_property, this)); - properties.insert(p_name, property); - - return property; - } - - return nullptr; -} - -const Vector<GDMonoProperty *> &GDMonoClass::get_all_properties() { - if (properties_fetched) { - return properties_list; - } - - void *iter = nullptr; - MonoProperty *raw_property = nullptr; - while ((raw_property = mono_class_get_properties(mono_class, &iter)) != nullptr) { - StringName name = String::utf8(mono_property_get_name(raw_property)); - - HashMap<StringName, GDMonoProperty *>::Iterator match = properties.find(name); - - if (match) { - properties_list.push_back(match->value); - } else { - GDMonoProperty *property = memnew(GDMonoProperty(raw_property, this)); - properties.insert(name, property); - properties_list.push_back(property); - } - } - - properties_fetched = true; - - return properties_list; -} - -const Vector<GDMonoClass *> &GDMonoClass::get_all_delegates() { - if (delegates_fetched) { - return delegates_list; - } - - // If the class is generic we must use the generic type definition. - MonoClass *klass = mono_class; - if (mono_type_get_type(get_mono_type()) == MONO_TYPE_GENERICINST) { - MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), get_mono_type()); - GDMonoUtils::Marshal::get_generic_type_definition(reftype, &reftype); - MonoType *type = mono_reflection_type_get_type(reftype); - klass = mono_class_from_mono_type(type); - } - - void *iter = nullptr; - MonoClass *raw_class = nullptr; - while ((raw_class = mono_class_get_nested_types(klass, &iter)) != nullptr) { - if (mono_class_is_delegate(raw_class)) { - StringName name = String::utf8(mono_class_get_name(raw_class)); - - HashMap<StringName, GDMonoClass *>::Iterator match = delegates.find(name); - - if (match) { - delegates_list.push_back(match->value); - } else { - GDMonoClass *delegate = memnew(GDMonoClass(String::utf8(mono_class_get_namespace(raw_class)), String::utf8(mono_class_get_name(raw_class)), raw_class, assembly)); - delegates.insert(name, delegate); - delegates_list.push_back(delegate); - } - } - } - - delegates_fetched = true; - - return delegates_list; -} - -const Vector<GDMonoMethod *> &GDMonoClass::get_all_methods() { - if (!method_list_fetched) { - void *iter = nullptr; - MonoMethod *raw_method = nullptr; - while ((raw_method = mono_class_get_methods(get_mono_ptr(), &iter)) != nullptr) { - method_list.push_back(memnew(GDMonoMethod(String::utf8(mono_method_get_name(raw_method)), raw_method))); - } - - method_list_fetched = true; - } - - return method_list; -} - -GDMonoClass::GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly) { - namespace_name = p_namespace; - class_name = p_name; - mono_class = p_class; - assembly = p_assembly; - - attrs_fetched = false; - attributes = nullptr; - - methods_fetched = false; - method_list_fetched = false; - fields_fetched = false; - properties_fetched = false; - delegates_fetched = false; -} - -GDMonoClass::~GDMonoClass() { - if (attributes) { - mono_custom_attrs_free(attributes); - } - - for (const KeyValue<StringName, GDMonoField *> &E : fields) { - memdelete(E.value); - } - - for (const KeyValue<StringName, GDMonoProperty *> &E : properties) { - memdelete(E.value); - } - - { - // Ugly workaround... - // We may have duplicated values, because we redirect snake_case methods to PascalCasel (only Godot API methods). - // This way, we end with both the snake_case name and the PascalCasel name paired with the same method. - // Therefore, we must avoid deleting the same pointer twice. - - int offset = 0; - Vector<GDMonoMethod *> deleted_methods; - deleted_methods.resize(methods.size()); - - for (const KeyValue<MethodKey, GDMonoMethod *> &E : methods) { - GDMonoMethod *method = E.value; - - if (method) { - for (int i = 0; i < offset; i++) { - if (deleted_methods[i] == method) { - // Already deleted - goto already_deleted; - } - } - - deleted_methods.write[offset] = method; - ++offset; - - memdelete(method); - } - - already_deleted:; - } - - methods.clear(); - } - - for (int i = 0; i < method_list.size(); ++i) { - memdelete(method_list[i]); - } -} diff --git a/modules/mono/mono_gd/gd_mono_class.h b/modules/mono/mono_gd/gd_mono_class.h deleted file mode 100644 index 6b35da30f9..0000000000 --- a/modules/mono/mono_gd/gd_mono_class.h +++ /dev/null @@ -1,160 +0,0 @@ -/*************************************************************************/ -/* gd_mono_class.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_CLASS_H -#define GD_MONO_CLASS_H - -#include "core/string/ustring.h" -#include "core/templates/rb_map.h" - -#include "gd_mono_field.h" -#include "gd_mono_header.h" -#include "gd_mono_method.h" -#include "gd_mono_property.h" -#include "gd_mono_utils.h" - -class GDMonoClass { - struct MethodKey { - struct Hasher { - static _FORCE_INLINE_ uint32_t hash(const MethodKey &p_key) { - uint32_t hash = 0; - - GDMonoUtils::hash_combine(hash, p_key.name.hash()); - GDMonoUtils::hash_combine(hash, HashMapHasherDefault::hash(p_key.params_count)); - - return hash; - } - }; - - _FORCE_INLINE_ bool operator==(const MethodKey &p_a) const { - return p_a.params_count == params_count && p_a.name == name; - } - - MethodKey() {} - - MethodKey(const StringName &p_name, uint16_t p_params_count) : - name(p_name), params_count(p_params_count) { - } - - StringName name; - uint16_t params_count = 0; - }; - - StringName namespace_name; - StringName class_name; - - MonoClass *mono_class = nullptr; - GDMonoAssembly *assembly = nullptr; - - bool attrs_fetched; - MonoCustomAttrInfo *attributes = nullptr; - - // This contains both the original method names and remapped method names from the native Godot identifiers to the C# functions. - // Most method-related functions refer to this and it's possible this is unintuitive for outside users; this may be a prime location for refactoring or renaming. - bool methods_fetched; - HashMap<MethodKey, GDMonoMethod *, MethodKey::Hasher> methods; - - bool method_list_fetched; - Vector<GDMonoMethod *> method_list; - - bool fields_fetched; - HashMap<StringName, GDMonoField *> fields; - Vector<GDMonoField *> fields_list; - - bool properties_fetched; - HashMap<StringName, GDMonoProperty *> properties; - Vector<GDMonoProperty *> properties_list; - - bool delegates_fetched; - HashMap<StringName, GDMonoClass *> delegates; - Vector<GDMonoClass *> delegates_list; - - friend class GDMonoAssembly; - GDMonoClass(const StringName &p_namespace, const StringName &p_name, MonoClass *p_class, GDMonoAssembly *p_assembly); - -public: - static String get_full_name(MonoClass *p_mono_class); - static MonoType *get_mono_type(MonoClass *p_mono_class); - - String get_full_name() const; - String get_type_desc() const; - MonoType *get_mono_type() const; - - uint32_t get_flags() const; - bool is_static() const; - - bool is_assignable_from(GDMonoClass *p_from) const; - - StringName get_namespace() const; - _FORCE_INLINE_ StringName get_name() const { return class_name; } - String get_name_for_lookup() const; - - _FORCE_INLINE_ MonoClass *get_mono_ptr() const { return mono_class; } - _FORCE_INLINE_ const GDMonoAssembly *get_assembly() const { return assembly; } - - GDMonoClass *get_parent_class() const; - GDMonoClass *get_nesting_class() const; - -#ifdef TOOLS_ENABLED - Vector<MonoClassField *> get_enum_fields(); -#endif - - GDMonoMethod *get_fetched_method_unknown_params(const StringName &p_name); - bool has_fetched_method_unknown_params(const StringName &p_name); - - bool has_attribute(GDMonoClass *p_attr_class); - MonoObject *get_attribute(GDMonoClass *p_attr_class); - - void fetch_attributes(); - void fetch_methods_with_godot_api_checks(GDMonoClass *p_native_base); - - bool implements_interface(GDMonoClass *p_interface); - bool has_public_parameterless_ctor(); - - GDMonoMethod *get_method(const StringName &p_name, uint16_t p_params_count = 0); - GDMonoMethod *get_method(MonoMethod *p_raw_method); - GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name); - GDMonoMethod *get_method(MonoMethod *p_raw_method, const StringName &p_name, uint16_t p_params_count); - GDMonoMethod *get_method_with_desc(const String &p_description, bool p_include_namespace); - - GDMonoField *get_field(const StringName &p_name); - const Vector<GDMonoField *> &get_all_fields(); - - GDMonoProperty *get_property(const StringName &p_name); - const Vector<GDMonoProperty *> &get_all_properties(); - - const Vector<GDMonoClass *> &get_all_delegates(); - - const Vector<GDMonoMethod *> &get_all_methods(); - - ~GDMonoClass(); -}; - -#endif // GD_MONO_CLASS_H diff --git a/modules/mono/mono_gd/gd_mono_field.cpp b/modules/mono/mono_gd/gd_mono_field.cpp deleted file mode 100644 index cb025fc67a..0000000000 --- a/modules/mono/mono_gd/gd_mono_field.cpp +++ /dev/null @@ -1,556 +0,0 @@ -/*************************************************************************/ -/* gd_mono_field.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_field.h" - -#include <mono/metadata/attrdefs.h> - -#include "gd_mono_cache.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_utils.h" - -void GDMonoField::set_value(MonoObject *p_object, MonoObject *p_value) { - mono_field_set_value(p_object, mono_field, p_value); -} - -void GDMonoField::set_value_raw(MonoObject *p_object, void *p_ptr) { - mono_field_set_value(p_object, mono_field, &p_ptr); -} - -void GDMonoField::set_value_from_variant(MonoObject *p_object, const Variant &p_value) { - switch (type.type_encoding) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_value.operator bool(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_CHAR: { - int16_t val = p_value.operator unsigned short(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_I1: { - int8_t val = p_value.operator signed char(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_I2: { - int16_t val = p_value.operator signed short(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_I4: { - int32_t val = p_value.operator signed int(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_I8: { - int64_t val = p_value.operator int64_t(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_U1: { - uint8_t val = p_value.operator unsigned char(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_U2: { - uint16_t val = p_value.operator unsigned short(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_U4: { - uint32_t val = p_value.operator unsigned int(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_U8: { - uint64_t val = p_value.operator uint64_t(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_R4: { - float val = p_value.operator float(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_R8: { - double val = p_value.operator double(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case MONO_TYPE_VALUETYPE: { - GDMonoClass *tclass = type.type_class; - - if (tclass == CACHED_CLASS(Vector2)) { - GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_value.operator ::Vector2()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector2i)) { - GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_value.operator ::Vector2i()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Rect2)) { - GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_value.operator ::Rect2()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Rect2i)) { - GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_value.operator ::Rect2i()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Transform2D)) { - GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_value.operator ::Transform2D()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector3)) { - GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_value.operator ::Vector3()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector3i)) { - GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_value.operator ::Vector3i()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector4)) { - GDMonoMarshal::M_Vector4 from = MARSHALLED_OUT(Vector4, p_value.operator ::Vector4()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Vector4i)) { - GDMonoMarshal::M_Vector4i from = MARSHALLED_OUT(Vector4i, p_value.operator ::Vector4i()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Basis)) { - GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_value.operator ::Basis()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Quaternion)) { - GDMonoMarshal::M_Quaternion from = MARSHALLED_OUT(Quaternion, p_value.operator ::Quaternion()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Transform3D)) { - GDMonoMarshal::M_Transform3D from = MARSHALLED_OUT(Transform3D, p_value.operator ::Transform3D()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Projection)) { - GDMonoMarshal::M_Projection from = MARSHALLED_OUT(Projection, p_value.operator ::Projection()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(AABB)) { - GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_value.operator ::AABB()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Color)) { - GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_value.operator ::Color()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Plane)) { - GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_value.operator ::Plane()); - mono_field_set_value(p_object, mono_field, &from); - break; - } - - if (tclass == CACHED_CLASS(Callable)) { - GDMonoMarshal::M_Callable val = GDMonoMarshal::callable_to_managed(p_value.operator Callable()); - mono_field_set_value(p_object, mono_field, &val); - break; - } - - if (tclass == CACHED_CLASS(SignalInfo)) { - GDMonoMarshal::M_SignalInfo val = GDMonoMarshal::signal_info_to_managed(p_value.operator Signal()); - mono_field_set_value(p_object, mono_field, &val); - break; - } - - if (mono_class_is_enum(tclass->get_mono_ptr())) { - MonoType *enum_basetype = mono_class_enum_basetype(tclass->get_mono_ptr()); - switch (mono_type_get_type(enum_basetype)) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_value.operator bool(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_CHAR: { - uint16_t val = p_value.operator unsigned short(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_I1: { - int8_t val = p_value.operator signed char(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_I2: { - int16_t val = p_value.operator signed short(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_I4: { - int32_t val = p_value.operator signed int(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_I8: { - int64_t val = p_value.operator int64_t(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_U1: { - uint8_t val = p_value.operator unsigned char(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_U2: { - uint16_t val = p_value.operator unsigned short(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_U4: { - uint32_t val = p_value.operator unsigned int(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - case MONO_TYPE_U8: { - uint64_t val = p_value.operator uint64_t(); - mono_field_set_value(p_object, mono_field, &val); - break; - } - default: { - ERR_FAIL_MSG("Attempted to convert Variant to a managed enum value of unmarshallable base type."); - } - } - - break; - } - - ERR_FAIL_MSG("Attempted to set the value of a field of unmarshallable type: '" + tclass->get_name() + "'."); - } break; - case MONO_TYPE_STRING: { - if (p_value.get_type() == Variant::NIL) { - // Otherwise, Variant -> String would return the string "Null" - MonoString *mono_string = nullptr; - mono_field_set_value(p_object, mono_field, mono_string); - } else { - MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value); - mono_field_set_value(p_object, mono_field, mono_string); - } - } break; - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: { - MonoArray *managed = GDMonoMarshal::variant_to_mono_array(p_value, type.type_class); - if (likely(managed != nullptr)) { - mono_field_set_value(p_object, mono_field, managed); - } - } break; - case MONO_TYPE_CLASS: { - MonoObject *managed = GDMonoMarshal::variant_to_mono_object_of_class(p_value, type.type_class); - if (likely(managed != nullptr)) { - mono_field_set_value(p_object, mono_field, managed); - } - } break; - case MONO_TYPE_GENERICINST: { - MonoObject *managed = GDMonoMarshal::variant_to_mono_object_of_genericinst(p_value, type.type_class); - if (likely(managed != nullptr)) { - mono_field_set_value(p_object, mono_field, managed); - } - } break; - case MONO_TYPE_OBJECT: { - // Variant - switch (p_value.get_type()) { - case Variant::BOOL: { - MonoBoolean val = p_value.operator bool(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case Variant::INT: { - int32_t val = p_value.operator signed int(); - mono_field_set_value(p_object, mono_field, &val); - } break; - case Variant::FLOAT: { -#ifdef REAL_T_IS_DOUBLE - double val = p_value.operator double(); - mono_field_set_value(p_object, mono_field, &val); -#else - float val = p_value.operator float(); - mono_field_set_value(p_object, mono_field, &val); -#endif - } break; - case Variant::STRING: { - MonoString *mono_string = GDMonoMarshal::mono_string_from_godot(p_value); - mono_field_set_value(p_object, mono_field, mono_string); - } break; - case Variant::VECTOR2: { - GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_value.operator ::Vector2()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR2I: { - GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_value.operator ::Vector2i()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::RECT2: { - GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_value.operator ::Rect2()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::RECT2I: { - GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_value.operator ::Rect2i()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR3: { - GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_value.operator ::Vector3()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR3I: { - GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_value.operator ::Vector3i()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR4: { - GDMonoMarshal::M_Vector4 from = MARSHALLED_OUT(Vector4, p_value.operator ::Vector4()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::VECTOR4I: { - GDMonoMarshal::M_Vector4i from = MARSHALLED_OUT(Vector4i, p_value.operator ::Vector4i()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::TRANSFORM2D: { - GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_value.operator ::Transform2D()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::PLANE: { - GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_value.operator ::Plane()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::QUATERNION: { - GDMonoMarshal::M_Quaternion from = MARSHALLED_OUT(Quaternion, p_value.operator ::Quaternion()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::AABB: { - GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_value.operator ::AABB()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::BASIS: { - GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_value.operator ::Basis()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::TRANSFORM3D: { - GDMonoMarshal::M_Transform3D from = MARSHALLED_OUT(Transform3D, p_value.operator ::Transform3D()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::PROJECTION: { - GDMonoMarshal::M_Projection from = MARSHALLED_OUT(Projection, p_value.operator ::Projection()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::COLOR: { - GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_value.operator ::Color()); - mono_field_set_value(p_object, mono_field, &from); - } break; - case Variant::STRING_NAME: { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator StringName()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::NODE_PATH: { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator NodePath()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::RID: { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator ::RID()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::OBJECT: { - MonoObject *managed = GDMonoUtils::unmanaged_get_managed(p_value.operator Object *()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::CALLABLE: { - GDMonoMarshal::M_Callable val = GDMonoMarshal::callable_to_managed(p_value.operator Callable()); - mono_field_set_value(p_object, mono_field, &val); - } break; - case Variant::SIGNAL: { - GDMonoMarshal::M_SignalInfo val = GDMonoMarshal::signal_info_to_managed(p_value.operator Signal()); - mono_field_set_value(p_object, mono_field, &val); - } break; - case Variant::DICTIONARY: { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Dictionary(), CACHED_CLASS(Dictionary)); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::ARRAY: { - MonoObject *managed = GDMonoUtils::create_managed_from(p_value.operator Array(), CACHED_CLASS(Array)); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_BYTE_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedByteArray_to_mono_array(p_value.operator ::PackedByteArray()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_INT32_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedInt32Array_to_mono_array(p_value.operator ::PackedInt32Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_INT64_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedInt64Array_to_mono_array(p_value.operator ::PackedInt64Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_FLOAT32_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedFloat32Array_to_mono_array(p_value.operator ::PackedFloat32Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_FLOAT64_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedFloat64Array_to_mono_array(p_value.operator ::PackedFloat64Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_STRING_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedStringArray_to_mono_array(p_value.operator ::PackedStringArray()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_VECTOR2_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedVector2Array_to_mono_array(p_value.operator ::PackedVector2Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_VECTOR3_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedVector3Array_to_mono_array(p_value.operator ::PackedVector3Array()); - mono_field_set_value(p_object, mono_field, managed); - } break; - case Variant::PACKED_COLOR_ARRAY: { - MonoArray *managed = GDMonoMarshal::PackedColorArray_to_mono_array(p_value.operator ::PackedColorArray()); - mono_field_set_value(p_object, mono_field, managed); - } break; - default: - break; - } - } break; - default: { - ERR_PRINT("Attempted to set the value of a field of unexpected type encoding: " + itos(type.type_encoding) + "."); - } break; - } -} - -MonoObject *GDMonoField::get_value(MonoObject *p_object) { - return mono_field_get_value_object(mono_domain_get(), mono_field, p_object); -} - -bool GDMonoField::get_bool_value(MonoObject *p_object) { - return (bool)GDMonoMarshal::unbox<MonoBoolean>(get_value(p_object)); -} - -int GDMonoField::get_int_value(MonoObject *p_object) { - return GDMonoMarshal::unbox<int32_t>(get_value(p_object)); -} - -String GDMonoField::get_string_value(MonoObject *p_object) { - MonoObject *val = get_value(p_object); - return GDMonoMarshal::mono_string_to_godot((MonoString *)val); -} - -bool GDMonoField::has_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, false); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoField::get_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, nullptr); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoField::fetch_attributes() { - ERR_FAIL_COND(attributes != nullptr); - attributes = mono_custom_attrs_from_field(owner->get_mono_ptr(), mono_field); - attrs_fetched = true; -} - -bool GDMonoField::is_static() { - return mono_field_get_flags(mono_field) & MONO_FIELD_ATTR_STATIC; -} - -IMonoClassMember::Visibility GDMonoField::get_visibility() { - switch (mono_field_get_flags(mono_field) & MONO_FIELD_ATTR_FIELD_ACCESS_MASK) { - case MONO_FIELD_ATTR_PRIVATE: - return IMonoClassMember::PRIVATE; - case MONO_FIELD_ATTR_FAM_AND_ASSEM: - return IMonoClassMember::PROTECTED_AND_INTERNAL; - case MONO_FIELD_ATTR_ASSEMBLY: - return IMonoClassMember::INTERNAL; - case MONO_FIELD_ATTR_FAMILY: - return IMonoClassMember::PROTECTED; - case MONO_FIELD_ATTR_PUBLIC: - return IMonoClassMember::PUBLIC; - default: - ERR_FAIL_V(IMonoClassMember::PRIVATE); - } -} - -GDMonoField::GDMonoField(MonoClassField *p_mono_field, GDMonoClass *p_owner) { - owner = p_owner; - mono_field = p_mono_field; - name = String::utf8(mono_field_get_name(mono_field)); - MonoType *field_type = mono_field_get_type(mono_field); - type.type_encoding = mono_type_get_type(field_type); - MonoClass *field_type_class = mono_class_from_mono_type(field_type); - type.type_class = GDMono::get_singleton()->get_class(field_type_class); - - attrs_fetched = false; - attributes = nullptr; -} - -GDMonoField::~GDMonoField() { - if (attributes) { - mono_custom_attrs_free(attributes); - } -} diff --git a/modules/mono/mono_gd/gd_mono_field.h b/modules/mono/mono_gd/gd_mono_field.h deleted file mode 100644 index 1d30f7a369..0000000000 --- a/modules/mono/mono_gd/gd_mono_field.h +++ /dev/null @@ -1,78 +0,0 @@ -/*************************************************************************/ -/* gd_mono_field.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_FIELD_H -#define GD_MONO_FIELD_H - -#include "gd_mono.h" -#include "gd_mono_header.h" -#include "i_mono_class_member.h" - -class GDMonoField : public IMonoClassMember { - GDMonoClass *owner = nullptr; - MonoClassField *mono_field = nullptr; - - StringName name; - ManagedType type; - - bool attrs_fetched; - MonoCustomAttrInfo *attributes = nullptr; - -public: - virtual GDMonoClass *get_enclosing_class() const final { return owner; } - - virtual MemberType get_member_type() const final { return MEMBER_TYPE_FIELD; } - - virtual StringName get_name() const final { return name; } - - virtual bool is_static() final; - virtual Visibility get_visibility() final; - - virtual bool has_attribute(GDMonoClass *p_attr_class) final; - virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) final; - void fetch_attributes(); - - _FORCE_INLINE_ ManagedType get_type() const { return type; } - - void set_value(MonoObject *p_object, MonoObject *p_value); - void set_value_raw(MonoObject *p_object, void *p_ptr); - void set_value_from_variant(MonoObject *p_object, const Variant &p_value); - - MonoObject *get_value(MonoObject *p_object); - - bool get_bool_value(MonoObject *p_object); - int get_int_value(MonoObject *p_object); - String get_string_value(MonoObject *p_object); - - GDMonoField(MonoClassField *p_mono_field, GDMonoClass *p_owner); - ~GDMonoField(); -}; - -#endif // GD_MONO_FIELD_H diff --git a/modules/mono/mono_gd/gd_mono_internals.cpp b/modules/mono/mono_gd/gd_mono_internals.cpp deleted file mode 100644 index d206b0dfc3..0000000000 --- a/modules/mono/mono_gd/gd_mono_internals.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/*************************************************************************/ -/* gd_mono_internals.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_internals.h" - -#include "../csharp_script.h" -#include "../mono_gc_handle.h" -#include "../utils/macros.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_utils.h" - -#include "core/debugger/engine_debugger.h" -#include "core/debugger/script_debugger.h" - -#include <mono/metadata/exception.h> - -namespace GDMonoInternals { -void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged) { - // This method should not fail - - CRASH_COND(!unmanaged); - - // All mono objects created from the managed world (e.g.: 'new Player()') - // need to have a CSharpScript in order for their methods to be callable from the unmanaged side - - RefCounted *rc = Object::cast_to<RefCounted>(unmanaged); - - GDMonoClass *klass = GDMonoUtils::get_object_class(managed); - - CRASH_COND(!klass); - - GDMonoClass *native = GDMonoUtils::get_class_native_base(klass); - - CRASH_COND(native == nullptr); - - if (native == klass) { - // If it's just a wrapper Godot class and not a custom inheriting class, then attach a - // script binding instead. One of the advantages of this is that if a script is attached - // later and it's not a C# script, then the managed object won't have to be disposed. - // Another reason for doing this is that this instance could outlive CSharpLanguage, which would - // be problematic when using a script. See: https://github.com/godotengine/godot/issues/25621 - - CSharpScriptBinding script_binding; - - script_binding.inited = true; - script_binding.type_name = NATIVE_GDMONOCLASS_NAME(klass); - script_binding.wrapper_class = klass; - script_binding.gchandle = rc ? MonoGCHandleData::new_weak_handle(managed) : MonoGCHandleData::new_strong_handle(managed); - script_binding.owner = unmanaged; - - if (rc) { - // Unsafe refcount increment. The managed instance also counts as a reference. - // This way if the unmanaged world has no references to our owner - // but the managed instance is alive, the refcount will be 1 instead of 0. - // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) - - // May not me referenced yet, so we must use init_ref() instead of reference() - if (rc->init_ref()) { - CSharpLanguage::get_singleton()->post_unsafe_reference(rc); - } - } - - // The object was just created, no script instance binding should have been attached - CRASH_COND(CSharpLanguage::has_instance_binding(unmanaged)); - - void *data; - { - MutexLock lock(CSharpLanguage::get_singleton()->get_language_bind_mutex()); - data = (void *)CSharpLanguage::get_singleton()->insert_script_binding(unmanaged, script_binding); - } - - // Should be thread safe because the object was just created and nothing else should be referencing it - CSharpLanguage::set_instance_binding(unmanaged, data); - - return; - } - - MonoGCHandleData gchandle = rc ? MonoGCHandleData::new_weak_handle(managed) : MonoGCHandleData::new_strong_handle(managed); - - Ref<CSharpScript> script = CSharpScript::create_for_managed_type(klass, native); - - CRASH_COND(script.is_null()); - - CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(unmanaged, script.ptr(), gchandle); - - unmanaged->set_script_and_instance(script, csharp_instance); -} - -void unhandled_exception(MonoException *p_exc) { - mono_print_unhandled_exception((MonoObject *)p_exc); - gd_unhandled_exception_event(p_exc); - - if (GDMono::get_singleton()->get_unhandled_exception_policy() == GDMono::POLICY_TERMINATE_APP) { - // Too bad 'mono_invoke_unhandled_exception_hook' is not exposed to embedders - mono_unhandled_exception((MonoObject *)p_exc); - GDMono::unhandled_exception_hook((MonoObject *)p_exc, nullptr); - GD_UNREACHABLE(); - } else { -#ifdef DEBUG_ENABLED - GDMonoUtils::debug_send_unhandled_exception_error(p_exc); - if (EngineDebugger::is_active()) { - EngineDebugger::get_singleton()->poll_events(false); - } -#endif - } -} - -void gd_unhandled_exception_event(MonoException *p_exc) { - MonoImage *mono_image = GDMono::get_singleton()->get_core_api_assembly()->get_image(); - - MonoClass *gd_klass = mono_class_from_name(mono_image, "Godot", "GD"); - MonoMethod *unhandled_exception_method = mono_class_get_method_from_name(gd_klass, "OnUnhandledException", -1); - void *args[1]; - args[0] = p_exc; - mono_runtime_invoke(unhandled_exception_method, nullptr, (void **)args, nullptr); -} -} // namespace GDMonoInternals diff --git a/modules/mono/mono_gd/gd_mono_internals.h b/modules/mono/mono_gd/gd_mono_internals.h deleted file mode 100644 index a8f9cfa3ca..0000000000 --- a/modules/mono/mono_gd/gd_mono_internals.h +++ /dev/null @@ -1,52 +0,0 @@ -/*************************************************************************/ -/* gd_mono_internals.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_INTERNALS_H -#define GD_MONO_INTERNALS_H - -#include <mono/jit/jit.h> - -#include "../utils/macros.h" - -#include "core/object/class_db.h" - -namespace GDMonoInternals { -void tie_managed_to_unmanaged(MonoObject *managed, Object *unmanaged); - -/** - * Do not call this function directly. - * Use GDMonoUtils::debug_unhandled_exception(MonoException *) instead. - */ -void unhandled_exception(MonoException *p_exc); - -void gd_unhandled_exception_event(MonoException *p_exc); -} // namespace GDMonoInternals - -#endif // GD_MONO_INTERNALS_H diff --git a/modules/mono/mono_gd/gd_mono_log.cpp b/modules/mono/mono_gd/gd_mono_log.cpp deleted file mode 100644 index 6ea3c5539e..0000000000 --- a/modules/mono/mono_gd/gd_mono_log.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/*************************************************************************/ -/* gd_mono_log.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_log.h" - -#include <stdlib.h> // abort - -#include "core/io/dir_access.h" -#include "core/os/os.h" - -#include "../godotsharp_dirs.h" -#include "../utils/string_utils.h" - -static CharString get_default_log_level() { -#ifdef DEBUG_ENABLED - return String("info").utf8(); -#else - return String("warning").utf8(); -#endif -} - -GDMonoLog *GDMonoLog::singleton = nullptr; - -#ifdef GD_MONO_LOG_ENABLED - -static int get_log_level_id(const char *p_log_level) { - const char *valid_log_levels[] = { "error", "critical", "warning", "message", "info", "debug", nullptr }; - - int i = 0; - while (valid_log_levels[i]) { - if (!strcmp(valid_log_levels[i], p_log_level)) { - return i; - } - i++; - } - - return -1; -} - -static String make_text(const char *log_domain, const char *log_level, const char *message) { - String text(message); - text += " (in domain "; - text += log_domain; - if (log_level) { - text += ", "; - text += log_level; - } - text += ")"; - return text; -} - -void GDMonoLog::mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *) { - if (GDMonoLog::get_singleton()->log_level_id >= get_log_level_id(log_level)) { - String text = make_text(log_domain, log_level, message); - text += "\n"; - - GDMonoLog::get_singleton()->log_file->seek_end(); - GDMonoLog::get_singleton()->log_file->store_string(text); - } - - if (fatal) { - String text = make_text(log_domain, log_level, message); - ERR_PRINT("Mono: FATAL ERROR '" + text + "', ABORTING! Logfile: '" + GDMonoLog::get_singleton()->log_file_path + "'."); - // Make sure to flush before aborting - GDMonoLog::get_singleton()->log_file->flush(); - GDMonoLog::get_singleton()->log_file.unref(); - - abort(); - } -} - -bool GDMonoLog::_try_create_logs_dir(const String &p_logs_dir) { - if (!DirAccess::exists(p_logs_dir)) { - Ref<DirAccess> diraccess = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND_V(diraccess.is_null(), false); - Error logs_mkdir_err = diraccess->make_dir_recursive(p_logs_dir); - ERR_FAIL_COND_V_MSG(logs_mkdir_err != OK, false, "Failed to create mono logs directory."); - } - - return true; -} - -void GDMonoLog::_delete_old_log_files(const String &p_logs_dir) { - static const uint64_t MAX_SECS = 5 * 86400; // 5 days - - Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); - ERR_FAIL_COND(da.is_null()); - - Error err = da->change_dir(p_logs_dir); - ERR_FAIL_COND_MSG(err != OK, "Cannot change directory to '" + p_logs_dir + "'."); - - ERR_FAIL_COND(da->list_dir_begin() != OK); - - String current = da->get_next(); - while (!current.is_empty()) { - if (da->current_is_dir() || !current.ends_with(".txt")) { - current = da->get_next(); - continue; - } - - uint64_t modified_time = FileAccess::get_modified_time(da->get_current_dir().plus_file(current)); - - if (OS::get_singleton()->get_unix_time() - modified_time > MAX_SECS) { - da->remove(current); - } - current = da->get_next(); - } - - da->list_dir_end(); -} - -void GDMonoLog::initialize() { - CharString log_level = OS::get_singleton()->get_environment("GODOT_MONO_LOG_LEVEL").utf8(); - - if (log_level.length() != 0 && get_log_level_id(log_level.get_data()) == -1) { - ERR_PRINT(String() + "Mono: Ignoring invalid log level (GODOT_MONO_LOG_LEVEL): '" + log_level.get_data() + "'."); - log_level = CharString(); - } - - if (log_level.length() == 0) { - log_level = get_default_log_level(); - } - - String logs_dir = GodotSharpDirs::get_mono_logs_dir(); - - if (_try_create_logs_dir(logs_dir)) { - _delete_old_log_files(logs_dir); - - OS::Date date_now = OS::get_singleton()->get_date(); - OS::Time time_now = OS::get_singleton()->get_time(); - - String log_file_name = str_format("%04d-%02d-%02d_%02d.%02d.%02d", - (int)date_now.year, (int)date_now.month, (int)date_now.day, - (int)time_now.hour, (int)time_now.minute, (int)time_now.second); - - log_file_name += str_format("_%d", OS::get_singleton()->get_process_id()); - - log_file_name += ".log"; - - log_file_path = logs_dir.plus_file(log_file_name); - - log_file = FileAccess::open(log_file_path, FileAccess::WRITE); - if (log_file.is_null()) { - ERR_PRINT("Mono: Cannot create log file at: " + log_file_path); - } - } - - mono_trace_set_level_string(log_level.get_data()); - log_level_id = get_log_level_id(log_level.get_data()); - - if (log_file.is_valid()) { - OS::get_singleton()->print("Mono: Log file is: '%s'\n", log_file_path.utf8().get_data()); - mono_trace_set_log_handler(mono_log_callback, this); - } else { - OS::get_singleton()->printerr("Mono: No log file, using default log handler\n"); - } -} - -GDMonoLog::GDMonoLog() { - singleton = this; -} - -GDMonoLog::~GDMonoLog() { - singleton = nullptr; -} - -#else - -void GDMonoLog::initialize() { - CharString log_level = get_default_log_level(); - mono_trace_set_level_string(log_level.get_data()); -} - -GDMonoLog::GDMonoLog() { - singleton = this; -} - -GDMonoLog::~GDMonoLog() { - singleton = nullptr; -} - -#endif // !defined(JAVASCRIPT_ENABLED) diff --git a/modules/mono/mono_gd/gd_mono_log.h b/modules/mono/mono_gd/gd_mono_log.h deleted file mode 100644 index 93ba6a410e..0000000000 --- a/modules/mono/mono_gd/gd_mono_log.h +++ /dev/null @@ -1,71 +0,0 @@ -/*************************************************************************/ -/* gd_mono_log.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_LOG_H -#define GD_MONO_LOG_H - -#include <mono/utils/mono-logger.h> - -#include "core/typedefs.h" - -#if !defined(JAVASCRIPT_ENABLED) && !defined(IOS_ENABLED) -// We have custom mono log callbacks for WASM and iOS -#define GD_MONO_LOG_ENABLED -#endif - -#ifdef GD_MONO_LOG_ENABLED -#include "core/io/file_access.h" -#endif - -class GDMonoLog { -#ifdef GD_MONO_LOG_ENABLED - int log_level_id = -1; - - Ref<FileAccess> log_file; - String log_file_path; - - bool _try_create_logs_dir(const String &p_logs_dir); - void _delete_old_log_files(const String &p_logs_dir); - - static void mono_log_callback(const char *log_domain, const char *log_level, const char *message, mono_bool fatal, void *user_data); -#endif - - static GDMonoLog *singleton; - -public: - _FORCE_INLINE_ static GDMonoLog *get_singleton() { return singleton; } - - void initialize(); - - GDMonoLog(); - ~GDMonoLog(); -}; - -#endif // GD_MONO_LOG_H diff --git a/modules/mono/mono_gd/gd_mono_marshal.cpp b/modules/mono/mono_gd/gd_mono_marshal.cpp deleted file mode 100644 index a860442764..0000000000 --- a/modules/mono/mono_gd/gd_mono_marshal.cpp +++ /dev/null @@ -1,1824 +0,0 @@ -/*************************************************************************/ -/* gd_mono_marshal.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_marshal.h" - -#include "../signal_awaiter_utils.h" -#include "gd_mono.h" -#include "gd_mono_cache.h" -#include "gd_mono_class.h" - -namespace GDMonoMarshal { - -Variant::Type managed_to_variant_type(const ManagedType &p_type, bool *r_nil_is_variant) { - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: - return Variant::BOOL; - - case MONO_TYPE_I1: - return Variant::INT; - case MONO_TYPE_I2: - return Variant::INT; - case MONO_TYPE_I4: - return Variant::INT; - case MONO_TYPE_I8: - return Variant::INT; - - case MONO_TYPE_U1: - return Variant::INT; - case MONO_TYPE_U2: - return Variant::INT; - case MONO_TYPE_U4: - return Variant::INT; - case MONO_TYPE_U8: - return Variant::INT; - - case MONO_TYPE_R4: - return Variant::FLOAT; - case MONO_TYPE_R8: - return Variant::FLOAT; - - case MONO_TYPE_STRING: { - return Variant::STRING; - } break; - - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - - if (vtclass == CACHED_CLASS(Vector2)) { - return Variant::VECTOR2; - } - - if (vtclass == CACHED_CLASS(Vector2i)) { - return Variant::VECTOR2I; - } - - if (vtclass == CACHED_CLASS(Rect2)) { - return Variant::RECT2; - } - - if (vtclass == CACHED_CLASS(Rect2i)) { - return Variant::RECT2I; - } - - if (vtclass == CACHED_CLASS(Transform2D)) { - return Variant::TRANSFORM2D; - } - - if (vtclass == CACHED_CLASS(Vector3)) { - return Variant::VECTOR3; - } - - if (vtclass == CACHED_CLASS(Vector3i)) { - return Variant::VECTOR3I; - } - if (vtclass == CACHED_CLASS(Vector4)) { - return Variant::VECTOR4; - } - - if (vtclass == CACHED_CLASS(Vector4i)) { - return Variant::VECTOR4I; - } - - if (vtclass == CACHED_CLASS(Basis)) { - return Variant::BASIS; - } - - if (vtclass == CACHED_CLASS(Quaternion)) { - return Variant::QUATERNION; - } - - if (vtclass == CACHED_CLASS(Transform3D)) { - return Variant::TRANSFORM3D; - } - if (vtclass == CACHED_CLASS(Projection)) { - return Variant::PROJECTION; - } - if (vtclass == CACHED_CLASS(AABB)) { - return Variant::AABB; - } - - if (vtclass == CACHED_CLASS(Color)) { - return Variant::COLOR; - } - - if (vtclass == CACHED_CLASS(Plane)) { - return Variant::PLANE; - } - - if (vtclass == CACHED_CLASS(Callable)) { - return Variant::CALLABLE; - } - - if (vtclass == CACHED_CLASS(SignalInfo)) { - return Variant::SIGNAL; - } - - if (mono_class_is_enum(vtclass->get_mono_ptr())) { - return Variant::INT; - } - } break; - - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: { - MonoClass *elem_class = mono_class_get_element_class(p_type.type_class->get_mono_ptr()); - - if (elem_class == CACHED_CLASS_RAW(MonoObject)) { - return Variant::ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(uint8_t)) { - return Variant::PACKED_BYTE_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(int32_t)) { - return Variant::PACKED_INT32_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(int64_t)) { - return Variant::PACKED_INT64_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(float)) { - return Variant::PACKED_FLOAT32_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(double)) { - return Variant::PACKED_FLOAT64_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(String)) { - return Variant::PACKED_STRING_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(Vector2)) { - return Variant::PACKED_VECTOR2_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(Vector3)) { - return Variant::PACKED_VECTOR3_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(Color)) { - return Variant::PACKED_COLOR_ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(StringName)) { - return Variant::ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(NodePath)) { - return Variant::ARRAY; - } - - if (elem_class == CACHED_CLASS_RAW(RID)) { - return Variant::ARRAY; - } - - if (mono_class_is_enum(elem_class)) { - return Variant::ARRAY; - } - - GDMonoClass *array_type_class = GDMono::get_singleton()->get_class(elem_class); - if (CACHED_CLASS(GodotObject)->is_assignable_from(array_type_class)) { - return Variant::ARRAY; - } - } break; - - case MONO_TYPE_CLASS: { - GDMonoClass *type_class = p_type.type_class; - - // GodotObject - if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { - return Variant::OBJECT; - } - - if (CACHED_CLASS(StringName) == type_class) { - return Variant::STRING_NAME; - } - - if (CACHED_CLASS(NodePath) == type_class) { - return Variant::NODE_PATH; - } - - if (CACHED_CLASS(RID) == type_class) { - return Variant::RID; - } - - if (CACHED_CLASS(Dictionary) == type_class) { - return Variant::DICTIONARY; - } - - if (CACHED_CLASS(Array) == type_class) { - return Variant::ARRAY; - } - - // IDictionary - if (p_type.type_class == CACHED_CLASS(System_Collections_IDictionary)) { - return Variant::DICTIONARY; - } - - // ICollection or IEnumerable - if (p_type.type_class == CACHED_CLASS(System_Collections_ICollection) || - p_type.type_class == CACHED_CLASS(System_Collections_IEnumerable)) { - return Variant::ARRAY; - } - } break; - - case MONO_TYPE_OBJECT: { - if (r_nil_is_variant) { - *r_nil_is_variant = true; - } - return Variant::NIL; - } break; - - case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); - - // Godot.Collections.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { - return Variant::DICTIONARY; - } - - // Godot.Collections.Array<T> - if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { - return Variant::ARRAY; - } - - // System.Collections.Generic.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_system_generic_dictionary(reftype)) { - return Variant::DICTIONARY; - } - - // System.Collections.Generic.List<T> - if (GDMonoUtils::Marshal::type_is_system_generic_list(reftype)) { - return Variant::ARRAY; - } - - // IDictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_idictionary(reftype)) { - return Variant::DICTIONARY; - } - - // ICollection<T> or IEnumerable<T> - if (GDMonoUtils::Marshal::type_is_generic_icollection(reftype) || GDMonoUtils::Marshal::type_is_generic_ienumerable(reftype)) { - return Variant::ARRAY; - } - - // GodotObject - GDMonoClass *type_class = p_type.type_class; - if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { - return Variant::OBJECT; - } - } break; - - default: { - } break; - } - - if (r_nil_is_variant) { - *r_nil_is_variant = false; - } - - // Unknown - return Variant::NIL; -} - -bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type) { - switch (p_array_type.type_encoding) { - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: { - MonoClass *elem_class = mono_class_get_element_class(p_array_type.type_class->get_mono_ptr()); - r_elem_type = ManagedType::from_class(elem_class); - return true; - } break; - case MONO_TYPE_GENERICINST: { - MonoReflectionType *array_reftype = mono_type_get_object(mono_domain_get(), p_array_type.type_class->get_mono_type()); - - if (GDMonoUtils::Marshal::type_is_generic_array(array_reftype) || - GDMonoUtils::Marshal::type_is_system_generic_list(array_reftype) || - GDMonoUtils::Marshal::type_is_generic_icollection(array_reftype) || - GDMonoUtils::Marshal::type_is_generic_ienumerable(array_reftype)) { - MonoReflectionType *elem_reftype; - - GDMonoUtils::Marshal::array_get_element_type(array_reftype, &elem_reftype); - - r_elem_type = ManagedType::from_reftype(elem_reftype); - return true; - } - } break; - default: { - } break; - } - - return false; -} - -MonoString *variant_to_mono_string(const Variant &p_var) { - if (p_var.get_type() == Variant::NIL) { - return nullptr; // Otherwise, Variant -> String would return the string "Null" - } - return mono_string_from_godot(p_var.operator String()); -} - -MonoArray *variant_to_mono_array(const Variant &p_var, GDMonoClass *p_type_class) { - MonoArrayType *array_type = mono_type_get_array_type(p_type_class->get_mono_type()); - - if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) { - return Array_to_mono_array(p_var.operator Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) { - return PackedByteArray_to_mono_array(p_var.operator PackedByteArray()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) { - return PackedInt32Array_to_mono_array(p_var.operator PackedInt32Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) { - return PackedInt64Array_to_mono_array(p_var.operator PackedInt64Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(float)) { - return PackedFloat32Array_to_mono_array(p_var.operator PackedFloat32Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(double)) { - return PackedFloat64Array_to_mono_array(p_var.operator PackedFloat64Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(String)) { - return PackedStringArray_to_mono_array(p_var.operator PackedStringArray()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) { - return PackedVector2Array_to_mono_array(p_var.operator PackedVector2Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) { - return PackedVector3Array_to_mono_array(p_var.operator PackedVector3Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Color)) { - return PackedColorArray_to_mono_array(p_var.operator PackedColorArray()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(StringName)) { - return Array_to_mono_array(p_var.operator Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(NodePath)) { - return Array_to_mono_array(p_var.operator Array()); - } - - if (array_type->eklass == CACHED_CLASS_RAW(RID)) { - return Array_to_mono_array(p_var.operator Array()); - } - - if (mono_class_is_assignable_from(CACHED_CLASS(GodotObject)->get_mono_ptr(), array_type->eklass)) { - return Array_to_mono_array(p_var.operator ::Array(), array_type->eklass); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to array of unsupported element type:" + GDMonoClass::get_full_name(array_type->eklass) + "'."); -} - -MonoObject *variant_to_mono_object_of_class(const Variant &p_var, GDMonoClass *p_type_class) { - // GodotObject - if (CACHED_CLASS(GodotObject)->is_assignable_from(p_type_class)) { - return GDMonoUtils::unmanaged_get_managed(p_var.operator Object *()); - } - - if (CACHED_CLASS(StringName) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator StringName()); - } - - if (CACHED_CLASS(NodePath) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator NodePath()); - } - - if (CACHED_CLASS(RID) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator ::RID()); - } - - // Godot.Collections.Dictionary or IDictionary - if (CACHED_CLASS(Dictionary) == p_type_class || CACHED_CLASS(System_Collections_IDictionary) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), CACHED_CLASS(Dictionary)); - } - - // Godot.Collections.Array or ICollection or IEnumerable - if (CACHED_CLASS(Array) == p_type_class || - CACHED_CLASS(System_Collections_ICollection) == p_type_class || - CACHED_CLASS(System_Collections_IEnumerable) == p_type_class) { - return GDMonoUtils::create_managed_from(p_var.operator Array(), CACHED_CLASS(Array)); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported type: '" + p_type_class->get_full_name() + "'."); -} - -MonoObject *variant_to_mono_object_of_genericinst(const Variant &p_var, GDMonoClass *p_type_class) { - MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type_class->get_mono_type()); - - // Godot.Collections.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { - return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), p_type_class); - } - - // Godot.Collections.Array<T> - if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { - return GDMonoUtils::create_managed_from(p_var.operator Array(), p_type_class); - } - - // System.Collections.Generic.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_system_generic_dictionary(reftype)) { - MonoReflectionType *key_reftype = nullptr; - MonoReflectionType *value_reftype = nullptr; - GDMonoUtils::Marshal::dictionary_get_key_value_types(reftype, &key_reftype, &value_reftype); - return Dictionary_to_system_generic_dict(p_var.operator Dictionary(), p_type_class, key_reftype, value_reftype); - } - - // System.Collections.Generic.List<T> - if (GDMonoUtils::Marshal::type_is_system_generic_list(reftype)) { - MonoReflectionType *elem_reftype = nullptr; - GDMonoUtils::Marshal::array_get_element_type(reftype, &elem_reftype); - return Array_to_system_generic_list(p_var.operator Array(), p_type_class, elem_reftype); - } - - // IDictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_idictionary(reftype)) { - MonoReflectionType *key_reftype; - MonoReflectionType *value_reftype; - GDMonoUtils::Marshal::dictionary_get_key_value_types(reftype, &key_reftype, &value_reftype); - GDMonoClass *godot_dict_class = GDMonoUtils::Marshal::make_generic_dictionary_type(key_reftype, value_reftype); - - return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), godot_dict_class); - } - - // ICollection<T> or IEnumerable<T> - if (GDMonoUtils::Marshal::type_is_generic_icollection(reftype) || GDMonoUtils::Marshal::type_is_generic_ienumerable(reftype)) { - MonoReflectionType *elem_reftype; - GDMonoUtils::Marshal::array_get_element_type(reftype, &elem_reftype); - GDMonoClass *godot_array_class = GDMonoUtils::Marshal::make_generic_array_type(elem_reftype); - - return GDMonoUtils::create_managed_from(p_var.operator Array(), godot_array_class); - } - - // GodotObject - if (CACHED_CLASS(GodotObject)->is_assignable_from(p_type_class)) { - return GDMonoUtils::unmanaged_get_managed(p_var.operator Object *()); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported generic type: '" + p_type_class->get_full_name() + "'."); -} - -MonoObject *variant_to_mono_object(const Variant &p_var) { - // Variant - switch (p_var.get_type()) { - case Variant::BOOL: { - MonoBoolean val = p_var.operator bool(); - return BOX_BOOLEAN(val); - } - case Variant::INT: { - int64_t val = p_var.operator int64_t(); - return BOX_INT64(val); - } - case Variant::FLOAT: { -#ifdef REAL_T_IS_DOUBLE - double val = p_var.operator double(); - return BOX_DOUBLE(val); -#else - float val = p_var.operator float(); - return BOX_FLOAT(val); -#endif - } - case Variant::STRING: - return (MonoObject *)mono_string_from_godot(p_var.operator String()); - case Variant::VECTOR2: { - GDMonoMarshal::M_Vector2 from = MARSHALLED_OUT(Vector2, p_var.operator ::Vector2()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2), &from); - } - case Variant::VECTOR2I: { - GDMonoMarshal::M_Vector2i from = MARSHALLED_OUT(Vector2i, p_var.operator ::Vector2i()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector2i), &from); - } - case Variant::RECT2: { - GDMonoMarshal::M_Rect2 from = MARSHALLED_OUT(Rect2, p_var.operator ::Rect2()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2), &from); - } - case Variant::RECT2I: { - GDMonoMarshal::M_Rect2i from = MARSHALLED_OUT(Rect2i, p_var.operator ::Rect2i()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Rect2i), &from); - } - case Variant::VECTOR3: { - GDMonoMarshal::M_Vector3 from = MARSHALLED_OUT(Vector3, p_var.operator ::Vector3()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3), &from); - } - case Variant::VECTOR3I: { - GDMonoMarshal::M_Vector3i from = MARSHALLED_OUT(Vector3i, p_var.operator ::Vector3i()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector3i), &from); - } - case Variant::TRANSFORM2D: { - GDMonoMarshal::M_Transform2D from = MARSHALLED_OUT(Transform2D, p_var.operator ::Transform2D()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform2D), &from); - } - case Variant::VECTOR4: { - GDMonoMarshal::M_Vector4 from = MARSHALLED_OUT(Vector4, p_var.operator ::Vector4()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector4), &from); - } - case Variant::VECTOR4I: { - GDMonoMarshal::M_Vector4i from = MARSHALLED_OUT(Vector4i, p_var.operator ::Vector4i()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Vector4i), &from); - } - case Variant::PLANE: { - GDMonoMarshal::M_Plane from = MARSHALLED_OUT(Plane, p_var.operator ::Plane()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Plane), &from); - } - case Variant::QUATERNION: { - GDMonoMarshal::M_Quaternion from = MARSHALLED_OUT(Quaternion, p_var.operator ::Quaternion()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Quaternion), &from); - } - case Variant::AABB: { - GDMonoMarshal::M_AABB from = MARSHALLED_OUT(AABB, p_var.operator ::AABB()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(AABB), &from); - } - case Variant::BASIS: { - GDMonoMarshal::M_Basis from = MARSHALLED_OUT(Basis, p_var.operator ::Basis()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Basis), &from); - } - case Variant::TRANSFORM3D: { - GDMonoMarshal::M_Transform3D from = MARSHALLED_OUT(Transform3D, p_var.operator ::Transform3D()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Transform3D), &from); - } - case Variant::PROJECTION: { - GDMonoMarshal::M_Projection from = MARSHALLED_OUT(Projection, p_var.operator ::Projection()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Projection), &from); - } - case Variant::COLOR: { - GDMonoMarshal::M_Color from = MARSHALLED_OUT(Color, p_var.operator ::Color()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Color), &from); - } - case Variant::STRING_NAME: - return GDMonoUtils::create_managed_from(p_var.operator StringName()); - case Variant::NODE_PATH: - return GDMonoUtils::create_managed_from(p_var.operator NodePath()); - case Variant::RID: - return GDMonoUtils::create_managed_from(p_var.operator ::RID()); - case Variant::OBJECT: - return GDMonoUtils::unmanaged_get_managed(p_var.operator Object *()); - case Variant::CALLABLE: { - GDMonoMarshal::M_Callable from = GDMonoMarshal::callable_to_managed(p_var.operator Callable()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Callable), &from); - } - case Variant::SIGNAL: { - GDMonoMarshal::M_SignalInfo from = GDMonoMarshal::signal_info_to_managed(p_var.operator Signal()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(SignalInfo), &from); - } - case Variant::DICTIONARY: - return GDMonoUtils::create_managed_from(p_var.operator Dictionary(), CACHED_CLASS(Dictionary)); - case Variant::ARRAY: - return GDMonoUtils::create_managed_from(p_var.operator Array(), CACHED_CLASS(Array)); - case Variant::PACKED_BYTE_ARRAY: - return (MonoObject *)PackedByteArray_to_mono_array(p_var.operator PackedByteArray()); - case Variant::PACKED_INT32_ARRAY: - return (MonoObject *)PackedInt32Array_to_mono_array(p_var.operator PackedInt32Array()); - case Variant::PACKED_INT64_ARRAY: - return (MonoObject *)PackedInt64Array_to_mono_array(p_var.operator PackedInt64Array()); - case Variant::PACKED_FLOAT32_ARRAY: - return (MonoObject *)PackedFloat32Array_to_mono_array(p_var.operator PackedFloat32Array()); - case Variant::PACKED_FLOAT64_ARRAY: - return (MonoObject *)PackedFloat64Array_to_mono_array(p_var.operator PackedFloat64Array()); - case Variant::PACKED_STRING_ARRAY: - return (MonoObject *)PackedStringArray_to_mono_array(p_var.operator PackedStringArray()); - case Variant::PACKED_VECTOR2_ARRAY: - return (MonoObject *)PackedVector2Array_to_mono_array(p_var.operator PackedVector2Array()); - case Variant::PACKED_VECTOR3_ARRAY: - return (MonoObject *)PackedVector3Array_to_mono_array(p_var.operator PackedVector3Array()); - case Variant::PACKED_COLOR_ARRAY: - return (MonoObject *)PackedColorArray_to_mono_array(p_var.operator PackedColorArray()); - default: - return nullptr; - } -} - -size_t variant_get_managed_unboxed_size(const ManagedType &p_type) { - // This method prints no errors for unsupported types. It's called on all methods, not only - // those that end up being invoked with Variant parameters. - - // For MonoObject* we return 0, as it doesn't need to be stored. - constexpr size_t zero_for_mono_object = 0; - - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: - return sizeof(MonoBoolean); - case MONO_TYPE_CHAR: - return sizeof(uint16_t); - case MONO_TYPE_I1: - return sizeof(int8_t); - case MONO_TYPE_I2: - return sizeof(int16_t); - case MONO_TYPE_I4: - return sizeof(int32_t); - case MONO_TYPE_I8: - return sizeof(int64_t); - case MONO_TYPE_U1: - return sizeof(uint8_t); - case MONO_TYPE_U2: - return sizeof(uint16_t); - case MONO_TYPE_U4: - return sizeof(uint32_t); - case MONO_TYPE_U8: - return sizeof(uint64_t); - case MONO_TYPE_R4: - return sizeof(float); - case MONO_TYPE_R8: - return sizeof(double); - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - -#define RETURN_CHECK_FOR_STRUCT(m_struct) \ - if (vtclass == CACHED_CLASS(m_struct)) { \ - return sizeof(M_##m_struct); \ - } - - RETURN_CHECK_FOR_STRUCT(Vector2); - RETURN_CHECK_FOR_STRUCT(Vector2i); - RETURN_CHECK_FOR_STRUCT(Rect2); - RETURN_CHECK_FOR_STRUCT(Rect2i); - RETURN_CHECK_FOR_STRUCT(Transform2D); - RETURN_CHECK_FOR_STRUCT(Vector3); - RETURN_CHECK_FOR_STRUCT(Vector3i); - RETURN_CHECK_FOR_STRUCT(Basis); - RETURN_CHECK_FOR_STRUCT(Quaternion); - RETURN_CHECK_FOR_STRUCT(Transform3D); - RETURN_CHECK_FOR_STRUCT(AABB); - RETURN_CHECK_FOR_STRUCT(Color); - RETURN_CHECK_FOR_STRUCT(Plane); - RETURN_CHECK_FOR_STRUCT(Callable); - RETURN_CHECK_FOR_STRUCT(SignalInfo); - -#undef RETURN_CHECK_FOR_STRUCT - - if (mono_class_is_enum(vtclass->get_mono_ptr())) { - MonoType *enum_basetype = mono_class_enum_basetype(vtclass->get_mono_ptr()); - switch (mono_type_get_type(enum_basetype)) { - case MONO_TYPE_BOOLEAN: - return sizeof(MonoBoolean); - case MONO_TYPE_CHAR: - return sizeof(uint16_t); - case MONO_TYPE_I1: - return sizeof(int8_t); - case MONO_TYPE_I2: - return sizeof(int16_t); - case MONO_TYPE_I4: - return sizeof(int32_t); - case MONO_TYPE_I8: - return sizeof(int64_t); - case MONO_TYPE_U1: - return sizeof(uint8_t); - case MONO_TYPE_U2: - return sizeof(uint16_t); - case MONO_TYPE_U4: - return sizeof(uint32_t); - case MONO_TYPE_U8: - return sizeof(uint64_t); - default: { - // Enum with unsupported base type. We return nullptr MonoObject* on error. - return zero_for_mono_object; - } - } - } - - // Enum with unsupported value type. We return nullptr MonoObject* on error. - } break; - case MONO_TYPE_STRING: - return zero_for_mono_object; - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - case MONO_TYPE_CLASS: - case MONO_TYPE_GENERICINST: - return zero_for_mono_object; - case MONO_TYPE_OBJECT: - return zero_for_mono_object; - } - - // Unsupported type encoding. We return nullptr MonoObject* on error. - return zero_for_mono_object; -} - -void *variant_to_managed_unboxed(const Variant &p_var, const ManagedType &p_type, void *r_buffer, unsigned int &r_offset) { -#define RETURN_TYPE_VAL(m_type, m_val) \ - *reinterpret_cast<m_type *>(r_buffer) = m_val; \ - r_offset += sizeof(m_type); \ - return r_buffer; - - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: - RETURN_TYPE_VAL(MonoBoolean, (MonoBoolean)p_var.operator bool()); - case MONO_TYPE_CHAR: - RETURN_TYPE_VAL(uint16_t, p_var.operator unsigned short()); - case MONO_TYPE_I1: - RETURN_TYPE_VAL(int8_t, p_var.operator signed char()); - case MONO_TYPE_I2: - RETURN_TYPE_VAL(int16_t, p_var.operator signed short()); - case MONO_TYPE_I4: - RETURN_TYPE_VAL(int32_t, p_var.operator signed int()); - case MONO_TYPE_I8: - RETURN_TYPE_VAL(int64_t, p_var.operator int64_t()); - case MONO_TYPE_U1: - RETURN_TYPE_VAL(uint8_t, p_var.operator unsigned char()); - case MONO_TYPE_U2: - RETURN_TYPE_VAL(uint16_t, p_var.operator unsigned short()); - case MONO_TYPE_U4: - RETURN_TYPE_VAL(uint32_t, p_var.operator unsigned int()); - case MONO_TYPE_U8: - RETURN_TYPE_VAL(uint64_t, p_var.operator uint64_t()); - case MONO_TYPE_R4: - RETURN_TYPE_VAL(float, p_var.operator float()); - case MONO_TYPE_R8: - RETURN_TYPE_VAL(double, p_var.operator double()); - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - -#define RETURN_CHECK_FOR_STRUCT(m_struct) \ - if (vtclass == CACHED_CLASS(m_struct)) { \ - GDMonoMarshal::M_##m_struct from = MARSHALLED_OUT(m_struct, p_var.operator ::m_struct()); \ - RETURN_TYPE_VAL(M_##m_struct, from); \ - } - - RETURN_CHECK_FOR_STRUCT(Vector2); - RETURN_CHECK_FOR_STRUCT(Vector2i); - RETURN_CHECK_FOR_STRUCT(Rect2); - RETURN_CHECK_FOR_STRUCT(Rect2i); - RETURN_CHECK_FOR_STRUCT(Transform2D); - RETURN_CHECK_FOR_STRUCT(Vector3); - RETURN_CHECK_FOR_STRUCT(Vector3i); - RETURN_CHECK_FOR_STRUCT(Basis); - RETURN_CHECK_FOR_STRUCT(Quaternion); - RETURN_CHECK_FOR_STRUCT(Transform3D); - RETURN_CHECK_FOR_STRUCT(AABB); - RETURN_CHECK_FOR_STRUCT(Color); - RETURN_CHECK_FOR_STRUCT(Plane); - -#undef RETURN_CHECK_FOR_STRUCT - - if (vtclass == CACHED_CLASS(Callable)) { - GDMonoMarshal::M_Callable from = GDMonoMarshal::callable_to_managed(p_var.operator Callable()); - RETURN_TYPE_VAL(M_Callable, from); - } - - if (vtclass == CACHED_CLASS(SignalInfo)) { - GDMonoMarshal::M_SignalInfo from = GDMonoMarshal::signal_info_to_managed(p_var.operator Signal()); - RETURN_TYPE_VAL(M_SignalInfo, from); - } - - if (mono_class_is_enum(vtclass->get_mono_ptr())) { - MonoType *enum_basetype = mono_class_enum_basetype(vtclass->get_mono_ptr()); - switch (mono_type_get_type(enum_basetype)) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_var.operator bool(); - RETURN_TYPE_VAL(MonoBoolean, val); - } - case MONO_TYPE_CHAR: { - uint16_t val = p_var.operator unsigned short(); - RETURN_TYPE_VAL(uint16_t, val); - } - case MONO_TYPE_I1: { - int8_t val = p_var.operator signed char(); - RETURN_TYPE_VAL(int8_t, val); - } - case MONO_TYPE_I2: { - int16_t val = p_var.operator signed short(); - RETURN_TYPE_VAL(int16_t, val); - } - case MONO_TYPE_I4: { - int32_t val = p_var.operator signed int(); - RETURN_TYPE_VAL(int32_t, val); - } - case MONO_TYPE_I8: { - int64_t val = p_var.operator int64_t(); - RETURN_TYPE_VAL(int64_t, val); - } - case MONO_TYPE_U1: { - uint8_t val = p_var.operator unsigned char(); - RETURN_TYPE_VAL(uint8_t, val); - } - case MONO_TYPE_U2: { - uint16_t val = p_var.operator unsigned short(); - RETURN_TYPE_VAL(uint16_t, val); - } - case MONO_TYPE_U4: { - uint32_t val = p_var.operator unsigned int(); - RETURN_TYPE_VAL(uint32_t, val); - } - case MONO_TYPE_U8: { - uint64_t val = p_var.operator uint64_t(); - RETURN_TYPE_VAL(uint64_t, val); - } - default: { - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to enum value of unsupported base type: '" + GDMonoClass::get_full_name(mono_class_from_mono_type(enum_basetype)) + "'."); - } - } - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported value type: '" + p_type.type_class->get_full_name() + "'."); - } break; -#undef RETURN_TYPE_VAL - case MONO_TYPE_STRING: - return variant_to_mono_string(p_var); - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - return variant_to_mono_array(p_var, p_type.type_class); - case MONO_TYPE_CLASS: - return variant_to_mono_object_of_class(p_var, p_type.type_class); - case MONO_TYPE_GENERICINST: - return variant_to_mono_object_of_genericinst(p_var, p_type.type_class); - case MONO_TYPE_OBJECT: - return variant_to_mono_object(p_var); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported type with encoding: " + itos(p_type.type_encoding) + "."); -} - -MonoObject *variant_to_mono_object(const Variant &p_var, const ManagedType &p_type) { - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_var.operator bool(); - return BOX_BOOLEAN(val); - } - case MONO_TYPE_CHAR: { - uint16_t val = p_var.operator unsigned short(); - return BOX_UINT16(val); - } - case MONO_TYPE_I1: { - int8_t val = p_var.operator signed char(); - return BOX_INT8(val); - } - case MONO_TYPE_I2: { - int16_t val = p_var.operator signed short(); - return BOX_INT16(val); - } - case MONO_TYPE_I4: { - int32_t val = p_var.operator signed int(); - return BOX_INT32(val); - } - case MONO_TYPE_I8: { - int64_t val = p_var.operator int64_t(); - return BOX_INT64(val); - } - case MONO_TYPE_U1: { - uint8_t val = p_var.operator unsigned char(); - return BOX_UINT8(val); - } - case MONO_TYPE_U2: { - uint16_t val = p_var.operator unsigned short(); - return BOX_UINT16(val); - } - case MONO_TYPE_U4: { - uint32_t val = p_var.operator unsigned int(); - return BOX_UINT32(val); - } - case MONO_TYPE_U8: { - uint64_t val = p_var.operator uint64_t(); - return BOX_UINT64(val); - } - case MONO_TYPE_R4: { - float val = p_var.operator float(); - return BOX_FLOAT(val); - } - case MONO_TYPE_R8: { - double val = p_var.operator double(); - return BOX_DOUBLE(val); - } - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - -#define RETURN_CHECK_FOR_STRUCT(m_struct) \ - if (vtclass == CACHED_CLASS(m_struct)) { \ - GDMonoMarshal::M_##m_struct from = MARSHALLED_OUT(m_struct, p_var.operator ::m_struct()); \ - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(m_struct), &from); \ - } - - RETURN_CHECK_FOR_STRUCT(Vector2); - RETURN_CHECK_FOR_STRUCT(Vector2i); - RETURN_CHECK_FOR_STRUCT(Rect2); - RETURN_CHECK_FOR_STRUCT(Rect2i); - RETURN_CHECK_FOR_STRUCT(Transform2D); - RETURN_CHECK_FOR_STRUCT(Vector3); - RETURN_CHECK_FOR_STRUCT(Vector3i); - RETURN_CHECK_FOR_STRUCT(Basis); - RETURN_CHECK_FOR_STRUCT(Quaternion); - RETURN_CHECK_FOR_STRUCT(Transform3D); - RETURN_CHECK_FOR_STRUCT(AABB); - RETURN_CHECK_FOR_STRUCT(Color); - RETURN_CHECK_FOR_STRUCT(Plane); - -#undef RETURN_CHECK_FOR_STRUCT - - if (vtclass == CACHED_CLASS(Callable)) { - GDMonoMarshal::M_Callable from = GDMonoMarshal::callable_to_managed(p_var.operator Callable()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(Callable), &from); - } - - if (vtclass == CACHED_CLASS(SignalInfo)) { - GDMonoMarshal::M_SignalInfo from = GDMonoMarshal::signal_info_to_managed(p_var.operator Signal()); - return mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(SignalInfo), &from); - } - - if (mono_class_is_enum(vtclass->get_mono_ptr())) { - MonoType *enum_basetype = mono_class_enum_basetype(vtclass->get_mono_ptr()); - MonoClass *enum_baseclass = mono_class_from_mono_type(enum_basetype); - switch (mono_type_get_type(enum_basetype)) { - case MONO_TYPE_BOOLEAN: { - MonoBoolean val = p_var.operator bool(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_CHAR: { - uint16_t val = p_var.operator unsigned short(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_I1: { - int8_t val = p_var.operator signed char(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_I2: { - int16_t val = p_var.operator signed short(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_I4: { - int32_t val = p_var.operator signed int(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_I8: { - int64_t val = p_var.operator int64_t(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_U1: { - uint8_t val = p_var.operator unsigned char(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_U2: { - uint16_t val = p_var.operator unsigned short(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_U4: { - uint32_t val = p_var.operator unsigned int(); - return BOX_ENUM(enum_baseclass, val); - } - case MONO_TYPE_U8: { - uint64_t val = p_var.operator uint64_t(); - return BOX_ENUM(enum_baseclass, val); - } - default: { - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to enum value of unsupported base type: '" + GDMonoClass::get_full_name(enum_baseclass) + "'."); - } - } - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported value type: '" + p_type.type_class->get_full_name() + "'."); - } break; - case MONO_TYPE_STRING: - return (MonoObject *)variant_to_mono_string(p_var); - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: - return (MonoObject *)variant_to_mono_array(p_var, p_type.type_class); - case MONO_TYPE_CLASS: - return variant_to_mono_object_of_class(p_var, p_type.type_class); - case MONO_TYPE_GENERICINST: - return variant_to_mono_object_of_genericinst(p_var, p_type.type_class); - case MONO_TYPE_OBJECT: - return variant_to_mono_object(p_var); - } - - ERR_FAIL_V_MSG(nullptr, "Attempted to convert Variant to unsupported type with encoding: " + itos(p_type.type_encoding) + "."); -} - -Variant mono_object_to_variant_impl(MonoObject *p_obj, const ManagedType &p_type, bool p_fail_with_err = true) { - ERR_FAIL_COND_V(!p_type.type_class, Variant()); - -#ifdef DEBUG_ENABLED - CRASH_COND_MSG(p_type.type_encoding == MONO_TYPE_OBJECT, "Type of object should be known."); -#endif - - switch (p_type.type_encoding) { - case MONO_TYPE_BOOLEAN: - return (bool)unbox<MonoBoolean>(p_obj); - case MONO_TYPE_CHAR: - return unbox<uint16_t>(p_obj); - case MONO_TYPE_I1: - return unbox<int8_t>(p_obj); - case MONO_TYPE_I2: - return unbox<int16_t>(p_obj); - case MONO_TYPE_I4: - return unbox<int32_t>(p_obj); - case MONO_TYPE_I8: - return unbox<int64_t>(p_obj); - case MONO_TYPE_U1: - return unbox<uint8_t>(p_obj); - case MONO_TYPE_U2: - return unbox<uint16_t>(p_obj); - case MONO_TYPE_U4: - return unbox<uint32_t>(p_obj); - case MONO_TYPE_U8: - return unbox<uint64_t>(p_obj); - case MONO_TYPE_R4: - return unbox<float>(p_obj); - case MONO_TYPE_R8: - return unbox<double>(p_obj); - case MONO_TYPE_VALUETYPE: { - GDMonoClass *vtclass = p_type.type_class; - - if (vtclass == CACHED_CLASS(Vector2)) { - return MARSHALLED_IN(Vector2, unbox_addr<GDMonoMarshal::M_Vector2>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Vector2i)) { - return MARSHALLED_IN(Vector2i, unbox_addr<GDMonoMarshal::M_Vector2i>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Rect2)) { - return MARSHALLED_IN(Rect2, unbox_addr<GDMonoMarshal::M_Rect2>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Rect2i)) { - return MARSHALLED_IN(Rect2i, unbox_addr<GDMonoMarshal::M_Rect2i>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Transform2D)) { - return MARSHALLED_IN(Transform2D, unbox_addr<GDMonoMarshal::M_Transform2D>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Vector3)) { - return MARSHALLED_IN(Vector3, unbox_addr<GDMonoMarshal::M_Vector3>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Vector3i)) { - return MARSHALLED_IN(Vector3i, unbox_addr<GDMonoMarshal::M_Vector3i>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Basis)) { - return MARSHALLED_IN(Basis, unbox_addr<GDMonoMarshal::M_Basis>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Quaternion)) { - return MARSHALLED_IN(Quaternion, unbox_addr<GDMonoMarshal::M_Quaternion>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Transform3D)) { - return MARSHALLED_IN(Transform3D, unbox_addr<GDMonoMarshal::M_Transform3D>(p_obj)); - } - - if (vtclass == CACHED_CLASS(AABB)) { - return MARSHALLED_IN(AABB, unbox_addr<GDMonoMarshal::M_AABB>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Color)) { - return MARSHALLED_IN(Color, unbox_addr<GDMonoMarshal::M_Color>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Plane)) { - return MARSHALLED_IN(Plane, unbox_addr<GDMonoMarshal::M_Plane>(p_obj)); - } - - if (vtclass == CACHED_CLASS(Callable)) { - return managed_to_callable(unbox<GDMonoMarshal::M_Callable>(p_obj)); - } - - if (vtclass == CACHED_CLASS(SignalInfo)) { - return managed_to_signal_info(unbox<GDMonoMarshal::M_SignalInfo>(p_obj)); - } - - if (mono_class_is_enum(vtclass->get_mono_ptr())) { - return unbox<int32_t>(p_obj); - } - } break; - case MONO_TYPE_STRING: { - if (p_obj == nullptr) { - return Variant(); // NIL - } - return mono_string_to_godot_not_null((MonoString *)p_obj); - } break; - case MONO_TYPE_ARRAY: - case MONO_TYPE_SZARRAY: { - MonoArrayType *array_type = mono_type_get_array_type(p_type.type_class->get_mono_type()); - - if (array_type->eklass == CACHED_CLASS_RAW(MonoObject)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(uint8_t)) { - return mono_array_to_PackedByteArray((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(int32_t)) { - return mono_array_to_PackedInt32Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(int64_t)) { - return mono_array_to_PackedInt64Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(float)) { - return mono_array_to_PackedFloat32Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(double)) { - return mono_array_to_PackedFloat64Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(String)) { - return mono_array_to_PackedStringArray((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Vector2)) { - return mono_array_to_PackedVector2Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Vector3)) { - return mono_array_to_PackedVector3Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(Color)) { - return mono_array_to_PackedColorArray((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(StringName)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(NodePath)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - if (array_type->eklass == CACHED_CLASS_RAW(RID)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - GDMonoClass *array_type_class = GDMono::get_singleton()->get_class(array_type->eklass); - if (CACHED_CLASS(GodotObject)->is_assignable_from(array_type_class)) { - return mono_array_to_Array((MonoArray *)p_obj); - } - - if (p_fail_with_err) { - ERR_FAIL_V_MSG(Variant(), "Attempted to convert a managed array of unmarshallable element type to Variant."); - } else { - return Variant(); - } - } break; - case MONO_TYPE_CLASS: { - GDMonoClass *type_class = p_type.type_class; - - // GodotObject - if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { - Object *ptr = unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_obj)); - if (ptr != nullptr) { - RefCounted *rc = Object::cast_to<RefCounted>(ptr); - return rc ? Variant(Ref<RefCounted>(rc)) : Variant(ptr); - } - return Variant(); - } - - if (CACHED_CLASS(StringName) == type_class) { - StringName *ptr = unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_obj)); - return ptr ? Variant(*ptr) : Variant(); - } - - if (CACHED_CLASS(NodePath) == type_class) { - NodePath *ptr = unbox<NodePath *>(CACHED_FIELD(NodePath, ptr)->get_value(p_obj)); - return ptr ? Variant(*ptr) : Variant(); - } - - if (CACHED_CLASS(RID) == type_class) { - RID *ptr = unbox<RID *>(CACHED_FIELD(RID, ptr)->get_value(p_obj)); - return ptr ? Variant(*ptr) : Variant(); - } - - // Godot.Collections.Dictionary - if (CACHED_CLASS(Dictionary) == type_class) { - MonoException *exc = nullptr; - Dictionary *ptr = CACHED_METHOD_THUNK(Dictionary, GetPtr).invoke(p_obj, &exc); - UNHANDLED_EXCEPTION(exc); - return ptr ? Variant(*ptr) : Variant(); - } - - // Godot.Collections.Array - if (CACHED_CLASS(Array) == type_class) { - MonoException *exc = nullptr; - Array *ptr = CACHED_METHOD_THUNK(Array, GetPtr).invoke(p_obj, &exc); - UNHANDLED_EXCEPTION(exc); - return ptr ? Variant(*ptr) : Variant(); - } - } break; - case MONO_TYPE_GENERICINST: { - MonoReflectionType *reftype = mono_type_get_object(mono_domain_get(), p_type.type_class->get_mono_type()); - - // Godot.Collections.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_generic_dictionary(reftype)) { - MonoException *exc = nullptr; - MonoObject *ret = p_type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); - UNHANDLED_EXCEPTION(exc); - return *unbox<Dictionary *>(ret); - } - - // Godot.Collections.Array<T> - if (GDMonoUtils::Marshal::type_is_generic_array(reftype)) { - MonoException *exc = nullptr; - MonoObject *ret = p_type.type_class->get_method("GetPtr")->invoke(p_obj, &exc); - UNHANDLED_EXCEPTION(exc); - return *unbox<Array *>(ret); - } - - // System.Collections.Generic.Dictionary<TKey, TValue> - if (GDMonoUtils::Marshal::type_is_system_generic_dictionary(reftype)) { - MonoReflectionType *key_reftype = nullptr; - MonoReflectionType *value_reftype = nullptr; - GDMonoUtils::Marshal::dictionary_get_key_value_types(reftype, &key_reftype, &value_reftype); - return system_generic_dict_to_Dictionary(p_obj, p_type.type_class, key_reftype, value_reftype); - } - - // System.Collections.Generic.List<T> - if (GDMonoUtils::Marshal::type_is_system_generic_list(reftype)) { - MonoReflectionType *elem_reftype = nullptr; - GDMonoUtils::Marshal::array_get_element_type(reftype, &elem_reftype); - return system_generic_list_to_Array_variant(p_obj, p_type.type_class, elem_reftype); - } - - // GodotObject - GDMonoClass *type_class = p_type.type_class; - if (CACHED_CLASS(GodotObject)->is_assignable_from(type_class)) { - Object *ptr = unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_obj)); - if (ptr != nullptr) { - RefCounted *rc = Object::cast_to<RefCounted>(ptr); - return rc ? Variant(Ref<RefCounted>(rc)) : Variant(ptr); - } - return Variant(); - } - } break; - } - - if (p_fail_with_err) { - ERR_FAIL_V_MSG(Variant(), "Attempted to convert an unmarshallable managed type to Variant. Name: '" + p_type.type_class->get_name() + "' Encoding: " + itos(p_type.type_encoding) + "."); - } else { - return Variant(); - } -} - -Variant mono_object_to_variant(MonoObject *p_obj) { - if (!p_obj) { - return Variant(); - } - - ManagedType type = ManagedType::from_class(mono_object_get_class(p_obj)); - - return mono_object_to_variant_impl(p_obj, type); -} - -Variant mono_object_to_variant(MonoObject *p_obj, const ManagedType &p_type) { - if (!p_obj) { - return Variant(); - } - - return mono_object_to_variant_impl(p_obj, p_type); -} - -Variant mono_object_to_variant_no_err(MonoObject *p_obj, const ManagedType &p_type) { - if (!p_obj) { - return Variant(); - } - - return mono_object_to_variant_impl(p_obj, p_type, /* fail_with_err: */ false); -} - -String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc) { - if (p_obj == nullptr) { - return String("null"); - } - - ManagedType type = ManagedType::from_class(mono_object_get_class(p_obj)); - Variant var = GDMonoMarshal::mono_object_to_variant_no_err(p_obj, type); - - if (var.get_type() == Variant::NIL) { // `&& p_obj != nullptr` but omitted because always true - // Cannot convert MonoObject* to Variant; fallback to 'ToString()'. - MonoException *exc = nullptr; - MonoString *mono_str = GDMonoUtils::object_to_string(p_obj, &exc); - - if (exc) { - if (r_exc) { - *r_exc = exc; - } - return String(); - } - - return GDMonoMarshal::mono_string_to_godot(mono_str); - } else { - return var.operator String(); - } -} - -MonoObject *Dictionary_to_system_generic_dict(const Dictionary &p_dict, GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) { - String ctor_desc = ":.ctor(System.Collections.Generic.IDictionary`2<" + GDMonoUtils::get_type_desc(p_key_reftype) + - ", " + GDMonoUtils::get_type_desc(p_value_reftype) + ">)"; - GDMonoMethod *ctor = p_class->get_method_with_desc(ctor_desc, true); - CRASH_COND(ctor == nullptr); - - MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, nullptr); - - GDMonoClass *godot_dict_class = GDMonoUtils::Marshal::make_generic_dictionary_type(p_key_reftype, p_value_reftype); - MonoObject *godot_dict = GDMonoUtils::create_managed_from(p_dict, godot_dict_class); - - void *ctor_args[1] = { godot_dict }; - - MonoException *exc = nullptr; - ctor->invoke_raw(mono_object, ctor_args, &exc); - UNHANDLED_EXCEPTION(exc); - - return mono_object; -} - -Dictionary system_generic_dict_to_Dictionary(MonoObject *p_obj, [[maybe_unused]] GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) { - GDMonoClass *godot_dict_class = GDMonoUtils::Marshal::make_generic_dictionary_type(p_key_reftype, p_value_reftype); - String ctor_desc = ":.ctor(System.Collections.Generic.IDictionary`2<" + GDMonoUtils::get_type_desc(p_key_reftype) + - ", " + GDMonoUtils::get_type_desc(p_value_reftype) + ">)"; - GDMonoMethod *godot_dict_ctor = godot_dict_class->get_method_with_desc(ctor_desc, true); - CRASH_COND(godot_dict_ctor == nullptr); - - MonoObject *godot_dict = mono_object_new(mono_domain_get(), godot_dict_class->get_mono_ptr()); - ERR_FAIL_NULL_V(godot_dict, Dictionary()); - - void *ctor_args[1] = { p_obj }; - - MonoException *exc = nullptr; - godot_dict_ctor->invoke_raw(godot_dict, ctor_args, &exc); - UNHANDLED_EXCEPTION(exc); - - exc = nullptr; - MonoObject *ret = godot_dict_class->get_method("GetPtr")->invoke(godot_dict, &exc); - UNHANDLED_EXCEPTION(exc); - - return *unbox<Dictionary *>(ret); -} - -MonoObject *Array_to_system_generic_list(const Array &p_array, GDMonoClass *p_class, MonoReflectionType *p_elem_reftype) { - MonoType *elem_type = mono_reflection_type_get_type(p_elem_reftype); - - String ctor_desc = ":.ctor(System.Collections.Generic.IEnumerable`1<" + GDMonoUtils::get_type_desc(elem_type) + ">)"; - GDMonoMethod *ctor = p_class->get_method_with_desc(ctor_desc, true); - CRASH_COND(ctor == nullptr); - - MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, nullptr); - - GDMonoClass *godot_array_class = GDMonoUtils::Marshal::make_generic_array_type(p_elem_reftype); - MonoObject *godot_array = GDMonoUtils::create_managed_from(p_array, godot_array_class); - - void *ctor_args[1] = { godot_array }; - - MonoException *exc = nullptr; - ctor->invoke_raw(mono_object, ctor_args, &exc); - UNHANDLED_EXCEPTION(exc); - - return mono_object; -} - -Variant system_generic_list_to_Array_variant(MonoObject *p_obj, GDMonoClass *p_class, [[maybe_unused]] MonoReflectionType *p_elem_reftype) { - GDMonoMethod *to_array = p_class->get_method("ToArray", 0); - CRASH_COND(to_array == nullptr); - - MonoException *exc = nullptr; - MonoObject *array = to_array->invoke_raw(p_obj, nullptr, &exc); - UNHANDLED_EXCEPTION(exc); - - ERR_FAIL_NULL_V(array, Variant()); - - ManagedType type = ManagedType::from_class(mono_object_get_class(array)); - - bool result_is_array = type.type_encoding != MONO_TYPE_SZARRAY && type.type_encoding != MONO_TYPE_ARRAY; - ERR_FAIL_COND_V(result_is_array, Variant()); - - return mono_object_to_variant(array, type); -} - -MonoArray *Array_to_mono_array(const Array &p_array) { - int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), length); - - for (int i = 0; i < length; i++) { - MonoObject *boxed = variant_to_mono_object(p_array[i]); - mono_array_setref(ret, i, boxed); - } - - return ret; -} - -MonoArray *Array_to_mono_array(const Array &p_array, MonoClass *p_array_type_class) { - int length = p_array.size(); - MonoArray *ret = mono_array_new(mono_domain_get(), p_array_type_class, length); - - for (int i = 0; i < length; i++) { - MonoObject *boxed = variant_to_mono_object(p_array[i]); - mono_array_setref(ret, i, boxed); - } - - return ret; -} - -Array mono_array_to_Array(MonoArray *p_array) { - Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - - for (int i = 0; i < length; i++) { - MonoObject *elem = mono_array_get(p_array, MonoObject *, i); - ret[i] = mono_object_to_variant(elem); - } - - return ret; -} - -MonoArray *PackedInt32Array_to_mono_array(const PackedInt32Array &p_array) { - const int32_t *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int32_t), length); - - int32_t *dst = mono_array_addr(ret, int32_t, 0); - memcpy(dst, src, length * sizeof(int32_t)); - - return ret; -} - -PackedInt32Array mono_array_to_PackedInt32Array(MonoArray *p_array) { - PackedInt32Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - int32_t *dst = ret.ptrw(); - - const int32_t *src = mono_array_addr(p_array, int32_t, 0); - memcpy(dst, src, length * sizeof(int32_t)); - - return ret; -} - -MonoArray *PackedInt64Array_to_mono_array(const PackedInt64Array &p_array) { - const int64_t *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(int64_t), length); - - int64_t *dst = mono_array_addr(ret, int64_t, 0); - memcpy(dst, src, length * sizeof(int64_t)); - - return ret; -} - -PackedInt64Array mono_array_to_PackedInt64Array(MonoArray *p_array) { - PackedInt64Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - int64_t *dst = ret.ptrw(); - - const int64_t *src = mono_array_addr(p_array, int64_t, 0); - memcpy(dst, src, length * sizeof(int64_t)); - - return ret; -} - -MonoArray *PackedByteArray_to_mono_array(const PackedByteArray &p_array) { - const uint8_t *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), length); - - uint8_t *dst = mono_array_addr(ret, uint8_t, 0); - memcpy(dst, src, length * sizeof(uint8_t)); - - return ret; -} - -PackedByteArray mono_array_to_PackedByteArray(MonoArray *p_array) { - PackedByteArray ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - uint8_t *dst = ret.ptrw(); - - const uint8_t *src = mono_array_addr(p_array, uint8_t, 0); - memcpy(dst, src, length * sizeof(uint8_t)); - - return ret; -} - -MonoArray *PackedFloat32Array_to_mono_array(const PackedFloat32Array &p_array) { - const float *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(float), length); - - float *dst = mono_array_addr(ret, float, 0); - memcpy(dst, src, length * sizeof(float)); - - return ret; -} - -PackedFloat32Array mono_array_to_PackedFloat32Array(MonoArray *p_array) { - PackedFloat32Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - float *dst = ret.ptrw(); - - const float *src = mono_array_addr(p_array, float, 0); - memcpy(dst, src, length * sizeof(float)); - - return ret; -} - -MonoArray *PackedFloat64Array_to_mono_array(const PackedFloat64Array &p_array) { - const double *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(double), length); - - double *dst = mono_array_addr(ret, double, 0); - memcpy(dst, src, length * sizeof(double)); - - return ret; -} - -PackedFloat64Array mono_array_to_PackedFloat64Array(MonoArray *p_array) { - PackedFloat64Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - double *dst = ret.ptrw(); - - const double *src = mono_array_addr(p_array, double, 0); - memcpy(dst, src, length * sizeof(double)); - - return ret; -} - -MonoArray *PackedStringArray_to_mono_array(const PackedStringArray &p_array) { - const String *r = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(String), length); - - for (int i = 0; i < length; i++) { - MonoString *boxed = mono_string_from_godot(r[i]); - mono_array_setref(ret, i, boxed); - } - - return ret; -} - -PackedStringArray mono_array_to_PackedStringArray(MonoArray *p_array) { - PackedStringArray ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - String *w = ret.ptrw(); - - for (int i = 0; i < length; i++) { - MonoString *elem = mono_array_get(p_array, MonoString *, i); - w[i] = mono_string_to_godot(elem); - } - - return ret; -} - -MonoArray *PackedColorArray_to_mono_array(const PackedColorArray &p_array) { - const Color *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Color), length); - - if constexpr (InteropLayout::MATCHES_Color) { - Color *dst = mono_array_addr(ret, Color, 0); - memcpy(dst, src, length * sizeof(Color)); - } else { - for (int i = 0; i < length; i++) { - M_Color *raw = (M_Color *)mono_array_addr_with_size(ret, sizeof(M_Color), i); - *raw = MARSHALLED_OUT(Color, src[i]); - } - } - - return ret; -} - -PackedColorArray mono_array_to_PackedColorArray(MonoArray *p_array) { - PackedColorArray ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - Color *dst = ret.ptrw(); - - if constexpr (InteropLayout::MATCHES_Color) { - const Color *src = mono_array_addr(p_array, Color, 0); - memcpy(dst, src, length * sizeof(Color)); - } else { - for (int i = 0; i < length; i++) { - dst[i] = MARSHALLED_IN(Color, (M_Color *)mono_array_addr_with_size(p_array, sizeof(M_Color), i)); - } - } - - return ret; -} - -MonoArray *PackedVector2Array_to_mono_array(const PackedVector2Array &p_array) { - const Vector2 *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector2), length); - - if constexpr (InteropLayout::MATCHES_Vector2) { - Vector2 *dst = mono_array_addr(ret, Vector2, 0); - memcpy(dst, src, length * sizeof(Vector2)); - } else { - for (int i = 0; i < length; i++) { - M_Vector2 *raw = (M_Vector2 *)mono_array_addr_with_size(ret, sizeof(M_Vector2), i); - *raw = MARSHALLED_OUT(Vector2, src[i]); - } - } - - return ret; -} - -PackedVector2Array mono_array_to_PackedVector2Array(MonoArray *p_array) { - PackedVector2Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - Vector2 *dst = ret.ptrw(); - - if constexpr (InteropLayout::MATCHES_Vector2) { - const Vector2 *src = mono_array_addr(p_array, Vector2, 0); - memcpy(dst, src, length * sizeof(Vector2)); - } else { - for (int i = 0; i < length; i++) { - dst[i] = MARSHALLED_IN(Vector2, (M_Vector2 *)mono_array_addr_with_size(p_array, sizeof(M_Vector2), i)); - } - } - - return ret; -} - -MonoArray *PackedVector3Array_to_mono_array(const PackedVector3Array &p_array) { - const Vector3 *src = p_array.ptr(); - int length = p_array.size(); - - MonoArray *ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(Vector3), length); - - if constexpr (InteropLayout::MATCHES_Vector3) { - Vector3 *dst = mono_array_addr(ret, Vector3, 0); - memcpy(dst, src, length * sizeof(Vector3)); - } else { - for (int i = 0; i < length; i++) { - M_Vector3 *raw = (M_Vector3 *)mono_array_addr_with_size(ret, sizeof(M_Vector3), i); - *raw = MARSHALLED_OUT(Vector3, src[i]); - } - } - - return ret; -} - -PackedVector3Array mono_array_to_PackedVector3Array(MonoArray *p_array) { - PackedVector3Array ret; - if (!p_array) { - return ret; - } - int length = mono_array_length(p_array); - ret.resize(length); - Vector3 *dst = ret.ptrw(); - - if constexpr (InteropLayout::MATCHES_Vector3) { - const Vector3 *src = mono_array_addr(p_array, Vector3, 0); - memcpy(dst, src, length * sizeof(Vector3)); - } else { - for (int i = 0; i < length; i++) { - dst[i] = MARSHALLED_IN(Vector3, (M_Vector3 *)mono_array_addr_with_size(p_array, sizeof(M_Vector3), i)); - } - } - - return ret; -} - -Callable managed_to_callable(const M_Callable &p_managed_callable) { - if (p_managed_callable.delegate) { - // TODO: Use pooling for ManagedCallable instances. - CallableCustom *managed_callable = memnew(ManagedCallable(p_managed_callable.delegate)); - return Callable(managed_callable); - } else { - Object *target = p_managed_callable.target - ? unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_managed_callable.target)) - : nullptr; - StringName *method_ptr = p_managed_callable.method_string_name - ? unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_callable.method_string_name)) - : nullptr; - StringName method = method_ptr ? *method_ptr : StringName(); - return Callable(target, method); - } -} - -M_Callable callable_to_managed(const Callable &p_callable) { - if (p_callable.is_custom()) { - CallableCustom *custom = p_callable.get_custom(); - CallableCustom::CompareEqualFunc compare_equal_func = custom->get_compare_equal_func(); - - if (compare_equal_func == ManagedCallable::compare_equal_func_ptr) { - ManagedCallable *managed_callable = static_cast<ManagedCallable *>(custom); - return { - nullptr, nullptr, - managed_callable->get_delegate() - }; - } else if (compare_equal_func == SignalAwaiterCallable::compare_equal_func_ptr) { - SignalAwaiterCallable *signal_awaiter_callable = static_cast<SignalAwaiterCallable *>(custom); - return { - GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(signal_awaiter_callable->get_object())), - GDMonoUtils::create_managed_from(signal_awaiter_callable->get_signal()), - nullptr - }; - } else if (compare_equal_func == EventSignalCallable::compare_equal_func_ptr) { - EventSignalCallable *event_signal_callable = static_cast<EventSignalCallable *>(custom); - return { - GDMonoUtils::unmanaged_get_managed(ObjectDB::get_instance(event_signal_callable->get_object())), - GDMonoUtils::create_managed_from(event_signal_callable->get_signal()), - nullptr - }; - } - - // Some other CallableCustom. We only support ManagedCallable. - return { nullptr, nullptr, nullptr }; - } else { - MonoObject *target_managed = GDMonoUtils::unmanaged_get_managed(p_callable.get_object()); - MonoObject *method_string_name_managed = GDMonoUtils::create_managed_from(p_callable.get_method()); - return { target_managed, method_string_name_managed, nullptr }; - } -} - -Signal managed_to_signal_info(const M_SignalInfo &p_managed_signal) { - Object *owner = p_managed_signal.owner - ? unbox<Object *>(CACHED_FIELD(GodotObject, ptr)->get_value(p_managed_signal.owner)) - : nullptr; - StringName *name_ptr = p_managed_signal.name_string_name - ? unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(p_managed_signal.name_string_name)) - : nullptr; - StringName name = name_ptr ? *name_ptr : StringName(); - return Signal(owner, name); -} - -M_SignalInfo signal_info_to_managed(const Signal &p_signal) { - Object *owner = p_signal.get_object(); - MonoObject *owner_managed = GDMonoUtils::unmanaged_get_managed(owner); - MonoObject *name_string_name_managed = GDMonoUtils::create_managed_from(p_signal.get_name()); - return { owner_managed, name_string_name_managed }; -} -} // namespace GDMonoMarshal diff --git a/modules/mono/mono_gd/gd_mono_marshal.h b/modules/mono/mono_gd/gd_mono_marshal.h deleted file mode 100644 index 51f11ab18a..0000000000 --- a/modules/mono/mono_gd/gd_mono_marshal.h +++ /dev/null @@ -1,605 +0,0 @@ -/*************************************************************************/ -/* gd_mono_marshal.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_MARSHAL_H -#define GD_MONO_MARSHAL_H - -#include "core/variant/variant.h" - -#include "../managed_callable.h" -#include "gd_mono.h" -#include "gd_mono_utils.h" - -namespace GDMonoMarshal { - -template <typename T> -T unbox(MonoObject *p_obj) { - return *(T *)mono_object_unbox(p_obj); -} - -template <typename T> -T *unbox_addr(MonoObject *p_obj) { - return (T *)mono_object_unbox(p_obj); -} - -#define BOX_DOUBLE(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(double), &x) -#define BOX_FLOAT(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(float), &x) -#define BOX_INT64(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int64_t), &x) -#define BOX_INT32(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int32_t), &x) -#define BOX_INT16(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int16_t), &x) -#define BOX_INT8(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(int8_t), &x) -#define BOX_UINT64(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint64_t), &x) -#define BOX_UINT32(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint32_t), &x) -#define BOX_UINT16(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint16_t), &x) -#define BOX_UINT8(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), &x) -#define BOX_BOOLEAN(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(bool), &x) -#define BOX_PTR(x) mono_value_box(mono_domain_get(), CACHED_CLASS_RAW(IntPtr), x) -#define BOX_ENUM(m_enum_class, x) mono_value_box(mono_domain_get(), m_enum_class, &x) - -Variant::Type managed_to_variant_type(const ManagedType &p_type, bool *r_nil_is_variant = nullptr); - -bool try_get_array_element_type(const ManagedType &p_array_type, ManagedType &r_elem_type); - -// String - -_FORCE_INLINE_ String mono_string_to_godot_not_null(MonoString *p_mono_string) { - char32_t *utf32 = (char32_t *)mono_string_to_utf32(p_mono_string); - String ret = String(utf32); - mono_free(utf32); - return ret; -} - -_FORCE_INLINE_ String mono_string_to_godot(MonoString *p_mono_string) { - if (p_mono_string == nullptr) { - return String(); - } - - return mono_string_to_godot_not_null(p_mono_string); -} - -_FORCE_INLINE_ MonoString *mono_string_from_godot(const String &p_string) { - return mono_string_from_utf32((mono_unichar4 *)(p_string.get_data())); -} - -// Variant - -size_t variant_get_managed_unboxed_size(const ManagedType &p_type); -void *variant_to_managed_unboxed(const Variant &p_var, const ManagedType &p_type, void *r_buffer, unsigned int &r_offset); -MonoObject *variant_to_mono_object(const Variant &p_var, const ManagedType &p_type); - -MonoObject *variant_to_mono_object(const Variant &p_var); -MonoArray *variant_to_mono_array(const Variant &p_var, GDMonoClass *p_type_class); -MonoObject *variant_to_mono_object_of_class(const Variant &p_var, GDMonoClass *p_type_class); -MonoObject *variant_to_mono_object_of_genericinst(const Variant &p_var, GDMonoClass *p_type_class); -MonoString *variant_to_mono_string(const Variant &p_var); - -// These overloads were added to avoid passing a `const Variant *` to the `const Variant &` -// parameter. That would result in the `Variant(bool)` copy constructor being called as -// pointers are implicitly converted to bool. Implicit conversions are f-ing evil. - -_FORCE_INLINE_ void *variant_to_managed_unboxed(const Variant *p_var, const ManagedType &p_type, void *r_buffer, unsigned int &r_offset) { - return variant_to_managed_unboxed(*p_var, p_type, r_buffer, r_offset); -} -_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant *p_var, const ManagedType &p_type) { - return variant_to_mono_object(*p_var, p_type); -} -_FORCE_INLINE_ MonoObject *variant_to_mono_object(const Variant *p_var) { - return variant_to_mono_object(*p_var); -} -_FORCE_INLINE_ MonoArray *variant_to_mono_array(const Variant *p_var, GDMonoClass *p_type_class) { - return variant_to_mono_array(*p_var, p_type_class); -} -_FORCE_INLINE_ MonoObject *variant_to_mono_object_of_class(const Variant *p_var, GDMonoClass *p_type_class) { - return variant_to_mono_object_of_class(*p_var, p_type_class); -} -_FORCE_INLINE_ MonoObject *variant_to_mono_object_of_genericinst(const Variant *p_var, GDMonoClass *p_type_class) { - return variant_to_mono_object_of_genericinst(*p_var, p_type_class); -} -_FORCE_INLINE_ MonoString *variant_to_mono_string(const Variant *p_var) { - return variant_to_mono_string(*p_var); -} - -Variant mono_object_to_variant(MonoObject *p_obj); -Variant mono_object_to_variant(MonoObject *p_obj, const ManagedType &p_type); -Variant mono_object_to_variant_no_err(MonoObject *p_obj, const ManagedType &p_type); - -/// Tries to convert the MonoObject* to Variant and then convert the Variant to String. -/// If the MonoObject* cannot be converted to Variant, then 'ToString()' is called instead. -String mono_object_to_variant_string(MonoObject *p_obj, MonoException **r_exc); - -// System.Collections.Generic - -MonoObject *Dictionary_to_system_generic_dict(const Dictionary &p_dict, GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype); -Dictionary system_generic_dict_to_Dictionary(MonoObject *p_obj, GDMonoClass *p_class, MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype); - -MonoObject *Array_to_system_generic_list(const Array &p_array, GDMonoClass *p_class, MonoReflectionType *p_elem_reftype); -Variant system_generic_list_to_Array_variant(MonoObject *p_obj, GDMonoClass *p_class, MonoReflectionType *p_elem_reftype); - -// Array - -MonoArray *Array_to_mono_array(const Array &p_array); -MonoArray *Array_to_mono_array(const Array &p_array, MonoClass *p_array_type_class); -Array mono_array_to_Array(MonoArray *p_array); - -// PackedInt32Array - -MonoArray *PackedInt32Array_to_mono_array(const PackedInt32Array &p_array); -PackedInt32Array mono_array_to_PackedInt32Array(MonoArray *p_array); - -// PackedInt64Array - -MonoArray *PackedInt64Array_to_mono_array(const PackedInt64Array &p_array); -PackedInt64Array mono_array_to_PackedInt64Array(MonoArray *p_array); - -// PackedByteArray - -MonoArray *PackedByteArray_to_mono_array(const PackedByteArray &p_array); -PackedByteArray mono_array_to_PackedByteArray(MonoArray *p_array); - -// PackedFloat32Array - -MonoArray *PackedFloat32Array_to_mono_array(const PackedFloat32Array &p_array); -PackedFloat32Array mono_array_to_PackedFloat32Array(MonoArray *p_array); - -// PackedFloat64Array - -MonoArray *PackedFloat64Array_to_mono_array(const PackedFloat64Array &p_array); -PackedFloat64Array mono_array_to_PackedFloat64Array(MonoArray *p_array); - -// PackedStringArray - -MonoArray *PackedStringArray_to_mono_array(const PackedStringArray &p_array); -PackedStringArray mono_array_to_PackedStringArray(MonoArray *p_array); - -// PackedColorArray - -MonoArray *PackedColorArray_to_mono_array(const PackedColorArray &p_array); -PackedColorArray mono_array_to_PackedColorArray(MonoArray *p_array); - -// PackedVector2Array - -MonoArray *PackedVector2Array_to_mono_array(const PackedVector2Array &p_array); -PackedVector2Array mono_array_to_PackedVector2Array(MonoArray *p_array); - -// PackedVector3Array - -MonoArray *PackedVector3Array_to_mono_array(const PackedVector3Array &p_array); -PackedVector3Array mono_array_to_PackedVector3Array(MonoArray *p_array); - -#pragma pack(push, 1) - -struct M_Callable { - MonoObject *target = nullptr; - MonoObject *method_string_name = nullptr; - MonoDelegate *delegate = nullptr; -}; - -struct M_SignalInfo { - MonoObject *owner = nullptr; - MonoObject *name_string_name = nullptr; -}; - -#pragma pack(pop) - -// Callable -Callable managed_to_callable(const M_Callable &p_managed_callable); -M_Callable callable_to_managed(const Callable &p_callable); - -// SignalInfo -Signal managed_to_signal_info(const M_SignalInfo &p_managed_signal); -M_SignalInfo signal_info_to_managed(const Signal &p_signal); - -// Structures - -namespace InteropLayout { - -enum { - MATCHES_int = (sizeof(int32_t) == sizeof(uint32_t)), - - MATCHES_float = (sizeof(float) == sizeof(uint32_t)), - - MATCHES_double = (sizeof(double) == sizeof(uint64_t)), - -#ifdef REAL_T_IS_DOUBLE - MATCHES_real_t = (sizeof(real_t) == sizeof(uint64_t)), -#else - MATCHES_real_t = (sizeof(real_t) == sizeof(uint32_t)), -#endif - - MATCHES_Vector2 = (MATCHES_real_t && (sizeof(Vector2) == (sizeof(real_t) * 2)) && - offsetof(Vector2, x) == (sizeof(real_t) * 0) && - offsetof(Vector2, y) == (sizeof(real_t) * 1)), - - MATCHES_Vector2i = (MATCHES_int && (sizeof(Vector2i) == (sizeof(int32_t) * 2)) && - offsetof(Vector2i, x) == (sizeof(int32_t) * 0) && - offsetof(Vector2i, y) == (sizeof(int32_t) * 1)), - - MATCHES_Rect2 = (MATCHES_Vector2 && (sizeof(Rect2) == (sizeof(Vector2) * 2)) && - offsetof(Rect2, position) == (sizeof(Vector2) * 0) && - offsetof(Rect2, size) == (sizeof(Vector2) * 1)), - - MATCHES_Rect2i = (MATCHES_Vector2i && (sizeof(Rect2i) == (sizeof(Vector2i) * 2)) && - offsetof(Rect2i, position) == (sizeof(Vector2i) * 0) && - offsetof(Rect2i, size) == (sizeof(Vector2i) * 1)), - - MATCHES_Transform2D = (MATCHES_Vector2 && (sizeof(Transform2D) == (sizeof(Vector2) * 3))), // No field offset required, it stores an array - - MATCHES_Vector3 = (MATCHES_real_t && (sizeof(Vector3) == (sizeof(real_t) * 3)) && - offsetof(Vector3, x) == (sizeof(real_t) * 0) && - offsetof(Vector3, y) == (sizeof(real_t) * 1) && - offsetof(Vector3, z) == (sizeof(real_t) * 2)), - - MATCHES_Vector4 = (MATCHES_real_t && (sizeof(Vector4) == (sizeof(real_t) * 4)) && - offsetof(Vector4, x) == (sizeof(real_t) * 0) && - offsetof(Vector4, y) == (sizeof(real_t) * 1) && - offsetof(Vector4, z) == (sizeof(real_t) * 2) && - offsetof(Vector4, w) == (sizeof(real_t) * 3)), - - MATCHES_Vector4i = (MATCHES_int && (sizeof(Vector4i) == (sizeof(int32_t) * 4)) && - offsetof(Vector4i, x) == (sizeof(int32_t) * 0) && - offsetof(Vector4i, y) == (sizeof(int32_t) * 1) && - offsetof(Vector4i, z) == (sizeof(int32_t) * 2) && - offsetof(Vector4i, w) == (sizeof(int32_t) * 3)), - - MATCHES_Vector3i = (MATCHES_int && (sizeof(Vector3i) == (sizeof(int32_t) * 3)) && - offsetof(Vector3i, x) == (sizeof(int32_t) * 0) && - offsetof(Vector3i, y) == (sizeof(int32_t) * 1) && - offsetof(Vector3i, z) == (sizeof(int32_t) * 2)), - - MATCHES_Basis = (MATCHES_Vector3 && (sizeof(Basis) == (sizeof(Vector3) * 3))), // No field offset required, it stores an array - - MATCHES_Quaternion = (MATCHES_real_t && (sizeof(Quaternion) == (sizeof(real_t) * 4)) && - offsetof(Quaternion, x) == (sizeof(real_t) * 0) && - offsetof(Quaternion, y) == (sizeof(real_t) * 1) && - offsetof(Quaternion, z) == (sizeof(real_t) * 2) && - offsetof(Quaternion, w) == (sizeof(real_t) * 3)), - - MATCHES_Transform3D = (MATCHES_Basis && MATCHES_Vector3 && (sizeof(Transform3D) == (sizeof(Basis) + sizeof(Vector3))) && - offsetof(Transform3D, basis) == 0 && - offsetof(Transform3D, origin) == sizeof(Basis)), - - MATCHES_Projection = (MATCHES_Vector4 && (sizeof(Projection) == (sizeof(Vector4) * 4))), - - MATCHES_AABB = (MATCHES_Vector3 && (sizeof(AABB) == (sizeof(Vector3) * 2)) && - offsetof(AABB, position) == (sizeof(Vector3) * 0) && - offsetof(AABB, size) == (sizeof(Vector3) * 1)), - - MATCHES_Color = (MATCHES_float && (sizeof(Color) == (sizeof(float) * 4)) && - offsetof(Color, r) == (sizeof(float) * 0) && - offsetof(Color, g) == (sizeof(float) * 1) && - offsetof(Color, b) == (sizeof(float) * 2) && - offsetof(Color, a) == (sizeof(float) * 3)), - - MATCHES_Plane = (MATCHES_Vector3 && MATCHES_real_t && (sizeof(Plane) == (sizeof(Vector3) + sizeof(real_t))) && - offsetof(Plane, normal) == 0 && - offsetof(Plane, d) == sizeof(Vector3)) -}; - -// In the future we may force this if we want to ref return these structs -#ifdef GD_MONO_FORCE_INTEROP_STRUCT_COPY -/* clang-format off */ -static_assert(MATCHES_Vector2 && MATCHES_Rect2 && MATCHES_Transform2D && MATCHES_Vector3 && MATCHES_Vector4 && - MATCHES_Basis && MATCHES_Quaternion && MATCHES_Transform3D && MATCHES_Projection && MATCHES_AABB && MATCHES_Color && - MATCHES_Plane && MATCHES_Vector2i && MATCHES_Rect2i && MATCHES_Vector3i && MATCHES_Vector4i); -/* clang-format on */ -#endif -} // namespace InteropLayout - -#pragma pack(push, 1) - -struct M_Vector2 { - real_t x, y; - - static _FORCE_INLINE_ Vector2 convert_to(const M_Vector2 &p_from) { - return Vector2(p_from.x, p_from.y); - } - - static _FORCE_INLINE_ M_Vector2 convert_from(const Vector2 &p_from) { - M_Vector2 ret = { p_from.x, p_from.y }; - return ret; - } -}; - -struct M_Vector2i { - int32_t x, y; - - static _FORCE_INLINE_ Vector2i convert_to(const M_Vector2i &p_from) { - return Vector2i(p_from.x, p_from.y); - } - - static _FORCE_INLINE_ M_Vector2i convert_from(const Vector2i &p_from) { - M_Vector2i ret = { p_from.x, p_from.y }; - return ret; - } -}; - -struct M_Rect2 { - M_Vector2 position; - M_Vector2 size; - - static _FORCE_INLINE_ Rect2 convert_to(const M_Rect2 &p_from) { - return Rect2(M_Vector2::convert_to(p_from.position), - M_Vector2::convert_to(p_from.size)); - } - - static _FORCE_INLINE_ M_Rect2 convert_from(const Rect2 &p_from) { - M_Rect2 ret = { M_Vector2::convert_from(p_from.position), M_Vector2::convert_from(p_from.size) }; - return ret; - } -}; - -struct M_Rect2i { - M_Vector2i position; - M_Vector2i size; - - static _FORCE_INLINE_ Rect2i convert_to(const M_Rect2i &p_from) { - return Rect2i(M_Vector2i::convert_to(p_from.position), - M_Vector2i::convert_to(p_from.size)); - } - - static _FORCE_INLINE_ M_Rect2i convert_from(const Rect2i &p_from) { - M_Rect2i ret = { M_Vector2i::convert_from(p_from.position), M_Vector2i::convert_from(p_from.size) }; - return ret; - } -}; - -struct M_Transform2D { - M_Vector2 elements[3]; - - static _FORCE_INLINE_ Transform2D convert_to(const M_Transform2D &p_from) { - return Transform2D(p_from.elements[0].x, p_from.elements[0].y, - p_from.elements[1].x, p_from.elements[1].y, - p_from.elements[2].x, p_from.elements[2].y); - } - - static _FORCE_INLINE_ M_Transform2D convert_from(const Transform2D &p_from) { - M_Transform2D ret = { - M_Vector2::convert_from(p_from.columns[0]), - M_Vector2::convert_from(p_from.columns[1]), - M_Vector2::convert_from(p_from.columns[2]) - }; - return ret; - } -}; - -struct M_Vector3 { - real_t x, y, z; - - static _FORCE_INLINE_ Vector3 convert_to(const M_Vector3 &p_from) { - return Vector3(p_from.x, p_from.y, p_from.z); - } - - static _FORCE_INLINE_ M_Vector3 convert_from(const Vector3 &p_from) { - M_Vector3 ret = { p_from.x, p_from.y, p_from.z }; - return ret; - } -}; - -struct M_Vector3i { - int32_t x, y, z; - - static _FORCE_INLINE_ Vector3i convert_to(const M_Vector3i &p_from) { - return Vector3i(p_from.x, p_from.y, p_from.z); - } - - static _FORCE_INLINE_ M_Vector3i convert_from(const Vector3i &p_from) { - M_Vector3i ret = { p_from.x, p_from.y, p_from.z }; - return ret; - } -}; - -struct M_Vector4 { - real_t x, y, z, w; - - static _FORCE_INLINE_ Vector4 convert_to(const M_Vector4 &p_from) { - return Vector4(p_from.x, p_from.y, p_from.z, p_from.w); - } - - static _FORCE_INLINE_ M_Vector4 convert_from(const Vector4 &p_from) { - M_Vector4 ret = { p_from.x, p_from.y, p_from.z, p_from.w }; - return ret; - } -}; - -struct M_Vector4i { - int32_t x, y, z, w; - - static _FORCE_INLINE_ Vector4i convert_to(const M_Vector4i &p_from) { - return Vector4i(p_from.x, p_from.y, p_from.z, p_from.w); - } - - static _FORCE_INLINE_ M_Vector4i convert_from(const Vector4i &p_from) { - M_Vector4i ret = { p_from.x, p_from.y, p_from.z, p_from.w }; - return ret; - } -}; - -struct M_Basis { - M_Vector3 elements[3]; - - static _FORCE_INLINE_ Basis convert_to(const M_Basis &p_from) { - return Basis(M_Vector3::convert_to(p_from.elements[0]), - M_Vector3::convert_to(p_from.elements[1]), - M_Vector3::convert_to(p_from.elements[2])); - } - - static _FORCE_INLINE_ M_Basis convert_from(const Basis &p_from) { - M_Basis ret = { - M_Vector3::convert_from(p_from.rows[0]), - M_Vector3::convert_from(p_from.rows[1]), - M_Vector3::convert_from(p_from.rows[2]) - }; - return ret; - } -}; - -struct M_Quaternion { - real_t x, y, z, w; - - static _FORCE_INLINE_ Quaternion convert_to(const M_Quaternion &p_from) { - return Quaternion(p_from.x, p_from.y, p_from.z, p_from.w); - } - - static _FORCE_INLINE_ M_Quaternion convert_from(const Quaternion &p_from) { - M_Quaternion ret = { p_from.x, p_from.y, p_from.z, p_from.w }; - return ret; - } -}; - -struct M_Transform3D { - M_Basis basis; - M_Vector3 origin; - - static _FORCE_INLINE_ Transform3D convert_to(const M_Transform3D &p_from) { - return Transform3D(M_Basis::convert_to(p_from.basis), M_Vector3::convert_to(p_from.origin)); - } - - static _FORCE_INLINE_ M_Transform3D convert_from(const Transform3D &p_from) { - M_Transform3D ret = { M_Basis::convert_from(p_from.basis), M_Vector3::convert_from(p_from.origin) }; - return ret; - } -}; - -struct M_Projection { - M_Vector4 vec1; - M_Vector4 vec2; - M_Vector4 vec3; - M_Vector4 vec4; - - static _FORCE_INLINE_ Projection convert_to(const M_Projection &p_from) { - return Projection(M_Vector4::convert_to(p_from.vec1), M_Vector4::convert_to(p_from.vec2), M_Vector4::convert_to(p_from.vec3), M_Vector4::convert_to(p_from.vec4)); - } - - static _FORCE_INLINE_ M_Projection convert_from(const Projection &p_from) { - M_Projection ret = { M_Vector4::convert_from(p_from.matrix[0]), M_Vector4::convert_from(p_from.matrix[1]), M_Vector4::convert_from(p_from.matrix[2]), M_Vector4::convert_from(p_from.matrix[3]) }; - return ret; - } -}; - -struct M_AABB { - M_Vector3 position; - M_Vector3 size; - - static _FORCE_INLINE_ AABB convert_to(const M_AABB &p_from) { - return AABB(M_Vector3::convert_to(p_from.position), M_Vector3::convert_to(p_from.size)); - } - - static _FORCE_INLINE_ M_AABB convert_from(const AABB &p_from) { - M_AABB ret = { M_Vector3::convert_from(p_from.position), M_Vector3::convert_from(p_from.size) }; - return ret; - } -}; - -struct M_Color { - float r, g, b, a; - - static _FORCE_INLINE_ Color convert_to(const M_Color &p_from) { - return Color(p_from.r, p_from.g, p_from.b, p_from.a); - } - - static _FORCE_INLINE_ M_Color convert_from(const Color &p_from) { - M_Color ret = { p_from.r, p_from.g, p_from.b, p_from.a }; - return ret; - } -}; - -struct M_Plane { - M_Vector3 normal; - real_t d; - - static _FORCE_INLINE_ Plane convert_to(const M_Plane &p_from) { - return Plane(M_Vector3::convert_to(p_from.normal), p_from.d); - } - - static _FORCE_INLINE_ M_Plane convert_from(const Plane &p_from) { - M_Plane ret = { M_Vector3::convert_from(p_from.normal), p_from.d }; - return ret; - } -}; - -#pragma pack(pop) - -#define DECL_TYPE_MARSHAL_TEMPLATES(m_type) \ - template <int> \ - _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl(const M_##m_type *p_from); \ - \ - template <> \ - _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl<0>(const M_##m_type *p_from) { \ - return M_##m_type::convert_to(*p_from); \ - } \ - \ - template <> \ - _FORCE_INLINE_ m_type marshalled_in_##m_type##_impl<1>(const M_##m_type *p_from) { \ - return *reinterpret_cast<const m_type *>(p_from); \ - } \ - \ - _FORCE_INLINE_ m_type marshalled_in_##m_type(const M_##m_type *p_from) { \ - return marshalled_in_##m_type##_impl<InteropLayout::MATCHES_##m_type>(p_from); \ - } \ - \ - template <int> \ - _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl(const m_type &p_from); \ - \ - template <> \ - _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl<0>(const m_type &p_from) { \ - return M_##m_type::convert_from(p_from); \ - } \ - \ - template <> \ - _FORCE_INLINE_ M_##m_type marshalled_out_##m_type##_impl<1>(const m_type &p_from) { \ - return *reinterpret_cast<const M_##m_type *>(&p_from); \ - } \ - \ - _FORCE_INLINE_ M_##m_type marshalled_out_##m_type(const m_type &p_from) { \ - return marshalled_out_##m_type##_impl<InteropLayout::MATCHES_##m_type>(p_from); \ - } - -DECL_TYPE_MARSHAL_TEMPLATES(Vector2) -DECL_TYPE_MARSHAL_TEMPLATES(Vector2i) -DECL_TYPE_MARSHAL_TEMPLATES(Rect2) -DECL_TYPE_MARSHAL_TEMPLATES(Rect2i) -DECL_TYPE_MARSHAL_TEMPLATES(Transform2D) -DECL_TYPE_MARSHAL_TEMPLATES(Vector3) -DECL_TYPE_MARSHAL_TEMPLATES(Vector3i) -DECL_TYPE_MARSHAL_TEMPLATES(Basis) -DECL_TYPE_MARSHAL_TEMPLATES(Vector4) -DECL_TYPE_MARSHAL_TEMPLATES(Vector4i) -DECL_TYPE_MARSHAL_TEMPLATES(Quaternion) -DECL_TYPE_MARSHAL_TEMPLATES(Transform3D) -DECL_TYPE_MARSHAL_TEMPLATES(Projection) -DECL_TYPE_MARSHAL_TEMPLATES(AABB) -DECL_TYPE_MARSHAL_TEMPLATES(Color) -DECL_TYPE_MARSHAL_TEMPLATES(Plane) - -#define MARSHALLED_IN(m_type, m_from_ptr) (GDMonoMarshal::marshalled_in_##m_type(m_from_ptr)) -#define MARSHALLED_OUT(m_type, m_from) (GDMonoMarshal::marshalled_out_##m_type(m_from)) -} // namespace GDMonoMarshal - -#endif // GD_MONO_MARSHAL_H diff --git a/modules/mono/mono_gd/gd_mono_method.cpp b/modules/mono/mono_gd/gd_mono_method.cpp deleted file mode 100644 index 6734b44783..0000000000 --- a/modules/mono/mono_gd/gd_mono_method.cpp +++ /dev/null @@ -1,296 +0,0 @@ -/*************************************************************************/ -/* gd_mono_method.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_method.h" - -#include <mono/metadata/attrdefs.h> -#include <mono/metadata/debug-helpers.h> - -#include "gd_mono_cache.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_utils.h" - -void GDMonoMethod::_update_signature() { - // Apparently MonoMethodSignature needs not to be freed. - // mono_method_signature caches the result, we don't need to cache it ourselves. - - MonoMethodSignature *method_sig = mono_method_signature(mono_method); - _update_signature(method_sig); -} - -void GDMonoMethod::_update_signature(MonoMethodSignature *p_method_sig) { - params_count = mono_signature_get_param_count(p_method_sig); - - MonoType *ret_type = mono_signature_get_return_type(p_method_sig); - if (ret_type) { - return_type.type_encoding = mono_type_get_type(ret_type); - - if (return_type.type_encoding != MONO_TYPE_VOID) { - MonoClass *ret_type_class = mono_class_from_mono_type(ret_type); - return_type.type_class = GDMono::get_singleton()->get_class(ret_type_class); - } - } - - void *iter = nullptr; - MonoType *param_raw_type; - while ((param_raw_type = mono_signature_get_params(p_method_sig, &iter)) != nullptr) { - ManagedType param_type; - - param_type.type_encoding = mono_type_get_type(param_raw_type); - - MonoClass *param_type_class = mono_class_from_mono_type(param_raw_type); - param_type.type_class = GDMono::get_singleton()->get_class(param_type_class); - - param_types.push_back(param_type); - } - - // clear the cache - method_info_fetched = false; - method_info = MethodInfo(); - - for (int i = 0; i < params_count; i++) { - params_buffer_size += GDMonoMarshal::variant_get_managed_unboxed_size(param_types[i]); - } -} - -GDMonoClass *GDMonoMethod::get_enclosing_class() const { - return GDMono::get_singleton()->get_class(mono_method_get_class(mono_method)); -} - -bool GDMonoMethod::is_static() { - return mono_method_get_flags(mono_method, nullptr) & MONO_METHOD_ATTR_STATIC; -} - -IMonoClassMember::Visibility GDMonoMethod::get_visibility() { - switch (mono_method_get_flags(mono_method, nullptr) & MONO_METHOD_ATTR_ACCESS_MASK) { - case MONO_METHOD_ATTR_PRIVATE: - return IMonoClassMember::PRIVATE; - case MONO_METHOD_ATTR_FAM_AND_ASSEM: - return IMonoClassMember::PROTECTED_AND_INTERNAL; - case MONO_METHOD_ATTR_ASSEM: - return IMonoClassMember::INTERNAL; - case MONO_METHOD_ATTR_FAMILY: - return IMonoClassMember::PROTECTED; - case MONO_METHOD_ATTR_PUBLIC: - return IMonoClassMember::PUBLIC; - default: - ERR_FAIL_V(IMonoClassMember::PRIVATE); - } -} - -MonoObject *GDMonoMethod::invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc) const { - MonoException *exc = nullptr; - MonoObject *ret; - - if (params_count > 0) { - void **params = (void **)alloca(params_count * sizeof(void *)); - uint8_t *buffer = (uint8_t *)alloca(params_buffer_size); - unsigned int offset = 0; - - for (int i = 0; i < params_count; i++) { - params[i] = GDMonoMarshal::variant_to_managed_unboxed(p_params[i], param_types[i], buffer + offset, offset); - } - - ret = GDMonoUtils::runtime_invoke(mono_method, p_object, params, &exc); - } else { - ret = GDMonoUtils::runtime_invoke(mono_method, p_object, nullptr, &exc); - } - - if (exc) { - ret = nullptr; - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } - - return ret; -} - -MonoObject *GDMonoMethod::invoke(MonoObject *p_object, MonoException **r_exc) const { - ERR_FAIL_COND_V(get_parameters_count() > 0, nullptr); - return invoke_raw(p_object, nullptr, r_exc); -} - -MonoObject *GDMonoMethod::invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc) const { - MonoException *exc = nullptr; - MonoObject *ret = GDMonoUtils::runtime_invoke(mono_method, p_object, p_params, &exc); - - if (exc) { - ret = nullptr; - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } - - return ret; -} - -bool GDMonoMethod::has_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, false); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoMethod::get_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, nullptr); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoMethod::fetch_attributes() { - ERR_FAIL_COND(attributes != nullptr); - attributes = mono_custom_attrs_from_method(mono_method); - attrs_fetched = true; -} - -String GDMonoMethod::get_full_name(bool p_signature) const { - char *res = mono_method_full_name(mono_method, p_signature); - String full_name(res); - mono_free(res); - return full_name; -} - -String GDMonoMethod::get_full_name_no_class() const { - String res; - - MonoMethodSignature *method_sig = mono_method_signature(mono_method); - - char *ret_str = mono_type_full_name(mono_signature_get_return_type(method_sig)); - res += ret_str; - mono_free(ret_str); - - res += " "; - res += name; - res += "("; - - char *sig_desc = mono_signature_get_desc(method_sig, true); - res += sig_desc; - mono_free(sig_desc); - - res += ")"; - - return res; -} - -String GDMonoMethod::get_ret_type_full_name() const { - MonoMethodSignature *method_sig = mono_method_signature(mono_method); - char *ret_str = mono_type_full_name(mono_signature_get_return_type(method_sig)); - String res = ret_str; - mono_free(ret_str); - return res; -} - -String GDMonoMethod::get_signature_desc(bool p_namespaces) const { - MonoMethodSignature *method_sig = mono_method_signature(mono_method); - char *sig_desc = mono_signature_get_desc(method_sig, p_namespaces); - String res = sig_desc; - mono_free(sig_desc); - return res; -} - -void GDMonoMethod::get_parameter_names(Vector<StringName> &names) const { - if (params_count > 0) { - const char **_names = memnew_arr(const char *, params_count); - mono_method_get_param_names(mono_method, _names); - for (int i = 0; i < params_count; ++i) { - names.push_back(StringName(_names[i])); - } - memdelete_arr(_names); - } -} - -void GDMonoMethod::get_parameter_types(Vector<ManagedType> &types) const { - for (int i = 0; i < params_count; ++i) { - types.push_back(param_types[i]); - } -} - -const MethodInfo &GDMonoMethod::get_method_info() { - if (!method_info_fetched) { - method_info.name = name; - - bool nil_is_variant = false; - method_info.return_val = PropertyInfo(GDMonoMarshal::managed_to_variant_type(return_type, &nil_is_variant), ""); - if (method_info.return_val.type == Variant::NIL && nil_is_variant) { - method_info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } - - Vector<StringName> names; - get_parameter_names(names); - - for (int i = 0; i < params_count; ++i) { - nil_is_variant = false; - PropertyInfo arg_info = PropertyInfo(GDMonoMarshal::managed_to_variant_type(param_types[i], &nil_is_variant), names[i]); - if (arg_info.type == Variant::NIL && nil_is_variant) { - arg_info.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; - } - - method_info.arguments.push_back(arg_info); - } - - // TODO: default arguments - - method_info_fetched = true; - } - - return method_info; -} - -GDMonoMethod::GDMonoMethod(StringName p_name, MonoMethod *p_method) : - name(p_name), mono_method(p_method) { - _update_signature(); -} - -GDMonoMethod::~GDMonoMethod() { - if (attributes) { - mono_custom_attrs_free(attributes); - } -} diff --git a/modules/mono/mono_gd/gd_mono_method.h b/modules/mono/mono_gd/gd_mono_method.h deleted file mode 100644 index be11ef5bfe..0000000000 --- a/modules/mono/mono_gd/gd_mono_method.h +++ /dev/null @@ -1,97 +0,0 @@ -/*************************************************************************/ -/* gd_mono_method.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_METHOD_H -#define GD_MONO_METHOD_H - -#include "gd_mono.h" -#include "gd_mono_header.h" -#include "i_mono_class_member.h" - -class GDMonoMethod : public IMonoClassMember { - StringName name; - - uint16_t params_count; - unsigned int params_buffer_size = 0; - ManagedType return_type; - Vector<ManagedType> param_types; - - bool method_info_fetched = false; - MethodInfo method_info; - - bool attrs_fetched = false; - MonoCustomAttrInfo *attributes = nullptr; - - void _update_signature(); - void _update_signature(MonoMethodSignature *p_method_sig); - - friend class GDMonoClass; - - MonoMethod *mono_method = nullptr; - -public: - virtual GDMonoClass *get_enclosing_class() const final; - - virtual MemberType get_member_type() const final { return MEMBER_TYPE_METHOD; } - - virtual StringName get_name() const final { return name; } - - virtual bool is_static() final; - - virtual Visibility get_visibility() final; - - virtual bool has_attribute(GDMonoClass *p_attr_class) final; - virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) final; - void fetch_attributes(); - - _FORCE_INLINE_ MonoMethod *get_mono_ptr() const { return mono_method; } - - _FORCE_INLINE_ uint16_t get_parameters_count() const { return params_count; } - _FORCE_INLINE_ ManagedType get_return_type() const { return return_type; } - - MonoObject *invoke(MonoObject *p_object, const Variant **p_params, MonoException **r_exc = nullptr) const; - MonoObject *invoke(MonoObject *p_object, MonoException **r_exc = nullptr) const; - MonoObject *invoke_raw(MonoObject *p_object, void **p_params, MonoException **r_exc = nullptr) const; - - String get_full_name(bool p_signature = false) const; - String get_full_name_no_class() const; - String get_ret_type_full_name() const; - String get_signature_desc(bool p_namespaces = false) const; - - void get_parameter_names(Vector<StringName> &names) const; - void get_parameter_types(Vector<ManagedType> &types) const; - - const MethodInfo &get_method_info(); - - GDMonoMethod(StringName p_name, MonoMethod *p_method); - ~GDMonoMethod(); -}; - -#endif // GD_MONO_METHOD_H diff --git a/modules/mono/mono_gd/gd_mono_method_thunk.h b/modules/mono/mono_gd/gd_mono_method_thunk.h deleted file mode 100644 index 0180dee3ea..0000000000 --- a/modules/mono/mono_gd/gd_mono_method_thunk.h +++ /dev/null @@ -1,320 +0,0 @@ -/*************************************************************************/ -/* gd_mono_method_thunk.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_METHOD_THUNK_H -#define GD_MONO_METHOD_THUNK_H - -#include <type_traits> - -#include "gd_mono_class.h" -#include "gd_mono_header.h" -#include "gd_mono_marshal.h" -#include "gd_mono_method.h" -#include "gd_mono_utils.h" - -#if !defined(JAVASCRIPT_ENABLED) && !defined(IOS_ENABLED) -#define HAVE_METHOD_THUNKS -#endif - -#ifdef HAVE_METHOD_THUNKS - -template <class... ParamTypes> -struct GDMonoMethodThunk { - typedef void(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **); - - M mono_method_thunk = nullptr; - -public: - _FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - mono_method_thunk(p_args..., r_exc); - GD_MONO_END_RUNTIME_INVOKE; - } - - _FORCE_INLINE_ bool is_null() { - return mono_method_thunk == nullptr; - } - - _FORCE_INLINE_ void nullify() { - mono_method_thunk = nullptr; - } - - _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == nullptr); - CRASH_COND(p_mono_method->get_return_type().type_encoding != MONO_TYPE_VOID); - - if (p_mono_method->is_static()) { - CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); - } else { - CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); - } -#endif - mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); - } - - GDMonoMethodThunk() {} - - explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { - set_from_method(p_mono_method); - } -}; - -template <class R, class... ParamTypes> -struct GDMonoMethodThunkR { - typedef R(GD_MONO_STDCALL *M)(ParamTypes... p_args, MonoException **); - - M mono_method_thunk = nullptr; - -public: - _FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - R r = mono_method_thunk(p_args..., r_exc); - GD_MONO_END_RUNTIME_INVOKE; - return r; - } - - _FORCE_INLINE_ bool is_null() { - return mono_method_thunk == nullptr; - } - - _FORCE_INLINE_ void nullify() { - mono_method_thunk = nullptr; - } - - _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == nullptr); - CRASH_COND(p_mono_method->get_return_type().type_encoding == MONO_TYPE_VOID); - - if (p_mono_method->is_static()) { - CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); - } else { - CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); - } -#endif - mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); - } - - GDMonoMethodThunkR() {} - - explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == nullptr); -#endif - mono_method_thunk = (M)mono_method_get_unmanaged_thunk(p_mono_method->get_mono_ptr()); - } -}; - -#else - -template <unsigned int ThunkParamCount, class P1, class... ParamTypes> -struct VariadicInvokeMonoMethodImpl { - static void invoke(GDMonoMethod *p_mono_method, P1 p_arg1, ParamTypes... p_args, MonoException **r_exc) { - if (p_mono_method->is_static()) { - void *args[ThunkParamCount] = { p_arg1, p_args... }; - p_mono_method->invoke_raw(nullptr, args, r_exc); - } else { - void *args[ThunkParamCount] = { p_args... }; - p_mono_method->invoke_raw((MonoObject *)p_arg1, args, r_exc); - } - } -}; - -template <unsigned int ThunkParamCount, class... ParamTypes> -struct VariadicInvokeMonoMethod { - static void invoke(GDMonoMethod *p_mono_method, ParamTypes... p_args, MonoException **r_exc) { - VariadicInvokeMonoMethodImpl<ThunkParamCount, ParamTypes...>::invoke(p_mono_method, p_args..., r_exc); - } -}; - -template <> -struct VariadicInvokeMonoMethod<0> { - static void invoke(GDMonoMethod *p_mono_method, MonoException **r_exc) { -#ifdef DEBUG_ENABLED - CRASH_COND(!p_mono_method->is_static()); -#endif - p_mono_method->invoke_raw(nullptr, nullptr, r_exc); - } -}; - -template <class P1> -struct VariadicInvokeMonoMethod<1, P1> { - static void invoke(GDMonoMethod *p_mono_method, P1 p_arg1, MonoException **r_exc) { - if (p_mono_method->is_static()) { - void *args[1] = { p_arg1 }; - p_mono_method->invoke_raw(nullptr, args, r_exc); - } else { - p_mono_method->invoke_raw((MonoObject *)p_arg1, nullptr, r_exc); - } - } -}; - -template <class R> -R unbox_if_needed(MonoObject *p_val, const ManagedType &, typename std::enable_if<!std::is_pointer<R>::value>::type * = 0) { - return GDMonoMarshal::unbox<R>(p_val); -} - -template <class R> -R unbox_if_needed(MonoObject *p_val, const ManagedType &p_type, typename std::enable_if<std::is_pointer<R>::value>::type * = 0) { - if (mono_class_is_valuetype(p_type.type_class->get_mono_ptr())) { - return GDMonoMarshal::unbox<R>(p_val); - } else { - // If it's not a value type, we assume 'R' is a pointer to 'MonoObject' or a compatible type, like 'MonoException'. - return (R)p_val; - } -} - -template <unsigned int ThunkParamCount, class R, class P1, class... ParamTypes> -struct VariadicInvokeMonoMethodRImpl { - static R invoke(GDMonoMethod *p_mono_method, P1 p_arg1, ParamTypes... p_args, MonoException **r_exc) { - if (p_mono_method->is_static()) { - void *args[ThunkParamCount] = { p_arg1, p_args... }; - MonoObject *r = p_mono_method->invoke_raw(nullptr, args, r_exc); - return unbox_if_needed<R>(r, p_mono_method->get_return_type()); - } else { - void *args[ThunkParamCount] = { p_args... }; - MonoObject *r = p_mono_method->invoke_raw((MonoObject *)p_arg1, args, r_exc); - return unbox_if_needed<R>(r, p_mono_method->get_return_type()); - } - } -}; - -template <unsigned int ThunkParamCount, class R, class... ParamTypes> -struct VariadicInvokeMonoMethodR { - static R invoke(GDMonoMethod *p_mono_method, ParamTypes... p_args, MonoException **r_exc) { - return VariadicInvokeMonoMethodRImpl<ThunkParamCount, R, ParamTypes...>::invoke(p_mono_method, p_args..., r_exc); - } -}; - -template <class R> -struct VariadicInvokeMonoMethodR<0, R> { - static R invoke(GDMonoMethod *p_mono_method, MonoException **r_exc) { -#ifdef DEBUG_ENABLED - CRASH_COND(!p_mono_method->is_static()); -#endif - MonoObject *r = p_mono_method->invoke_raw(nullptr, nullptr, r_exc); - return unbox_if_needed<R>(r, p_mono_method->get_return_type()); - } -}; - -template <class R, class P1> -struct VariadicInvokeMonoMethodR<1, R, P1> { - static R invoke(GDMonoMethod *p_mono_method, P1 p_arg1, MonoException **r_exc) { - if (p_mono_method->is_static()) { - void *args[1] = { p_arg1 }; - MonoObject *r = p_mono_method->invoke_raw(nullptr, args, r_exc); - return unbox_if_needed<R>(r, p_mono_method->get_return_type()); - } else { - MonoObject *r = p_mono_method->invoke_raw((MonoObject *)p_arg1, nullptr, r_exc); - return unbox_if_needed<R>(r, p_mono_method->get_return_type()); - } - } -}; - -template <class... ParamTypes> -struct GDMonoMethodThunk { - GDMonoMethod *mono_method = nullptr; - -public: - _FORCE_INLINE_ void invoke(ParamTypes... p_args, MonoException **r_exc) { - VariadicInvokeMonoMethod<sizeof...(ParamTypes), ParamTypes...>::invoke(mono_method, p_args..., r_exc); - } - - _FORCE_INLINE_ bool is_null() { - return mono_method == nullptr; - } - - _FORCE_INLINE_ void nullify() { - mono_method = nullptr; - } - - _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == nullptr); - CRASH_COND(p_mono_method->get_return_type().type_encoding != MONO_TYPE_VOID); - - if (p_mono_method->is_static()) { - CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); - } else { - CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); - } -#endif - mono_method = p_mono_method; - } - - GDMonoMethodThunk() {} - - explicit GDMonoMethodThunk(GDMonoMethod *p_mono_method) { - set_from_method(p_mono_method); - } -}; - -template <class R, class... ParamTypes> -struct GDMonoMethodThunkR { - GDMonoMethod *mono_method = nullptr; - -public: - _FORCE_INLINE_ R invoke(ParamTypes... p_args, MonoException **r_exc) { - return VariadicInvokeMonoMethodR<sizeof...(ParamTypes), R, ParamTypes...>::invoke(mono_method, p_args..., r_exc); - } - - _FORCE_INLINE_ bool is_null() { - return mono_method == nullptr; - } - - _FORCE_INLINE_ void nullify() { - mono_method = nullptr; - } - - _FORCE_INLINE_ void set_from_method(GDMonoMethod *p_mono_method) { -#ifdef DEBUG_ENABLED - CRASH_COND(p_mono_method == nullptr); - CRASH_COND(p_mono_method->get_return_type().type_encoding == MONO_TYPE_VOID); - - if (p_mono_method->is_static()) { - CRASH_COND(p_mono_method->get_parameters_count() != sizeof...(ParamTypes)); - } else { - CRASH_COND(p_mono_method->get_parameters_count() != (sizeof...(ParamTypes) - 1)); - } -#endif - mono_method = p_mono_method; - } - - GDMonoMethodThunkR() {} - - explicit GDMonoMethodThunkR(GDMonoMethod *p_mono_method) { - set_from_method(p_mono_method); - } -}; - -#endif - -#endif // GD_MONO_METHOD_THUNK_H diff --git a/modules/mono/mono_gd/gd_mono_property.cpp b/modules/mono/mono_gd/gd_mono_property.cpp deleted file mode 100644 index c9775ae9cb..0000000000 --- a/modules/mono/mono_gd/gd_mono_property.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/*************************************************************************/ -/* gd_mono_property.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_property.h" - -#include "gd_mono_cache.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" -#include "gd_mono_utils.h" - -#include <mono/metadata/attrdefs.h> - -GDMonoProperty::GDMonoProperty(MonoProperty *p_mono_property, GDMonoClass *p_owner) { - owner = p_owner; - mono_property = p_mono_property; - name = String::utf8(mono_property_get_name(mono_property)); - - MonoMethod *prop_method = mono_property_get_get_method(mono_property); - - if (prop_method) { - MonoMethodSignature *getter_sig = mono_method_signature(prop_method); - - MonoType *ret_type = mono_signature_get_return_type(getter_sig); - - type.type_encoding = mono_type_get_type(ret_type); - MonoClass *ret_type_class = mono_class_from_mono_type(ret_type); - type.type_class = GDMono::get_singleton()->get_class(ret_type_class); - } else { - prop_method = mono_property_get_set_method(mono_property); - - MonoMethodSignature *setter_sig = mono_method_signature(prop_method); - - void *iter = nullptr; - MonoType *param_raw_type = mono_signature_get_params(setter_sig, &iter); - - type.type_encoding = mono_type_get_type(param_raw_type); - MonoClass *param_type_class = mono_class_from_mono_type(param_raw_type); - type.type_class = GDMono::get_singleton()->get_class(param_type_class); - } - - param_buffer_size = GDMonoMarshal::variant_get_managed_unboxed_size(type); - - attrs_fetched = false; - attributes = nullptr; -} - -GDMonoProperty::~GDMonoProperty() { - if (attributes) { - mono_custom_attrs_free(attributes); - } -} - -bool GDMonoProperty::is_static() { - MonoMethod *prop_method = mono_property_get_get_method(mono_property); - if (prop_method == nullptr) { - prop_method = mono_property_get_set_method(mono_property); - } - return mono_method_get_flags(prop_method, nullptr) & MONO_METHOD_ATTR_STATIC; -} - -IMonoClassMember::Visibility GDMonoProperty::get_visibility() { - MonoMethod *prop_method = mono_property_get_get_method(mono_property); - if (prop_method == nullptr) { - prop_method = mono_property_get_set_method(mono_property); - } - - switch (mono_method_get_flags(prop_method, nullptr) & MONO_METHOD_ATTR_ACCESS_MASK) { - case MONO_METHOD_ATTR_PRIVATE: - return IMonoClassMember::PRIVATE; - case MONO_METHOD_ATTR_FAM_AND_ASSEM: - return IMonoClassMember::PROTECTED_AND_INTERNAL; - case MONO_METHOD_ATTR_ASSEM: - return IMonoClassMember::INTERNAL; - case MONO_METHOD_ATTR_FAMILY: - return IMonoClassMember::PROTECTED; - case MONO_METHOD_ATTR_PUBLIC: - return IMonoClassMember::PUBLIC; - default: - ERR_FAIL_V(IMonoClassMember::PRIVATE); - } -} - -bool GDMonoProperty::has_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, false); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return false; - } - - return mono_custom_attrs_has_attr(attributes, p_attr_class->get_mono_ptr()); -} - -MonoObject *GDMonoProperty::get_attribute(GDMonoClass *p_attr_class) { - ERR_FAIL_NULL_V(p_attr_class, nullptr); - - if (!attrs_fetched) { - fetch_attributes(); - } - - if (!attributes) { - return nullptr; - } - - return mono_custom_attrs_get_attr(attributes, p_attr_class->get_mono_ptr()); -} - -void GDMonoProperty::fetch_attributes() { - ERR_FAIL_COND(attributes != nullptr); - attributes = mono_custom_attrs_from_property(owner->get_mono_ptr(), mono_property); - attrs_fetched = true; -} - -bool GDMonoProperty::has_getter() { - return mono_property_get_get_method(mono_property) != nullptr; -} - -bool GDMonoProperty::has_setter() { - return mono_property_get_set_method(mono_property) != nullptr; -} - -void GDMonoProperty::set_value_from_variant(MonoObject *p_object, const Variant &p_value, MonoException **r_exc) { - uint8_t *buffer = (uint8_t *)alloca(param_buffer_size); - unsigned int offset = 0; - - void *params[1] = { - GDMonoMarshal::variant_to_managed_unboxed(p_value, type, buffer, offset) - }; - -#ifdef DEBUG_ENABLED - CRASH_COND(offset != param_buffer_size); -#endif - - MonoException *exc = nullptr; - GDMonoUtils::property_set_value(mono_property, p_object, params, &exc); - if (exc) { - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } -} - -MonoObject *GDMonoProperty::get_value(MonoObject *p_object, MonoException **r_exc) { - MonoException *exc = nullptr; - MonoObject *ret = GDMonoUtils::property_get_value(mono_property, p_object, nullptr, &exc); - - if (exc) { - ret = nullptr; - if (r_exc) { - *r_exc = exc; - } else { - GDMonoUtils::set_pending_exception(exc); - } - } - - return ret; -} - -bool GDMonoProperty::get_bool_value(MonoObject *p_object) { - return (bool)GDMonoMarshal::unbox<MonoBoolean>(get_value(p_object)); -} - -int GDMonoProperty::get_int_value(MonoObject *p_object) { - return GDMonoMarshal::unbox<int32_t>(get_value(p_object)); -} - -String GDMonoProperty::get_string_value(MonoObject *p_object) { - MonoObject *val = get_value(p_object); - return GDMonoMarshal::mono_string_to_godot((MonoString *)val); -} diff --git a/modules/mono/mono_gd/gd_mono_property.h b/modules/mono/mono_gd/gd_mono_property.h deleted file mode 100644 index 6fc681aeb5..0000000000 --- a/modules/mono/mono_gd/gd_mono_property.h +++ /dev/null @@ -1,80 +0,0 @@ -/*************************************************************************/ -/* gd_mono_property.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_PROPERTY_H -#define GD_MONO_PROPERTY_H - -#include "gd_mono.h" -#include "gd_mono_header.h" -#include "i_mono_class_member.h" - -class GDMonoProperty : public IMonoClassMember { - GDMonoClass *owner = nullptr; - MonoProperty *mono_property = nullptr; - - StringName name; - ManagedType type; - - bool attrs_fetched; - MonoCustomAttrInfo *attributes = nullptr; - - unsigned int param_buffer_size; - -public: - virtual GDMonoClass *get_enclosing_class() const final { return owner; } - - virtual MemberType get_member_type() const final { return MEMBER_TYPE_PROPERTY; } - - virtual StringName get_name() const final { return name; } - - virtual bool is_static() final; - virtual Visibility get_visibility() final; - - virtual bool has_attribute(GDMonoClass *p_attr_class) final; - virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) final; - void fetch_attributes(); - - bool has_getter(); - bool has_setter(); - - _FORCE_INLINE_ ManagedType get_type() const { return type; } - - void set_value_from_variant(MonoObject *p_object, const Variant &p_value, MonoException **r_exc = nullptr); - MonoObject *get_value(MonoObject *p_object, MonoException **r_exc = nullptr); - - bool get_bool_value(MonoObject *p_object); - int get_int_value(MonoObject *p_object); - String get_string_value(MonoObject *p_object); - - GDMonoProperty(MonoProperty *p_mono_property, GDMonoClass *p_owner); - ~GDMonoProperty(); -}; - -#endif // GD_MONO_PROPERTY_H diff --git a/modules/mono/mono_gd/gd_mono_utils.cpp b/modules/mono/mono_gd/gd_mono_utils.cpp deleted file mode 100644 index 1983d6ebe2..0000000000 --- a/modules/mono/mono_gd/gd_mono_utils.cpp +++ /dev/null @@ -1,677 +0,0 @@ -/*************************************************************************/ -/* gd_mono_utils.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_utils.h" - -#include <mono/metadata/debug-helpers.h> -#include <mono/metadata/exception.h> - -#include "core/debugger/engine_debugger.h" -#include "core/debugger/script_debugger.h" -#include "core/io/dir_access.h" -#include "core/object/ref_counted.h" -#include "core/os/mutex.h" -#include "core/os/os.h" - -#ifdef TOOLS_ENABLED -#include "editor/debugger/editor_debugger_node.h" -#endif - -#include "../csharp_script.h" -#include "../utils/macros.h" -#include "gd_mono.h" -#include "gd_mono_cache.h" -#include "gd_mono_class.h" -#include "gd_mono_marshal.h" - -namespace GDMonoUtils { - -MonoObject *unmanaged_get_managed(Object *unmanaged) { - if (!unmanaged) { - return nullptr; - } - - if (unmanaged->get_script_instance()) { - CSharpInstance *cs_instance = CAST_CSHARP_INSTANCE(unmanaged->get_script_instance()); - - if (cs_instance) { - return cs_instance->get_mono_object(); - } - } - - // If the owner does not have a CSharpInstance... - - void *data = CSharpLanguage::get_instance_binding(unmanaged); - ERR_FAIL_NULL_V(data, nullptr); - CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->value(); - ERR_FAIL_COND_V(!script_binding.inited, nullptr); - - MonoGCHandleData &gchandle = script_binding.gchandle; - - MonoObject *target = gchandle.get_target(); - - if (target) { - return target; - } - - CSharpLanguage::get_singleton()->release_script_gchandle(gchandle); - - // Create a new one - -#ifdef DEBUG_ENABLED - CRASH_COND(script_binding.type_name == StringName()); - CRASH_COND(script_binding.wrapper_class == nullptr); -#endif - - MonoObject *mono_object = GDMonoUtils::create_managed_for_godot_object(script_binding.wrapper_class, script_binding.type_name, unmanaged); - ERR_FAIL_NULL_V(mono_object, nullptr); - - gchandle = MonoGCHandleData::new_strong_handle(mono_object); - - // Tie managed to unmanaged - RefCounted *rc = Object::cast_to<RefCounted>(unmanaged); - - if (rc) { - // Unsafe refcount increment. The managed instance also counts as a reference. - // This way if the unmanaged world has no references to our owner - // but the managed instance is alive, the refcount will be 1 instead of 0. - // See: godot_icall_RefCounted_Dtor(MonoObject *p_obj, Object *p_ptr) - rc->reference(); - CSharpLanguage::get_singleton()->post_unsafe_reference(rc); - } - - return mono_object; -} - -void set_main_thread(MonoThread *p_thread) { - mono_thread_set_main(p_thread); -} - -MonoThread *attach_current_thread() { - ERR_FAIL_COND_V(!GDMono::get_singleton()->is_runtime_initialized(), nullptr); - MonoDomain *scripts_domain = GDMono::get_singleton()->get_scripts_domain(); -#ifndef GD_MONO_SINGLE_APPDOMAIN - MonoThread *mono_thread = mono_thread_attach(scripts_domain ? scripts_domain : mono_get_root_domain()); -#else - // The scripts domain is the root domain - MonoThread *mono_thread = mono_thread_attach(scripts_domain); -#endif - ERR_FAIL_NULL_V(mono_thread, nullptr); - return mono_thread; -} - -void detach_current_thread() { - ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - MonoThread *mono_thread = mono_thread_current(); - ERR_FAIL_NULL(mono_thread); - mono_thread_detach(mono_thread); -} - -void detach_current_thread(MonoThread *p_mono_thread) { - ERR_FAIL_COND(!GDMono::get_singleton()->is_runtime_initialized()); - ERR_FAIL_NULL(p_mono_thread); - mono_thread_detach(p_mono_thread); -} - -MonoThread *get_current_thread() { - return mono_thread_current(); -} - -bool is_thread_attached() { - return mono_domain_get() != nullptr; -} - -uint32_t new_strong_gchandle(MonoObject *p_object) { - return mono_gchandle_new(p_object, /* pinned: */ false); -} - -uint32_t new_strong_gchandle_pinned(MonoObject *p_object) { - return mono_gchandle_new(p_object, /* pinned: */ true); -} - -uint32_t new_weak_gchandle(MonoObject *p_object) { - return mono_gchandle_new_weakref(p_object, /* track_resurrection: */ false); -} - -void free_gchandle(uint32_t p_gchandle) { - mono_gchandle_free(p_gchandle); -} - -void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc) { - GDMonoMethod *ctor = p_class->get_method(".ctor", 0); - ERR_FAIL_NULL(ctor); - ctor->invoke_raw(p_this_obj, nullptr, r_exc); -} - -bool mono_delegate_equal(MonoDelegate *p_a, MonoDelegate *p_b) { - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(Delegate, Equals).invoke((MonoObject *)p_a, (MonoObject *)p_b, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -GDMonoClass *get_object_class(MonoObject *p_object) { - return GDMono::get_singleton()->get_class(mono_object_get_class(p_object)); -} - -GDMonoClass *type_get_proxy_class(const StringName &p_type) { - String class_name = p_type; - - if (class_name[0] == '_') { - class_name = class_name.substr(1, class_name.length()); - } - - GDMonoClass *klass = GDMono::get_singleton()->get_core_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name); - - if (klass && klass->is_static()) { - // A static class means this is a Godot singleton class. If an instance is needed we use Godot.Object. - return GDMonoCache::cached_data.class_GodotObject; - } - -#ifdef TOOLS_ENABLED - if (!klass) { - return GDMono::get_singleton()->get_editor_api_assembly()->get_class(BINDINGS_NAMESPACE, class_name); - } -#endif - - return klass; -} - -GDMonoClass *get_class_native_base(GDMonoClass *p_class) { - GDMonoClass *klass = p_class; - - do { - const GDMonoAssembly *assembly = klass->get_assembly(); - - if (assembly == GDMono::get_singleton()->get_core_api_assembly()) { - return klass; - } -#ifdef TOOLS_ENABLED - if (assembly == GDMono::get_singleton()->get_editor_api_assembly()) { - return klass; - } -#endif - } while ((klass = klass->get_parent_class()) != nullptr); - - return nullptr; -} - -MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object) { - bool parent_is_object_class = ClassDB::is_parent_class(p_object->get_class_name(), p_native); - ERR_FAIL_COND_V_MSG(!parent_is_object_class, nullptr, - "Type inherits from native type '" + p_native + "', so it can't be instantiated in object of type: '" + p_object->get_class() + "'."); - - MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, nullptr); - - CACHED_FIELD(GodotObject, ptr)->set_value_raw(mono_object, p_object); - - // Construct - GDMonoUtils::runtime_object_init(mono_object, p_class); - - return mono_object; -} - -MonoObject *create_managed_from(const StringName &p_from) { - MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(StringName)); - ERR_FAIL_NULL_V(mono_object, nullptr); - - // Construct - GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(StringName)); - - CACHED_FIELD(StringName, ptr)->set_value_raw(mono_object, memnew(StringName(p_from))); - - return mono_object; -} - -MonoObject *create_managed_from(const NodePath &p_from) { - MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(NodePath)); - ERR_FAIL_NULL_V(mono_object, nullptr); - - // Construct - GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(NodePath)); - - CACHED_FIELD(NodePath, ptr)->set_value_raw(mono_object, memnew(NodePath(p_from))); - - return mono_object; -} - -MonoObject *create_managed_from(const RID &p_from) { - MonoObject *mono_object = mono_object_new(mono_domain_get(), CACHED_CLASS_RAW(RID)); - ERR_FAIL_NULL_V(mono_object, nullptr); - - // Construct - GDMonoUtils::runtime_object_init(mono_object, CACHED_CLASS(RID)); - - CACHED_FIELD(RID, ptr)->set_value_raw(mono_object, memnew(RID(p_from))); - - return mono_object; -} - -MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class) { - MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, nullptr); - - // Search constructor that takes a pointer as parameter - MonoMethod *m; - void *iter = nullptr; - while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) { - if (strcmp(mono_method_get_name(m), ".ctor") == 0) { - MonoMethodSignature *sig = mono_method_signature(m); - void *front = nullptr; - if (mono_signature_get_param_count(sig) == 1 && - mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) { - break; - } - } - } - - CRASH_COND(m == nullptr); - - Array *new_array = memnew(Array(p_from)); - void *args[1] = { &new_array }; - - MonoException *exc = nullptr; - GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); - UNHANDLED_EXCEPTION(exc); - - return mono_object; -} - -MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class) { - MonoObject *mono_object = mono_object_new(mono_domain_get(), p_class->get_mono_ptr()); - ERR_FAIL_NULL_V(mono_object, nullptr); - - // Search constructor that takes a pointer as parameter - MonoMethod *m; - void *iter = nullptr; - while ((m = mono_class_get_methods(p_class->get_mono_ptr(), &iter))) { - if (strcmp(mono_method_get_name(m), ".ctor") == 0) { - MonoMethodSignature *sig = mono_method_signature(m); - void *front = nullptr; - if (mono_signature_get_param_count(sig) == 1 && - mono_class_from_mono_type(mono_signature_get_params(sig, &front)) == CACHED_CLASS(IntPtr)->get_mono_ptr()) { - break; - } - } - } - - CRASH_COND(m == nullptr); - - Dictionary *new_dict = memnew(Dictionary(p_from)); - void *args[1] = { &new_dict }; - - MonoException *exc = nullptr; - GDMonoUtils::runtime_invoke(m, mono_object, args, &exc); - UNHANDLED_EXCEPTION(exc); - - return mono_object; -} - -MonoDomain *create_domain(const String &p_friendly_name) { - print_verbose("Mono: Creating domain '" + p_friendly_name + "'..."); - - MonoDomain *domain = mono_domain_create_appdomain((char *)p_friendly_name.utf8().get_data(), nullptr); - - if (domain) { - // Workaround to avoid this exception: - // System.Configuration.ConfigurationErrorsException: Error Initializing the configuration system. - // ---> System.ArgumentException: The 'ExeConfigFilename' argument cannot be null. - mono_domain_set_config(domain, ".", ""); - } - - return domain; -} - -String get_type_desc(MonoType *p_type) { - return mono_type_full_name(p_type); -} - -String get_type_desc(MonoReflectionType *p_reftype) { - return get_type_desc(mono_reflection_type_get_type(p_reftype)); -} - -String get_exception_name_and_message(MonoException *p_exc) { - String res; - - MonoClass *klass = mono_object_get_class((MonoObject *)p_exc); - MonoType *type = mono_class_get_type(klass); - - char *full_name = mono_type_full_name(type); - res += full_name; - mono_free(full_name); - - res += ": "; - - MonoProperty *prop = mono_class_get_property_from_name(klass, "Message"); - MonoString *msg = (MonoString *)property_get_value(prop, (MonoObject *)p_exc, nullptr, nullptr); - res += GDMonoMarshal::mono_string_to_godot(msg); - - return res; -} - -void debug_print_unhandled_exception(MonoException *p_exc) { - print_unhandled_exception(p_exc); - debug_send_unhandled_exception_error(p_exc); -} - -void debug_send_unhandled_exception_error(MonoException *p_exc) { -#ifdef DEBUG_ENABLED - if (!EngineDebugger::is_active()) { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - ERR_PRINT(GDMonoUtils::get_exception_name_and_message(p_exc)); - } -#endif - return; - } - - static thread_local bool _recursion_flag_ = false; - if (_recursion_flag_) { - return; - } - _recursion_flag_ = true; - SCOPE_EXIT { _recursion_flag_ = false; }; - - ScriptLanguage::StackInfo separator; - separator.file = String(); - separator.func = "--- " + RTR("End of inner exception stack trace") + " ---"; - separator.line = 0; - - Vector<ScriptLanguage::StackInfo> si; - String exc_msg; - - while (p_exc != nullptr) { - GDMonoClass *st_klass = CACHED_CLASS(System_Diagnostics_StackTrace); - MonoObject *stack_trace = mono_object_new(mono_domain_get(), st_klass->get_mono_ptr()); - - MonoBoolean need_file_info = true; - void *ctor_args[2] = { p_exc, &need_file_info }; - - MonoException *unexpected_exc = nullptr; - CACHED_METHOD(System_Diagnostics_StackTrace, ctor_Exception_bool)->invoke_raw(stack_trace, ctor_args, &unexpected_exc); - - if (unexpected_exc) { - GDMonoInternals::unhandled_exception(unexpected_exc); - return; - } - - Vector<ScriptLanguage::StackInfo> _si; - if (stack_trace != nullptr) { - _si = CSharpLanguage::get_singleton()->stack_trace_get_info(stack_trace); - for (int i = _si.size() - 1; i >= 0; i--) { - si.insert(0, _si[i]); - } - } - - exc_msg += (exc_msg.length() > 0 ? " ---> " : "") + GDMonoUtils::get_exception_name_and_message(p_exc); - - GDMonoClass *exc_class = GDMono::get_singleton()->get_class(mono_get_exception_class()); - GDMonoProperty *inner_exc_prop = exc_class->get_property("InnerException"); - CRASH_COND(inner_exc_prop == nullptr); - - MonoObject *inner_exc = inner_exc_prop->get_value((MonoObject *)p_exc); - if (inner_exc != nullptr) { - si.insert(0, separator); - } - - p_exc = (MonoException *)inner_exc; - } - - String file = si.size() ? si[0].file : __FILE__; - String func = si.size() ? si[0].func : FUNCTION_STR; - int line = si.size() ? si[0].line : __LINE__; - String error_msg = "Unhandled exception"; - - EngineDebugger::get_script_debugger()->send_error(func, file, line, error_msg, exc_msg, true, ERR_HANDLER_ERROR, si); -#endif -} - -void debug_unhandled_exception(MonoException *p_exc) { - GDMonoInternals::unhandled_exception(p_exc); // prints the exception as well -} - -void print_unhandled_exception(MonoException *p_exc) { - mono_print_unhandled_exception((MonoObject *)p_exc); -} - -void set_pending_exception(MonoException *p_exc) { -#ifdef NO_PENDING_EXCEPTIONS - debug_unhandled_exception(p_exc); -#else - if (get_runtime_invoke_count() == 0) { - debug_unhandled_exception(p_exc); - return; - } - - if (!mono_runtime_set_pending_exception(p_exc, false)) { - ERR_PRINT("Exception thrown from managed code, but it could not be set as pending:"); - GDMonoUtils::debug_print_unhandled_exception(p_exc); - } -#endif -} - -thread_local int current_invoke_count = 0; - -MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = mono_runtime_invoke(p_method, p_obj, p_params, (MonoObject **)r_exc); - GD_MONO_END_RUNTIME_INVOKE; - return ret; -} - -MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoString *ret = mono_object_to_string(p_obj, (MonoObject **)r_exc); - GD_MONO_END_RUNTIME_INVOKE; - return ret; -} - -void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - mono_property_set_value(p_prop, p_obj, p_params, (MonoObject **)r_exc); - GD_MONO_END_RUNTIME_INVOKE; -} - -MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc) { - GD_MONO_BEGIN_RUNTIME_INVOKE; - MonoObject *ret = mono_property_get_value(p_prop, p_obj, p_params, (MonoObject **)r_exc); - GD_MONO_END_RUNTIME_INVOKE; - return ret; -} - -uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error) { - r_error = false; - switch (mono_type_get_type(p_enum_basetype)) { - case MONO_TYPE_BOOLEAN: - return (bool)GDMonoMarshal::unbox<MonoBoolean>(p_boxed) ? 1 : 0; - case MONO_TYPE_CHAR: - return GDMonoMarshal::unbox<uint16_t>(p_boxed); - case MONO_TYPE_U1: - return GDMonoMarshal::unbox<uint8_t>(p_boxed); - case MONO_TYPE_U2: - return GDMonoMarshal::unbox<uint16_t>(p_boxed); - case MONO_TYPE_U4: - return GDMonoMarshal::unbox<uint32_t>(p_boxed); - case MONO_TYPE_U8: - return GDMonoMarshal::unbox<uint64_t>(p_boxed); - case MONO_TYPE_I1: - return GDMonoMarshal::unbox<int8_t>(p_boxed); - case MONO_TYPE_I2: - return GDMonoMarshal::unbox<int16_t>(p_boxed); - case MONO_TYPE_I4: - return GDMonoMarshal::unbox<int32_t>(p_boxed); - case MONO_TYPE_I8: - return GDMonoMarshal::unbox<int64_t>(p_boxed); - default: - r_error = true; - return 0; - } -} - -void dispose(MonoObject *p_mono_object, MonoException **r_exc) { - CACHED_METHOD_THUNK(GodotObject, Dispose).invoke(p_mono_object, r_exc); -} - -namespace Marshal { - -#ifdef MONO_GLUE_ENABLED -#ifdef TOOLS_ENABLED -#define NO_GLUE_RET(m_ret) \ - { \ - if (!GDMonoCache::cached_data.godot_api_cache_updated) \ - return m_ret; \ - } -#else -#define NO_GLUE_RET(m_ret) \ - {} -#endif -#else -#define NO_GLUE_RET(m_ret) \ - { return m_ret; } -#endif - -bool type_is_generic_array(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericArray).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_generic_dictionary(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericDictionary).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_system_generic_list(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsSystemGenericList).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_system_generic_dictionary(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsSystemGenericDictionary).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_generic_ienumerable(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericIEnumerable).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_generic_icollection(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericICollection).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_is_generic_idictionary(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeIsGenericIDictionary).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -bool type_has_flags_attribute(MonoReflectionType *p_reftype) { - NO_GLUE_RET(false); - MonoException *exc = nullptr; - MonoBoolean res = CACHED_METHOD_THUNK(MarshalUtils, TypeHasFlagsAttribute).invoke(p_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return (bool)res; -} - -void get_generic_type_definition(MonoReflectionType *p_reftype, MonoReflectionType **r_generic_reftype) { - MonoException *exc = nullptr; - CACHED_METHOD_THUNK(MarshalUtils, GetGenericTypeDefinition).invoke(p_reftype, r_generic_reftype, &exc); - UNHANDLED_EXCEPTION(exc); -} - -void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype) { - MonoException *exc = nullptr; - CACHED_METHOD_THUNK(MarshalUtils, ArrayGetElementType).invoke(p_array_reftype, r_elem_reftype, &exc); - UNHANDLED_EXCEPTION(exc); -} - -void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype) { - MonoException *exc = nullptr; - CACHED_METHOD_THUNK(MarshalUtils, DictionaryGetKeyValueTypes).invoke(p_dict_reftype, r_key_reftype, r_value_reftype, &exc); - UNHANDLED_EXCEPTION(exc); -} - -GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype) { - NO_GLUE_RET(nullptr); - MonoException *exc = nullptr; - MonoReflectionType *reftype = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericArrayType).invoke(p_elem_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype))); -} - -GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype) { - NO_GLUE_RET(nullptr); - MonoException *exc = nullptr; - MonoReflectionType *reftype = CACHED_METHOD_THUNK(MarshalUtils, MakeGenericDictionaryType).invoke(p_key_reftype, p_value_reftype, &exc); - UNHANDLED_EXCEPTION(exc); - return GDMono::get_singleton()->get_class(mono_class_from_mono_type(mono_reflection_type_get_type(reftype))); -} -} // namespace Marshal - -ScopeThreadAttach::ScopeThreadAttach() { - if (likely(GDMono::get_singleton()->is_runtime_initialized()) && unlikely(!mono_domain_get())) { - mono_thread = GDMonoUtils::attach_current_thread(); - } -} - -ScopeThreadAttach::~ScopeThreadAttach() { - if (unlikely(mono_thread)) { - GDMonoUtils::detach_current_thread(mono_thread); - } -} - -StringName get_native_godot_class_name(GDMonoClass *p_class) { - MonoObject *native_name_obj = p_class->get_field(BINDINGS_NATIVE_NAME_FIELD)->get_value(nullptr); - StringName *ptr = GDMonoMarshal::unbox<StringName *>(CACHED_FIELD(StringName, ptr)->get_value(native_name_obj)); - return ptr ? *ptr : StringName(); -} -} // namespace GDMonoUtils diff --git a/modules/mono/mono_gd/gd_mono_utils.h b/modules/mono/mono_gd/gd_mono_utils.h deleted file mode 100644 index 300cacfa4b..0000000000 --- a/modules/mono/mono_gd/gd_mono_utils.h +++ /dev/null @@ -1,205 +0,0 @@ -/*************************************************************************/ -/* gd_mono_utils.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_UTILS_H -#define GD_MONO_UTILS_H - -#include <mono/metadata/threads.h> - -#include "../mono_gc_handle.h" -#include "../utils/macros.h" -#include "gd_mono_header.h" -#ifdef JAVASCRIPT_ENABLED -#include "gd_mono_wasm_m2n.h" -#endif - -#include "core/object/class_db.h" -#include "core/object/ref_counted.h" - -#define UNHANDLED_EXCEPTION(m_exc) \ - if (unlikely(m_exc != nullptr)) { \ - GDMonoUtils::debug_unhandled_exception(m_exc); \ - GD_UNREACHABLE(); \ - } else \ - ((void)0) - -namespace GDMonoUtils { - -namespace Marshal { - -bool type_is_generic_array(MonoReflectionType *p_reftype); -bool type_is_generic_dictionary(MonoReflectionType *p_reftype); -bool type_is_system_generic_list(MonoReflectionType *p_reftype); -bool type_is_system_generic_dictionary(MonoReflectionType *p_reftype); -bool type_is_generic_ienumerable(MonoReflectionType *p_reftype); -bool type_is_generic_icollection(MonoReflectionType *p_reftype); -bool type_is_generic_idictionary(MonoReflectionType *p_reftype); -bool type_has_flags_attribute(MonoReflectionType *p_reftype); - -void get_generic_type_definition(MonoReflectionType *p_reftype, MonoReflectionType **r_generic_reftype); - -void array_get_element_type(MonoReflectionType *p_array_reftype, MonoReflectionType **r_elem_reftype); -void dictionary_get_key_value_types(MonoReflectionType *p_dict_reftype, MonoReflectionType **r_key_reftype, MonoReflectionType **r_value_reftype); - -GDMonoClass *make_generic_array_type(MonoReflectionType *p_elem_reftype); -GDMonoClass *make_generic_dictionary_type(MonoReflectionType *p_key_reftype, MonoReflectionType *p_value_reftype); -} // namespace Marshal - -_FORCE_INLINE_ void hash_combine(uint32_t &p_hash, const uint32_t &p_with_hash) { - p_hash ^= p_with_hash + 0x9e3779b9 + (p_hash << 6) + (p_hash >> 2); -} - -/** - * If the object has a csharp script, returns the target of the gchandle stored in the script instance - * Otherwise returns a newly constructed MonoObject* which is attached to the object - * Returns nullptr on error - */ -MonoObject *unmanaged_get_managed(Object *unmanaged); - -void set_main_thread(MonoThread *p_thread); -MonoThread *attach_current_thread(); -void detach_current_thread(); -void detach_current_thread(MonoThread *p_mono_thread); -MonoThread *get_current_thread(); -bool is_thread_attached(); - -uint32_t new_strong_gchandle(MonoObject *p_object); -uint32_t new_strong_gchandle_pinned(MonoObject *p_object); -uint32_t new_weak_gchandle(MonoObject *p_object); -void free_gchandle(uint32_t p_gchandle); - -void runtime_object_init(MonoObject *p_this_obj, GDMonoClass *p_class, MonoException **r_exc = nullptr); - -bool mono_delegate_equal(MonoDelegate *p_a, MonoDelegate *p_b); - -GDMonoClass *get_object_class(MonoObject *p_object); -GDMonoClass *type_get_proxy_class(const StringName &p_type); -GDMonoClass *get_class_native_base(GDMonoClass *p_class); - -MonoObject *create_managed_for_godot_object(GDMonoClass *p_class, const StringName &p_native, Object *p_object); - -MonoObject *create_managed_from(const StringName &p_from); -MonoObject *create_managed_from(const NodePath &p_from); -MonoObject *create_managed_from(const RID &p_from); -MonoObject *create_managed_from(const Array &p_from, GDMonoClass *p_class); -MonoObject *create_managed_from(const Dictionary &p_from, GDMonoClass *p_class); - -MonoDomain *create_domain(const String &p_friendly_name); - -String get_type_desc(MonoType *p_type); -String get_type_desc(MonoReflectionType *p_reftype); - -String get_exception_name_and_message(MonoException *p_exc); - -void debug_print_unhandled_exception(MonoException *p_exc); -void debug_send_unhandled_exception_error(MonoException *p_exc); -void debug_unhandled_exception(MonoException *p_exc); -void print_unhandled_exception(MonoException *p_exc); - -/** - * Sets the exception as pending. The exception will be thrown when returning to managed code. - * If no managed method is being invoked by the runtime, the exception will be treated as - * an unhandled exception and the method will not return. - */ -void set_pending_exception(MonoException *p_exc); - -extern thread_local int current_invoke_count; - -_FORCE_INLINE_ int get_runtime_invoke_count() { - return current_invoke_count; -} - -_FORCE_INLINE_ int &get_runtime_invoke_count_ref() { - return current_invoke_count; -} - -MonoObject *runtime_invoke(MonoMethod *p_method, void *p_obj, void **p_params, MonoException **r_exc); - -MonoString *object_to_string(MonoObject *p_obj, MonoException **r_exc); - -void property_set_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc); -MonoObject *property_get_value(MonoProperty *p_prop, void *p_obj, void **p_params, MonoException **r_exc); - -uint64_t unbox_enum_value(MonoObject *p_boxed, MonoType *p_enum_basetype, bool &r_error); - -void dispose(MonoObject *p_mono_object, MonoException **r_exc); - -struct ScopeThreadAttach { - ScopeThreadAttach(); - ~ScopeThreadAttach(); - -private: - MonoThread *mono_thread = nullptr; -}; - -StringName get_native_godot_class_name(GDMonoClass *p_class); - -template <typename... P> -void add_internal_call(const char *p_name, void (*p_func)(P...)) { -#ifdef JAVASCRIPT_ENABLED - GDMonoWasmM2n::ICallTrampolines<P...>::add(); -#endif - mono_add_internal_call(p_name, (void *)p_func); -} - -template <typename R, typename... P> -void add_internal_call(const char *p_name, R (*p_func)(P...)) { -#ifdef JAVASCRIPT_ENABLED - GDMonoWasmM2n::ICallTrampolinesR<R, P...>::add(); -#endif - mono_add_internal_call(p_name, (void *)p_func); -} -} // namespace GDMonoUtils - -#define NATIVE_GDMONOCLASS_NAME(m_class) (GDMonoUtils::get_native_godot_class_name(m_class)) - -#define GD_MONO_BEGIN_RUNTIME_INVOKE \ - int &_runtime_invoke_count_ref = GDMonoUtils::get_runtime_invoke_count_ref(); \ - _runtime_invoke_count_ref += 1; \ - ((void)0) - -#define GD_MONO_END_RUNTIME_INVOKE \ - _runtime_invoke_count_ref -= 1; \ - ((void)0) - -#define GD_MONO_SCOPE_THREAD_ATTACH \ - GDMonoUtils::ScopeThreadAttach __gdmono__scope__thread__attach__; \ - (void)__gdmono__scope__thread__attach__; \ - ((void)0) - -#ifdef DEBUG_ENABLED -#define GD_MONO_ASSERT_THREAD_ATTACHED \ - CRASH_COND(!GDMonoUtils::is_thread_attached()); \ - ((void)0) -#else -#define GD_MONO_ASSERT_THREAD_ATTACHED ((void)0) -#endif - -#endif // GD_MONO_UTILS_H diff --git a/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp b/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp deleted file mode 100644 index dbfca2dc0c..0000000000 --- a/modules/mono/mono_gd/gd_mono_wasm_m2n.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/*************************************************************************/ -/* gd_mono_wasm_m2n.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "gd_mono_wasm_m2n.h" - -#ifdef JAVASCRIPT_ENABLED - -#include "core/templates/oa_hash_map.h" - -typedef mono_bool (*GodotMonoM2nIcallTrampolineDispatch)(const char *cookie, void *target_func, Mono_InterpMethodArguments *margs); - -// This extern function is implemented in our patched version of Mono -MONO_API void godot_mono_register_m2n_icall_trampoline_dispatch_hook(GodotMonoM2nIcallTrampolineDispatch hook); - -namespace GDMonoWasmM2n { - -struct HashMapCookieComparator { - static bool compare(const char *p_lhs, const char *p_rhs) { - return strcmp(p_lhs, p_rhs) == 0; - } -}; - -// The default hasher supports 'const char *' C Strings, but we need a custom comparator -OAHashMap<const char *, TrampolineFunc, HashMapHasherDefault, HashMapCookieComparator> trampolines; - -void set_trampoline(const char *cookies, GDMonoWasmM2n::TrampolineFunc trampoline_func) { - trampolines.set(cookies, trampoline_func); -} - -mono_bool trampoline_dispatch_hook(const char *cookie, void *target_func, Mono_InterpMethodArguments *margs) { - TrampolineFunc *trampoline_func = trampolines.lookup_ptr(cookie); - - if (!trampoline_func) { - return false; - } - - (*trampoline_func)(target_func, margs); - return true; -} - -bool initialized = false; - -void lazy_initialize() { - // Doesn't need to be thread safe - if (!initialized) { - initialized = true; - godot_mono_register_m2n_icall_trampoline_dispatch_hook(&trampoline_dispatch_hook); - } -} -} // namespace GDMonoWasmM2n - -#endif diff --git a/modules/mono/mono_gd/gd_mono_wasm_m2n.h b/modules/mono/mono_gd/gd_mono_wasm_m2n.h deleted file mode 100644 index 83e2750e5a..0000000000 --- a/modules/mono/mono_gd/gd_mono_wasm_m2n.h +++ /dev/null @@ -1,263 +0,0 @@ -/*************************************************************************/ -/* gd_mono_wasm_m2n.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 GD_MONO_WASM_M2N_H -#define GD_MONO_WASM_M2N_H - -#ifdef JAVASCRIPT_ENABLED - -#include "core/string/ustring.h" -#include "core/typedefs.h" - -#include <mono/metadata/loader.h> -#include <mono/utils/mono-publib.h> -#include <stdexcept> -#include <type_traits> - -extern "C" { - -struct Mono_InterpMethodArguments { - size_t ilen; - void **iargs; - size_t flen; - double *fargs = nullptr; - void **retval; - size_t is_float_ret; - //#ifdef TARGET_WASM - void *sig = nullptr; - //#endif -}; -} // extern "C" - -namespace GDMonoWasmM2n { - -template <typename T, size_t Size> -struct array { - T elems[Size]; -}; - -template <typename T> -constexpr char get_m2n_cookie_impl() { -#define M2N_REG_COOKIE(m_type, m_cookie) \ - if constexpr (std::is_same_v<m_type, T>) { \ - return m_cookie; \ - } - - M2N_REG_COOKIE(MonoBoolean, 'I'); - M2N_REG_COOKIE(int8_t, 'I'); - M2N_REG_COOKIE(uint8_t, 'I'); - M2N_REG_COOKIE(int16_t, 'I'); - M2N_REG_COOKIE(uint16_t, 'I'); - M2N_REG_COOKIE(int32_t, 'I'); - M2N_REG_COOKIE(uint32_t, 'I'); - M2N_REG_COOKIE(int64_t, 'L'); - M2N_REG_COOKIE(uint64_t, 'L'); - M2N_REG_COOKIE(float, 'F'); - M2N_REG_COOKIE(double, 'D'); - - if constexpr (std::is_pointer_v<T>) { - if constexpr (sizeof(void *) == 4) { - return 'I'; - } else { - return 'L'; - } - } - - if constexpr (std::is_void_v<T>) { - return 'V'; - } - - return 'X'; - -#undef M2N_REG_COOKIE -} - -template <typename T> -constexpr char get_m2n_cookie() { - constexpr char cookie = get_m2n_cookie_impl<T>(); - static_assert(cookie != 'X', "Type not supported in internal call signature."); - return cookie; -} - -template <typename... T> -constexpr array<const char, sizeof...(T) + 2> get_m2n_cookies() { - return array<const char, sizeof...(T) + 2>{ 'V', get_m2n_cookie<T>()..., '\0' }; -} - -template <typename R, typename... T> -constexpr array<const char, sizeof...(T) + 2> get_m2n_cookies_r() { - return array<const char, sizeof...(T) + 2>{ get_m2n_cookie<R>(), get_m2n_cookie<T>()..., '\0' }; -} - -template <typename T> -constexpr size_t calc_m2n_index(size_t &r_int_idx, size_t &r_float_idx) { - constexpr char cookie = get_m2n_cookie<T>(); - - static_assert(cookie == 'I' || cookie == 'L' || cookie == 'F' || cookie == 'D'); - - if constexpr (cookie == 'I' || cookie == 'L') { - size_t ret = r_int_idx; - r_int_idx += cookie == 'I' ? 1 : 2; - return ret; - } else { - size_t ret = r_float_idx; - r_float_idx += cookie == 'F' ? 1 : 2; - return ret; - } -} - -template <typename... P> -constexpr array<size_t, sizeof...(P)> get_indices_for_type() { - size_t int_idx = 0; - size_t float_idx = 0; - return array<size_t, sizeof...(P)>{ calc_m2n_index<P>(int_idx, float_idx)... }; -} - -constexpr size_t fidx(size_t p_x) { - if constexpr (sizeof(void *) == 4) { - return p_x * 2; - } else { - return p_x; - } -} - -template <typename T> -T m2n_arg_cast(Mono_InterpMethodArguments *p_margs, size_t p_idx) { - constexpr char cookie = get_m2n_cookie<T>(); - - static_assert(cookie == 'I' || cookie == 'L' || cookie == 'F' || cookie == 'D'); - - if constexpr (cookie == 'I') { - return (T)(size_t)p_margs->iargs[p_idx]; - } else if constexpr (cookie == 'L') { - static_assert(std::is_same_v<T, int64_t> || std::is_same_v<T, uint64_t> || - (sizeof(void *) == 8 && std::is_pointer_v<T>), - "Invalid type for cookie 'L'."); - - union { - T l; - struct { - int32_t lo; - int32_t hi; - } pair; - } p; - - p.pair.lo = (int32_t)(size_t)p_margs->iargs[p_idx]; - p.pair.hi = (int32_t)(size_t)p_margs->iargs[p_idx + 1]; - - return p.l; - } else if constexpr (cookie == 'F') { - return *reinterpret_cast<float *>(&p_margs->fargs[fidx(p_idx)]); - } else if constexpr (cookie == 'D') { - return (T)p_margs->fargs[p_idx]; - } -} - -template <typename... P, size_t... Is> -void m2n_trampoline_with_idx_seq(void *p_target_func, Mono_InterpMethodArguments *p_margs, IndexSequence<Is...>) { - constexpr array<size_t, sizeof...(P)> indices = get_indices_for_type<P...>(); - typedef void (*Func)(P...); - Func func = (Func)p_target_func; - func(m2n_arg_cast<P>(p_margs, indices.elems[Is])...); -} - -template <typename R, typename... P, size_t... Is> -void m2n_trampoline_with_idx_seq_r(void *p_target_func, Mono_InterpMethodArguments *p_margs, IndexSequence<Is...>) { - constexpr array<size_t, sizeof...(P)> indices = get_indices_for_type<P...>(); - typedef R (*Func)(P...); - Func func = (Func)p_target_func; - R res = func(m2n_arg_cast<P>(p_margs, indices.elems[Is])...); - *reinterpret_cast<R *>(p_margs->retval) = res; -} - -inline void m2n_trampoline_with_idx_seq_0(void *p_target_func, Mono_InterpMethodArguments *p_margs) { - typedef void (*Func)(); - Func func = (Func)p_target_func; - func(); -} - -template <typename R> -void m2n_trampoline_with_idx_seq_r0(void *p_target_func, Mono_InterpMethodArguments *p_margs) { - typedef R (*Func)(); - Func func = (Func)p_target_func; - R res = func(); - *reinterpret_cast<R *>(p_margs->retval) = res; -} - -template <typename... P> -void m2n_trampoline(void *p_target_func, Mono_InterpMethodArguments *p_margs) { - if constexpr (sizeof...(P) == 0) { - m2n_trampoline_with_idx_seq_0(p_target_func, p_margs); - } else { - m2n_trampoline_with_idx_seq<P...>(p_target_func, p_margs, BuildIndexSequence<sizeof...(P)>{}); - } -} - -template <typename R, typename... P> -void m2n_trampoline_r(void *p_target_func, Mono_InterpMethodArguments *p_margs) { - if constexpr (sizeof...(P) == 0) { - m2n_trampoline_with_idx_seq_r0<R>(p_target_func, p_margs); - } else { - m2n_trampoline_with_idx_seq_r<R, P...>(p_target_func, p_margs, BuildIndexSequence<sizeof...(P)>{}); - } -} - -typedef void (*TrampolineFunc)(void *p_target_func, Mono_InterpMethodArguments *p_margs); - -void set_trampoline(const char *cookies, TrampolineFunc trampoline_func); - -void lazy_initialize(); - -template <typename... P> -struct ICallTrampolines { - static constexpr auto cookies = get_m2n_cookies<P...>(); - - static void add() { - lazy_initialize(); - set_trampoline(cookies.elems, &m2n_trampoline<P...>); - } -}; - -template <typename R, typename... P> -struct ICallTrampolinesR { - static constexpr auto cookies = get_m2n_cookies_r<R, P...>(); - - static void add() { - lazy_initialize(); - set_trampoline(cookies.elems, &m2n_trampoline_r<R, P...>); - } -}; - -void initialize(); -} // namespace GDMonoWasmM2n - -#endif - -#endif // GD_MONO_WASM_M2N_H diff --git a/modules/mono/mono_gd/i_mono_class_member.h b/modules/mono/mono_gd/i_mono_class_member.h deleted file mode 100644 index 14e8ca82b9..0000000000 --- a/modules/mono/mono_gd/i_mono_class_member.h +++ /dev/null @@ -1,70 +0,0 @@ -/*************************************************************************/ -/* i_mono_class_member.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 I_MONO_CLASS_MEMBER_H -#define I_MONO_CLASS_MEMBER_H - -#include "gd_mono_header.h" - -#include <mono/metadata/object.h> - -class IMonoClassMember { -public: - enum Visibility { - PRIVATE, - PROTECTED_AND_INTERNAL, // FAM_AND_ASSEM - INTERNAL, // ASSEMBLY - PROTECTED, // FAMILY - PUBLIC - }; - - enum MemberType { - MEMBER_TYPE_FIELD, - MEMBER_TYPE_PROPERTY, - MEMBER_TYPE_METHOD - }; - - virtual ~IMonoClassMember() {} - - virtual GDMonoClass *get_enclosing_class() const = 0; - - virtual MemberType get_member_type() const = 0; - - virtual StringName get_name() const = 0; - - virtual bool is_static() = 0; - - virtual Visibility get_visibility() = 0; - - virtual bool has_attribute(GDMonoClass *p_attr_class) = 0; - virtual MonoObject *get_attribute(GDMonoClass *p_attr_class) = 0; -}; - -#endif // I_MONO_CLASS_MEMBER_H diff --git a/modules/mono/mono_gd/managed_type.cpp b/modules/mono/mono_gd/managed_type.cpp deleted file mode 100644 index 5860d7db1e..0000000000 --- a/modules/mono/mono_gd/managed_type.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************/ -/* managed_type.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "managed_type.h" - -#include "gd_mono.h" -#include "gd_mono_class.h" - -ManagedType ManagedType::from_class(GDMonoClass *p_class) { - return ManagedType(mono_type_get_type(p_class->get_mono_type()), p_class); -} - -ManagedType ManagedType::from_class(MonoClass *p_mono_class) { - GDMonoClass *tclass = GDMono::get_singleton()->get_class(p_mono_class); - ERR_FAIL_COND_V(!tclass, ManagedType()); - - return ManagedType(mono_type_get_type(tclass->get_mono_type()), tclass); -} - -ManagedType ManagedType::from_type(MonoType *p_mono_type) { - MonoClass *mono_class = mono_class_from_mono_type(p_mono_type); - GDMonoClass *tclass = GDMono::get_singleton()->get_class(mono_class); - ERR_FAIL_COND_V(!tclass, ManagedType()); - - return ManagedType(mono_type_get_type(p_mono_type), tclass); -} - -ManagedType ManagedType::from_reftype(MonoReflectionType *p_mono_reftype) { - MonoType *mono_type = mono_reflection_type_get_type(p_mono_reftype); - return from_type(mono_type); -} diff --git a/modules/mono/mono_gd/managed_type.h b/modules/mono/mono_gd/managed_type.h deleted file mode 100644 index 603ff3aca1..0000000000 --- a/modules/mono/mono_gd/managed_type.h +++ /dev/null @@ -1,55 +0,0 @@ -/*************************************************************************/ -/* managed_type.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 MANAGED_TYPE_H -#define MANAGED_TYPE_H - -#include <mono/metadata/object.h> - -#include "gd_mono_header.h" - -struct ManagedType { - int type_encoding = 0; - GDMonoClass *type_class = nullptr; - - static ManagedType from_class(GDMonoClass *p_class); - static ManagedType from_class(MonoClass *p_mono_class); - static ManagedType from_type(MonoType *p_mono_type); - static ManagedType from_reftype(MonoReflectionType *p_mono_reftype); - - ManagedType() {} - - ManagedType(int p_type_encoding, GDMonoClass *p_type_class) : - type_encoding(p_type_encoding), - type_class(p_type_class) { - } -}; - -#endif // MANAGED_TYPE_H diff --git a/modules/mono/mono_gd/support/android_support.cpp b/modules/mono/mono_gd/support/android_support.cpp index 4797d5dae1..7fb983cd37 100644 --- a/modules/mono/mono_gd/support/android_support.cpp +++ b/modules/mono/mono_gd/support/android_support.cpp @@ -359,7 +359,7 @@ MonoArray *_gd_mono_android_cert_store_lookup(MonoString *p_alias) { ScopedLocalRef<jbyteArray> encoded(env, (jbyteArray)env->CallObjectMethod(certificate, getEncoded)); jsize encodedLength = env->GetArrayLength(encoded); - MonoArray *encoded_ret = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(uint8_t), encodedLength); + MonoArray *encoded_ret = mono_array_new(mono_domain_get(), mono_get_byte_class(), encodedLength); uint8_t *dest = (uint8_t *)mono_array_addr(encoded_ret, uint8_t, 0); env->GetByteArrayRegion(encoded, 0, encodedLength, reinterpret_cast<jbyte *>(dest)); diff --git a/modules/mono/signal_awaiter_utils.cpp b/modules/mono/signal_awaiter_utils.cpp index 437c4ca54a..55d2138674 100644 --- a/modules/mono/signal_awaiter_utils.cpp +++ b/modules/mono/signal_awaiter_utils.cpp @@ -32,16 +32,14 @@ #include "csharp_script.h" #include "mono_gd/gd_mono_cache.h" -#include "mono_gd/gd_mono_class.h" -#include "mono_gd/gd_mono_marshal.h" -#include "mono_gd/gd_mono_utils.h" -Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, MonoObject *p_awaiter) { +Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, GCHandleIntPtr p_awaiter_handle_ptr) { ERR_FAIL_NULL_V(p_source, ERR_INVALID_DATA); ERR_FAIL_NULL_V(p_target, ERR_INVALID_DATA); // TODO: Use pooling for ManagedCallable instances. - SignalAwaiterCallable *awaiter_callable = memnew(SignalAwaiterCallable(p_target, p_awaiter, p_signal)); + MonoGCHandleData awaiter_handle(p_awaiter_handle_ptr, gdmono::GCHandleType::STRONG_HANDLE); + SignalAwaiterCallable *awaiter_callable = memnew(SignalAwaiterCallable(p_target, awaiter_handle, p_signal)); Callable callable = Callable(awaiter_callable); return p_source->connect(p_signal, callable, Object::CONNECT_ONESHOT); @@ -51,7 +49,7 @@ bool SignalAwaiterCallable::compare_equal(const CallableCustom *p_a, const Calla // Only called if both instances are of type SignalAwaiterCallable. Static cast is safe. const SignalAwaiterCallable *a = static_cast<const SignalAwaiterCallable *>(p_a); const SignalAwaiterCallable *b = static_cast<const SignalAwaiterCallable *>(p_b); - return a->awaiter_handle.handle == b->awaiter_handle.handle; + return a->awaiter_handle.handle.value == b->awaiter_handle.handle.value; } bool SignalAwaiterCallable::compare_less(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -92,6 +90,10 @@ ObjectID SignalAwaiterCallable::get_object() const { return target_id; } +StringName SignalAwaiterCallable::get_signal() const { + return signal; +} + void SignalAwaiterCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better r_return_value = Variant(); @@ -101,38 +103,20 @@ void SignalAwaiterCallable::call(const Variant **p_arguments, int p_argcount, Va "Resumed after await, but class instance is gone."); #endif - MonoArray *signal_args = nullptr; - - if (p_argcount > 0) { - signal_args = mono_array_new(mono_domain_get(), CACHED_CLASS_RAW(MonoObject), p_argcount); - - for (int i = 0; i < p_argcount; i++) { - MonoObject *boxed = GDMonoMarshal::variant_to_mono_object(*p_arguments[i]); - mono_array_setref(signal_args, i, boxed); - } - } + bool awaiter_is_null = false; + GDMonoCache::managed_callbacks.SignalAwaiter_SignalCallback(awaiter_handle.get_intptr(), p_arguments, p_argcount, &awaiter_is_null); - MonoObject *awaiter = awaiter_handle.get_target(); - - if (!awaiter) { + if (awaiter_is_null) { r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; return; } - MonoException *exc = nullptr; - CACHED_METHOD_THUNK(SignalAwaiter, SignalCallback).invoke(awaiter, signal_args, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - ERR_FAIL(); - } else { - r_call_error.error = Callable::CallError::CALL_OK; - } + r_call_error.error = Callable::CallError::CALL_OK; } -SignalAwaiterCallable::SignalAwaiterCallable(Object *p_target, MonoObject *p_awaiter, const StringName &p_signal) : +SignalAwaiterCallable::SignalAwaiterCallable(Object *p_target, MonoGCHandleData p_awaiter_handle, const StringName &p_signal) : target_id(p_target->get_instance_id()), - awaiter_handle(MonoGCHandleData::new_strong_handle(p_awaiter)), + awaiter_handle(p_awaiter_handle), signal(p_signal) { } @@ -148,7 +132,7 @@ bool EventSignalCallable::compare_equal(const CallableCustom *p_a, const Callabl return false; } - if (a->event_signal != b->event_signal) { + if (a->event_signal_name != b->event_signal_name) { return false; } @@ -163,7 +147,7 @@ bool EventSignalCallable::compare_less(const CallableCustom *p_a, const Callable } uint32_t EventSignalCallable::hash() const { - uint32_t hash = event_signal->field->get_name().hash(); + uint32_t hash = event_signal_name.hash(); return hash_murmur3_one_64(owner->get_instance_id(), hash); } @@ -173,8 +157,7 @@ String EventSignalCallable::get_as_text() const { if (script.is_valid() && script->get_path().is_resource_file()) { class_name += "(" + script->get_path().get_file() + ")"; } - StringName signal = event_signal->field->get_name(); - return class_name + "::EventSignalMiddleman::" + String(signal); + return class_name + "::EventSignalMiddleman::" + String(event_signal_name); } CallableCustom::CompareEqualFunc EventSignalCallable::get_compare_equal_func() const { @@ -190,39 +173,32 @@ ObjectID EventSignalCallable::get_object() const { } StringName EventSignalCallable::get_signal() const { - return event_signal->field->get_name(); + return event_signal_name; } void EventSignalCallable::call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const { r_call_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; // Can't find anything better r_return_value = Variant(); - ERR_FAIL_COND(p_argcount < event_signal->invoke_method->get_parameters_count()); - CSharpInstance *csharp_instance = CAST_CSHARP_INSTANCE(owner->get_script_instance()); ERR_FAIL_NULL(csharp_instance); - MonoObject *owner_managed = csharp_instance->get_mono_object(); - ERR_FAIL_NULL(owner_managed); + GCHandleIntPtr owner_gchandle_intptr = csharp_instance->get_gchandle_intptr(); + + bool awaiter_is_null = false; + GDMonoCache::managed_callbacks.ScriptManagerBridge_RaiseEventSignal( + owner_gchandle_intptr, &event_signal_name, + p_arguments, p_argcount, &awaiter_is_null); - MonoObject *delegate_field_value = event_signal->field->get_value(owner_managed); - if (!delegate_field_value) { - r_call_error.error = Callable::CallError::CALL_OK; + if (awaiter_is_null) { + r_call_error.error = Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL; return; } - MonoException *exc = nullptr; - event_signal->invoke_method->invoke(delegate_field_value, p_arguments, &exc); - - if (exc) { - GDMonoUtils::set_pending_exception(exc); - ERR_FAIL(); - } else { - r_call_error.error = Callable::CallError::CALL_OK; - } + r_call_error.error = Callable::CallError::CALL_OK; } -EventSignalCallable::EventSignalCallable(Object *p_owner, const CSharpScript::EventSignal *p_event_signal) : +EventSignalCallable::EventSignalCallable(Object *p_owner, const StringName &p_event_signal_name) : owner(p_owner), - event_signal(p_event_signal) { + event_signal_name(p_event_signal_name) { } diff --git a/modules/mono/signal_awaiter_utils.h b/modules/mono/signal_awaiter_utils.h index 532aa3e327..a53ae56bf5 100644 --- a/modules/mono/signal_awaiter_utils.h +++ b/modules/mono/signal_awaiter_utils.h @@ -36,9 +36,14 @@ #include "csharp_script.h" #include "mono_gc_handle.h" -Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, MonoObject *p_awaiter); +Error gd_mono_connect_signal_awaiter(Object *p_source, const StringName &p_signal, Object *p_target, GCHandleIntPtr p_awaiter_handle_ptr); -class SignalAwaiterCallable : public CallableCustom { +class BaseSignalCallable : public CallableCustom { +public: + virtual StringName get_signal() const = 0; +}; + +class SignalAwaiterCallable : public BaseSignalCallable { ObjectID target_id; MonoGCHandleData awaiter_handle; StringName signal; @@ -59,17 +64,17 @@ public: ObjectID get_object() const override; - _FORCE_INLINE_ StringName get_signal() const { return signal; } + StringName get_signal() const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; - SignalAwaiterCallable(Object *p_target, MonoObject *p_awaiter, const StringName &p_signal); + SignalAwaiterCallable(Object *p_target, MonoGCHandleData p_awaiter_handle, const StringName &p_signal); ~SignalAwaiterCallable(); }; -class EventSignalCallable : public CallableCustom { +class EventSignalCallable : public BaseSignalCallable { Object *owner = nullptr; - const CSharpScript::EventSignal *event_signal; + StringName event_signal_name; public: static bool compare_equal(const CallableCustom *p_a, const CallableCustom *p_b); @@ -87,11 +92,11 @@ public: ObjectID get_object() const override; - StringName get_signal() const; + StringName get_signal() const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; - EventSignalCallable(Object *p_owner, const CSharpScript::EventSignal *p_event_signal); + EventSignalCallable(Object *p_owner, const StringName &p_event_signal_name); }; #endif // SIGNAL_AWAITER_UTILS_H diff --git a/modules/mono/utils/mono_reg_utils.cpp b/modules/mono/utils/mono_reg_utils.cpp deleted file mode 100644 index 8e37e6943c..0000000000 --- a/modules/mono/utils/mono_reg_utils.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/*************************************************************************/ -/* mono_reg_utils.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 "mono_reg_utils.h" -#include "core/io/dir_access.h" - -#ifdef WINDOWS_ENABLED - -#include "core/os/os.h" - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -namespace MonoRegUtils { - -template <int> -REGSAM bitness_sam_impl(); - -template <> -REGSAM bitness_sam_impl<4>() { - return KEY_WOW64_64KEY; -} - -template <> -REGSAM bitness_sam_impl<8>() { - return KEY_WOW64_32KEY; -} - -REGSAM _get_bitness_sam() { - return bitness_sam_impl<sizeof(size_t)>(); -} - -LONG _RegOpenKey(HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult) { - LONG res = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ, phkResult); - - if (res != ERROR_SUCCESS) { - res = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ | _get_bitness_sam(), phkResult); - } - - return res; -} - -LONG _RegKeyQueryString(HKEY hKey, const String &p_value_name, String &r_value) { - Vector<WCHAR> buffer; - buffer.resize(512); - DWORD dwBufferSize = buffer.size(); - - LONG res = RegQueryValueExW(hKey, (LPCWSTR)(p_value_name.utf16().get_data()), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); - - if (res == ERROR_MORE_DATA) { - // dwBufferSize now contains the actual size - buffer.resize(dwBufferSize); - res = RegQueryValueExW(hKey, (LPCWSTR)(p_value_name.utf16().get_data()), 0, nullptr, (LPBYTE)buffer.ptr(), &dwBufferSize); - } - - if (res == ERROR_SUCCESS) { - r_value = String(buffer.ptr(), buffer.size()); - } else { - r_value = String(); - } - - return res; -} - -LONG _find_mono_in_reg(const String &p_subkey, MonoRegInfo &r_info, bool p_old_reg = false) { - HKEY hKey; - LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey); - - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - if (!p_old_reg) { - res = _RegKeyQueryString(hKey, "Version", r_info.version); - if (res != ERROR_SUCCESS) { - goto cleanup; - } - } - - res = _RegKeyQueryString(hKey, "SdkInstallRoot", r_info.install_root_dir); - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - res = _RegKeyQueryString(hKey, "FrameworkAssemblyDirectory", r_info.assembly_dir); - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - res = _RegKeyQueryString(hKey, "MonoConfigDir", r_info.config_dir); - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - if (r_info.install_root_dir.ends_with("\\")) { - r_info.bin_dir = r_info.install_root_dir + "bin"; - } else { - r_info.bin_dir = r_info.install_root_dir + "\\bin"; - } - -cleanup: - RegCloseKey(hKey); - return res; -} - -LONG _find_mono_in_reg_old(const String &p_subkey, MonoRegInfo &r_info) { - String default_clr; - - HKEY hKey; - LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, (LPCWSTR)(p_subkey.utf16().get_data()), &hKey); - - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - res = _RegKeyQueryString(hKey, "DefaultCLR", default_clr); - - if (res == ERROR_SUCCESS && default_clr.length()) { - r_info.version = default_clr; - res = _find_mono_in_reg(p_subkey + "\\" + default_clr, r_info, true); - } - -cleanup: - RegCloseKey(hKey); - return res; -} - -MonoRegInfo find_mono() { - MonoRegInfo info; - - if (_find_mono_in_reg("Software\\Mono", info) == ERROR_SUCCESS) { - return info; - } - - if (_find_mono_in_reg_old("Software\\Novell\\Mono", info) == ERROR_SUCCESS) { - return info; - } - - return MonoRegInfo(); -} - -String find_msbuild_tools_path() { - String msbuild_tools_path; - - // Try to find 15.0 with vswhere - - String vswhere_path = OS::get_singleton()->get_environment(sizeof(size_t) == 8 ? "ProgramFiles(x86)" : "ProgramFiles"); - vswhere_path += "\\Microsoft Visual Studio\\Installer\\vswhere.exe"; - - List<String> vswhere_args; - vswhere_args.push_back("-latest"); - vswhere_args.push_back("-products"); - vswhere_args.push_back("*"); - vswhere_args.push_back("-requires"); - vswhere_args.push_back("Microsoft.Component.MSBuild"); - - String output; - int exit_code; - OS::get_singleton()->execute(vswhere_path, vswhere_args, &output, &exit_code); - - if (exit_code == 0) { - Vector<String> lines = output.split("\n"); - - for (int i = 0; i < lines.size(); i++) { - const String &line = lines[i]; - int sep_idx = line.find(":"); - - if (sep_idx > 0) { - String key = line.substr(0, sep_idx); // No need to trim - - if (key == "installationPath") { - String val = line.substr(sep_idx + 1, line.length()).strip_edges(); - - ERR_BREAK(val.is_empty()); - - if (!val.ends_with("\\")) { - val += "\\"; - } - - // Since VS2019, the directory is simply named "Current" - String msbuild_dir = val + "MSBuild\\Current\\Bin"; - if (DirAccess::exists(msbuild_dir)) { - return msbuild_dir; - } - - // Directory name "15.0" is used in VS 2017 - return val + "MSBuild\\15.0\\Bin"; - } - } - } - } - - // Try to find 14.0 in the Registry - - HKEY hKey; - LONG res = _RegOpenKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\MSBuild\\ToolsVersions\\14.0", &hKey); - - if (res != ERROR_SUCCESS) { - goto cleanup; - } - - res = _RegKeyQueryString(hKey, "MSBuildToolsPath", msbuild_tools_path); - - if (res != ERROR_SUCCESS) { - goto cleanup; - } - -cleanup: - RegCloseKey(hKey); - - return msbuild_tools_path; -} -} // namespace MonoRegUtils - -#endif // WINDOWS_ENABLED diff --git a/modules/mono/utils/mono_reg_utils.h b/modules/mono/utils/mono_reg_utils.h deleted file mode 100644 index 5be60d4930..0000000000 --- a/modules/mono/utils/mono_reg_utils.h +++ /dev/null @@ -1,54 +0,0 @@ -/*************************************************************************/ -/* mono_reg_utils.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* 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 MONO_REG_UTILS_H -#define MONO_REG_UTILS_H - -#ifdef WINDOWS_ENABLED - -#include "core/string/ustring.h" - -struct MonoRegInfo { - String version; - String install_root_dir; - String assembly_dir; - String config_dir; - String bin_dir; -}; - -namespace MonoRegUtils { - -MonoRegInfo find_mono(); -String find_msbuild_tools_path(); -} // namespace MonoRegUtils - -#endif // WINDOWS_ENABLED - -#endif // MONO_REG_UTILS_H diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index a1905dfcfe..19ad59a1bc 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -51,6 +51,37 @@ namespace path { +String find_executable(const String &p_name) { +#ifdef WINDOWS_ENABLED + Vector<String> exts = OS::get_singleton()->get_environment("PATHEXT").split(ENV_PATH_SEP, false); +#endif + Vector<String> env_path = OS::get_singleton()->get_environment("PATH").split(ENV_PATH_SEP, false); + + if (env_path.is_empty()) { + return String(); + } + + for (int i = 0; i < env_path.size(); i++) { + String p = path::join(env_path[i], p_name); + +#ifdef WINDOWS_ENABLED + for (int j = 0; j < exts.size(); j++) { + String p2 = p + exts[j].to_lower(); // lowercase to reduce risk of case mismatch warning + + if (FileAccess::exists(p2)) { + return p2; + } + } +#else + if (FileAccess::exists(p)) { + return p; + } +#endif + } + + return String(); +} + String cwd() { #ifdef WINDOWS_ENABLED const DWORD expected_size = ::GetCurrentDirectoryW(0, nullptr); diff --git a/modules/mono/utils/path_utils.h b/modules/mono/utils/path_utils.h index 9a2c757361..d1c3d3ccfd 100644 --- a/modules/mono/utils/path_utils.h +++ b/modules/mono/utils/path_utils.h @@ -36,6 +36,8 @@ namespace path { +String find_executable(const String &p_name); + String join(const String &p_a, const String &p_b); String join(const String &p_a, const String &p_b, const String &p_c); String join(const String &p_a, const String &p_b, const String &p_c, const String &p_d); diff --git a/modules/mono/utils/string_utils.cpp b/modules/mono/utils/string_utils.cpp index 975f2d8332..b0f94310b8 100644 --- a/modules/mono/utils/string_utils.cpp +++ b/modules/mono/utils/string_utils.cpp @@ -65,7 +65,7 @@ int sfind(const String &p_text, int p_from) { break; case 1: { char32_t c = src[read_pos]; - found = src[read_pos] == 's' || (c >= '0' && c <= '4'); + found = src[read_pos] == 's' || (c >= '0' && c <= '5'); break; } default: @@ -86,32 +86,13 @@ int sfind(const String &p_text, int p_from) { } } // namespace -String sformat(const String &p_text, const Variant &p1, const Variant &p2, const Variant &p3, const Variant &p4, const Variant &p5) { +String sformat(const String &p_text, const String &p1, const String &p2, + const String &p3, const String &p4, const String &p5, const String &p6) { if (p_text.length() < 2) { return p_text; } - Array args; - - if (p1.get_type() != Variant::NIL) { - args.push_back(p1); - - if (p2.get_type() != Variant::NIL) { - args.push_back(p2); - - if (p3.get_type() != Variant::NIL) { - args.push_back(p3); - - if (p4.get_type() != Variant::NIL) { - args.push_back(p4); - - if (p5.get_type() != Variant::NIL) { - args.push_back(p5); - } - } - } - } - } + String args[6] = { p1, p2, p3, p4, p5, p6 }; String new_string; @@ -125,7 +106,7 @@ String sformat(const String &p_text, const Variant &p1, const Variant &p2, const int req_index = (c == 's' ? findex++ : c - '0'); new_string += p_text.substr(search_from, result - search_from); - new_string += args[req_index].operator String(); + new_string += args[req_index]; search_from = result + 2; } diff --git a/modules/mono/utils/string_utils.h b/modules/mono/utils/string_utils.h index fa4c5e89f4..b00dd9dde8 100644 --- a/modules/mono/utils/string_utils.h +++ b/modules/mono/utils/string_utils.h @@ -36,7 +36,8 @@ #include <stdarg.h> -String sformat(const String &p_text, const Variant &p1 = Variant(), const Variant &p2 = Variant(), const Variant &p3 = Variant(), const Variant &p4 = Variant(), const Variant &p5 = Variant()); +String sformat(const String &p_text, const String &p1 = String(), const String &p2 = String(), + const String &p3 = String(), const String &p4 = String(), const String &p5 = String(), const String &p6 = String()); #ifdef TOOLS_ENABLED bool is_csharp_keyword(const String &p_name); diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 73dbf2f443..c0cff08f13 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -1313,12 +1313,14 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f fargs.stream = &fd->stream; int max_index = 0; - FT_Face tmp_face; + FT_Face tmp_face = nullptr; error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); - if (error == 0) { + if (tmp_face && error == 0) { max_index = tmp_face->num_faces - 1; } - FT_Done_Face(tmp_face); + if (tmp_face) { + FT_Done_Face(tmp_face); + } error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); if (error) { diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index 55d912a10a..3b91c6981e 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -733,12 +733,14 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f fargs.stream = &fd->stream; int max_index = 0; - FT_Face tmp_face; + FT_Face tmp_face = nullptr; error = FT_Open_Face(ft_library, &fargs, -1, &tmp_face); - if (error == 0) { + if (tmp_face && error == 0) { max_index = tmp_face->num_faces - 1; } - FT_Done_Face(tmp_face); + if (tmp_face) { + FT_Done_Face(tmp_face); + } error = FT_Open_Face(ft_library, &fargs, CLAMP(p_font_data->face_index, 0, max_index), &fd->face); if (error) { diff --git a/modules/visual_script/editor/visual_script_editor.cpp b/modules/visual_script/editor/visual_script_editor.cpp index 7f8e9d8254..2c7d23a3c4 100644 --- a/modules/visual_script/editor/visual_script_editor.cpp +++ b/modules/visual_script/editor/visual_script_editor.cpp @@ -2936,7 +2936,7 @@ Control *VisualScriptEditor::get_edit_menu() { } void VisualScriptEditor::_change_base_type() { - select_base_type->popup_create(true, true); + select_base_type->popup_create(true, true, script->get_instance_base_type()); } void VisualScriptEditor::_toggle_tool_script() { diff --git a/modules/visual_script/visual_script.h b/modules/visual_script/visual_script.h index 14cb14e8d9..d3a90d53fb 100644 --- a/modules/visual_script/visual_script.h +++ b/modules/visual_script/visual_script.h @@ -409,6 +409,9 @@ public: virtual void get_property_list(List<PropertyInfo> *p_properties) const; virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const; + virtual bool property_can_revert(const StringName &p_name) const { return false; }; + virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const { return false; }; + virtual void get_method_list(List<MethodInfo> *p_list) const; virtual bool has_method(const StringName &p_method) const; virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js index c7729a8c5b..768eaf9e1d 100644 --- a/platform/javascript/js/libs/library_godot_display.js +++ b/platform/javascript/js/libs/library_godot_display.js @@ -336,26 +336,12 @@ const GodotDisplay = { $GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotEventListeners', '$GodotDisplayScreen', '$GodotDisplayVK'], $GodotDisplay: { window_icon: '', - findDPI: function () { - function testDPI(dpi) { - return window.matchMedia(`(max-resolution: ${dpi}dpi)`).matches; - } - function bisect(low, high, func) { - const mid = parseInt(((high - low) / 2) + low, 10); - if (high - low <= 1) { - return func(high) ? high : low; - } - if (func(mid)) { - return bisect(low, mid, func); - } - return bisect(mid, high, func); - } - try { - const dpi = bisect(0, 800, testDPI); - return dpi >= 96 ? dpi : 96; - } catch (e) { - return 96; - } + getDPI: function () { + // devicePixelRatio is given in dppx + // https://drafts.csswg.org/css-values/#resolution + // > due to the 1:96 fixed ratio of CSS *in* to CSS *px*, 1dppx is equivalent to 96dpi. + const dpi = Math.round(window.devicePixelRatio * 96); + return dpi >= 96 ? dpi : 96; }, }, @@ -461,7 +447,7 @@ const GodotDisplay = { godot_js_display_screen_dpi_get__sig: 'i', godot_js_display_screen_dpi_get: function () { - return GodotDisplay.findDPI(); + return GodotDisplay.getDPI(); }, godot_js_display_pixel_ratio_get__sig: 'f', diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h index 54c479fc81..5fda5dd974 100644 --- a/platform/macos/display_server_macos.h +++ b/platform/macos/display_server_macos.h @@ -196,6 +196,9 @@ private: static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil); + bool _has_help_menu() const; + NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out); + public: NSMenu *get_dock_menu() const; void menu_callback(id p_sender); @@ -224,15 +227,15 @@ public: virtual bool has_feature(Feature p_feature) const override; virtual String get_name() const override; - virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; - virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override; - virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1) override; + virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override; + virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override; + virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1) override; virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override; virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const override; @@ -250,6 +253,7 @@ public: virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override; virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override; virtual Ref<Texture2D> global_menu_get_item_icon(const String &p_menu_root, int p_idx) const override; + virtual int global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const override; virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override; virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override; @@ -264,6 +268,7 @@ public: virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override; virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override; virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) override; + virtual void global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) override; virtual int global_menu_get_item_count(const String &p_menu_root) const override; diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm index a49485154b..8142f44bb0 100644 --- a/platform/macos/display_server_macos.mm +++ b/platform/macos/display_server_macos.mm @@ -63,7 +63,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) const { const NSMenu *menu = nullptr; - if (p_menu_root == "") { + if (p_menu_root == "" || p_menu_root.to_lower() == "_main") { // Main menu. menu = [NSApp mainMenu]; } else if (p_menu_root.to_lower() == "_dock") { @@ -84,7 +84,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) { NSMenu *menu = nullptr; - if (p_menu_root == "") { + if (p_menu_root == "" || p_menu_root.to_lower() == "_main") { // Main menu. menu = [NSApp mainMenu]; } else if (p_menu_root.to_lower() == "_dock") { @@ -714,18 +714,51 @@ String DisplayServerMacOS::get_name() const { return "macOS"; } -void DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { - _THREAD_SAFE_METHOD_ +bool DisplayServerMacOS::_has_help_menu() const { + if ([NSApp helpMenu]) { + return true; + } else { + NSMenu *menu = [NSApp mainMenu]; + const NSMenuItem *menu_item = [menu itemAtIndex:[menu numberOfItems] - 1]; + if (menu_item) { + String menu_name = String::utf8([[menu_item title] UTF8String]); + if (menu_name == "Help" || menu_name == RTR("Help")) { + return true; + } + } + return false; + } +} +NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) { NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems]; + if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) { + p_index = [menu numberOfItems]; + } else if (p_index < 0) { + p_index = item_count; } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_index++; + } + p_index = CLAMP(p_index, 0, item_count); } + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + *r_out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index; + return menu_item; + } + return nullptr; +} + +int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { + _THREAD_SAFE_METHOD_ + + int out = -1; + NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out); + if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; @@ -735,20 +768,15 @@ void DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const S [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } + return out; } -void DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } + int out = -1; + NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out); + if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; @@ -758,20 +786,15 @@ void DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, c [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } + return out; } -void DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } + int out = -1; + NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out); + if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; @@ -790,20 +813,15 @@ void DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, co [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } + return out; } -void DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } + int out = -1; + NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out); + if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; @@ -822,20 +840,15 @@ void DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_ro [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } + return out; } -void DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } + int out = -1; + NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out); + if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; @@ -845,20 +858,15 @@ void DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_r [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } + return out; } -void DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ - NSMenu *menu = _get_menu_root(p_menu_root); - if (menu) { - String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); - NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; - } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; - } + int out = -1; + NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out); + if (menu_item) { GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; @@ -877,20 +885,31 @@ void DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_m [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } + return out; } -void DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); + int out = -1; if (menu) { String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK); NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems]; + if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) { + p_index = [menu numberOfItems]; + } else if (p_index < 0) { + p_index = item_count; } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]]; + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_index++; + } + p_index = CLAMP(p_index, 0, item_count); } + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index]; + out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index; + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; obj->callback = p_callback; obj->meta = p_tag; @@ -900,44 +919,69 @@ void DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_ro [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)]; [menu_item setRepresentedObject:obj]; } + return out; } -void DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { +int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); NSMenu *sub_menu = _get_menu_root(p_submenu); + int out = -1; if (menu && sub_menu) { if (sub_menu == menu) { ERR_PRINT("Can't set submenu to self!"); - return; + return -1; } if ([sub_menu supermenu]) { ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!"); - return; + return -1; } NSMenuItem *menu_item; - if (p_index != -1) { - menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; + int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems]; + if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) { + p_index = [menu numberOfItems]; + } else if (p_index < 0) { + p_index = item_count; } else { - menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""]; + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_index++; + } + p_index = CLAMP(p_index, 0, item_count); } + menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index]; + out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index; + + GodotMenuItem *obj = [[GodotMenuItem alloc] init]; + obj->callback = Callable(); + obj->checkable_type = CHECKABLE_TYPE_NONE; + obj->max_states = 0; + obj->state = 0; + [menu_item setRepresentedObject:obj]; + [sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]]; [menu setSubmenu:sub_menu forItem:menu_item]; } + return out; } -void DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) { +int DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if (p_index != -1) { - [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index]; + if (menu == [NSApp mainMenu]) { // Do not add separators into main menu. + return -1; + } + if (p_index < 0) { + p_index = [menu numberOfItems]; } else { - [menu addItem:[NSMenuItem separatorItem]]; + p_index = CLAMP(p_index, 0, [menu numberOfItems]); } + [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index]; + return p_index; } + return -1; } int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const { @@ -945,7 +989,11 @@ int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_men const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]] - 1; + } else { + return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; + } } return -1; @@ -956,12 +1004,16 @@ int DisplayServerMacOS::global_menu_get_item_index_from_tag(const String &p_menu const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - for (NSInteger i = 0; i < [menu numberOfItems]; i++) { + for (NSInteger i = (menu == [NSApp mainMenu]) ? 1 : 0; i < [menu numberOfItems]; i++) { const NSMenuItem *menu_item = [menu itemAtIndex:i]; if (menu_item) { const GodotMenuItem *obj = [menu_item representedObject]; if (obj && obj->meta == p_tag) { - return i; + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + return i - 1; + } else { + return i; + } } } } @@ -975,6 +1027,11 @@ bool DisplayServerMacOS::global_menu_is_item_checked(const String &p_menu_root, const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, false); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return ([menu_item state] == NSControlStateValueOn); @@ -988,6 +1045,11 @@ bool DisplayServerMacOS::global_menu_is_item_checkable(const String &p_menu_root const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, false); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1004,6 +1066,11 @@ bool DisplayServerMacOS::global_menu_is_item_radio_checkable(const String &p_men const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, false); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1020,6 +1087,11 @@ Callable DisplayServerMacOS::global_menu_get_item_callback(const String &p_menu_ const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, Callable()); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1036,6 +1108,11 @@ Variant DisplayServerMacOS::global_menu_get_item_tag(const String &p_menu_root, const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, Variant()); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Variant()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1052,6 +1129,11 @@ String DisplayServerMacOS::global_menu_get_item_text(const String &p_menu_root, const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, String()); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return String::utf8([[menu_item title] UTF8String]); @@ -1065,6 +1147,11 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, String()); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { const NSMenu *sub_menu = [menu_item submenu]; @@ -1085,6 +1172,11 @@ Key DisplayServerMacOS::global_menu_get_item_accelerator(const String &p_menu_ro const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, Key::NONE); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Key::NONE); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { String ret = String::utf8([[menu_item keyEquivalent] UTF8String]); @@ -1116,6 +1208,11 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root, const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, false); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return ![menu_item isEnabled]; @@ -1129,6 +1226,11 @@ String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_roo const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, String()); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { return String::utf8([[menu_item toolTip] UTF8String]); @@ -1142,6 +1244,11 @@ int DisplayServerMacOS::global_menu_get_item_state(const String &p_menu_root, in const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1158,6 +1265,11 @@ int DisplayServerMacOS::global_menu_get_item_max_states(const String &p_menu_roo const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1174,6 +1286,11 @@ Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_men const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { + ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>()); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Ref<Texture2D>()); const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; @@ -1187,14 +1304,34 @@ Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_men return Ref<Texture2D>(); } +int DisplayServerMacOS::global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const { + _THREAD_SAFE_METHOD_ + + const NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + ERR_FAIL_COND_V(p_idx < 0, 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0); + const NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + return [menu_item indentationLevel]; + } + } + return 0; +} + void DisplayServerMacOS::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) { _THREAD_SAFE_METHOD_ NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { if (p_checked) { @@ -1211,12 +1348,15 @@ void DisplayServerMacOS::global_menu_set_item_checkable(const String &p_menu_roo NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; + ERR_FAIL_COND(!obj); obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE; } } @@ -1227,12 +1367,15 @@ void DisplayServerMacOS::global_menu_set_item_radio_checkable(const String &p_me NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; + ERR_FAIL_COND(!obj); obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE; } } @@ -1243,12 +1386,15 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; + ERR_FAIL_COND(!obj); obj->callback = p_callback; } } @@ -1259,12 +1405,15 @@ void DisplayServerMacOS::global_menu_set_item_tag(const String &p_menu_root, int NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; + ERR_FAIL_COND(!obj); obj->meta = p_tag; } } @@ -1275,9 +1424,11 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]]; @@ -1299,9 +1450,11 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root, ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!"); return; } - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu setSubmenu:sub_menu forItem:menu_item]; @@ -1314,9 +1467,11 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)]; @@ -1331,9 +1486,11 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setEnabled:(!p_disabled)]; @@ -1346,9 +1503,11 @@ void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root, NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { [menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]]; @@ -1361,15 +1520,16 @@ void DisplayServerMacOS::global_menu_set_item_state(const String &p_menu_root, i NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; - if (obj) { - obj->state = p_state; - } + ERR_FAIL_COND(!obj); + obj->state = p_state; } } } @@ -1379,15 +1539,16 @@ void DisplayServerMacOS::global_menu_set_item_max_states(const String &p_menu_ro NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; - if (obj) { - obj->max_states = p_max_states; - } + ERR_FAIL_COND(!obj); + obj->max_states = p_max_states; } } } @@ -1397,12 +1558,15 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; if (menu_item) { GodotMenuItem *obj = [menu_item representedObject]; + ERR_FAIL_COND(!obj); if (p_icon.is_valid()) { obj->img = p_icon->get_image(); obj->img = obj->img->duplicate(); @@ -1419,12 +1583,33 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in } } +void DisplayServerMacOS::global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) { + _THREAD_SAFE_METHOD_ + + NSMenu *menu = _get_menu_root(p_menu_root); + if (menu) { + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; + } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); + NSMenuItem *menu_item = [menu itemAtIndex:p_idx]; + if (menu_item) { + [menu_item setIndentationLevel:p_level]; + } + } +} + int DisplayServerMacOS::global_menu_get_item_count(const String &p_menu_root) const { _THREAD_SAFE_METHOD_ const NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - return [menu numberOfItems]; + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + return [menu numberOfItems] - 1; + } else { + return [menu numberOfItems]; + } } else { return 0; } @@ -1435,9 +1620,11 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int NSMenu *menu = _get_menu_root(p_menu_root); if (menu) { - if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not delete Apple menu. - return; + ERR_FAIL_COND(p_idx < 0); + if (menu == [NSApp mainMenu]) { // Skip Apple menu. + p_idx++; } + ERR_FAIL_COND(p_idx >= [menu numberOfItems]); [menu removeItemAtIndex:p_idx]; } } @@ -1453,6 +1640,9 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) { NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""]; [menu setSubmenu:apple_menu forItem:menu_item]; } + if (submenu.has(p_menu_root)) { + submenu.erase(p_menu_root); + } } } @@ -3222,7 +3412,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM [apple_menu addItem:[NSMenuItem separatorItem]]; - title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname]; + title = [NSString stringWithFormat:NSLocalizedString(@"\t\tQuit %@", nil), nsappname]; [apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; // Add items to the menu bar. diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h index 2c12897f10..e0b9f41632 100644 --- a/platform/macos/godot_menu_item.h +++ b/platform/macos/godot_menu_item.h @@ -46,7 +46,6 @@ enum GlobalMenuCheckType { @public Callable callback; Variant meta; - int id; GlobalMenuCheckType checkable_type; int max_states; int state; diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp index 68e5ffdcf9..c4036faa79 100644 --- a/scene/2d/ray_cast_2d.cpp +++ b/scene/2d/ray_cast_2d.cpp @@ -82,6 +82,10 @@ Object *RayCast2D::get_collider() const { return ObjectDB::get_instance(against); } +RID RayCast2D::get_collider_rid() const { + return against_rid; +} + int RayCast2D::get_collider_shape() const { return against_shape; } @@ -203,12 +207,14 @@ void RayCast2D::_update_raycast_state() { if (dss->intersect_ray(ray_params, rr)) { collided = true; against = rr.collider_id; + against_rid = rr.rid; collision_point = rr.position; collision_normal = rr.normal; against_shape = rr.shape; } else { collided = false; against = ObjectID(); + against_rid = RID(); against_shape = 0; } @@ -321,6 +327,7 @@ void RayCast2D::_bind_methods() { ClassDB::bind_method(D_METHOD("force_raycast_update"), &RayCast2D::force_raycast_update); ClassDB::bind_method(D_METHOD("get_collider"), &RayCast2D::get_collider); + ClassDB::bind_method(D_METHOD("get_collider_rid"), &RayCast2D::get_collider_rid); ClassDB::bind_method(D_METHOD("get_collider_shape"), &RayCast2D::get_collider_shape); ClassDB::bind_method(D_METHOD("get_collision_point"), &RayCast2D::get_collision_point); ClassDB::bind_method(D_METHOD("get_collision_normal"), &RayCast2D::get_collision_normal); diff --git a/scene/2d/ray_cast_2d.h b/scene/2d/ray_cast_2d.h index 1fb97d89fe..57f993fe8d 100644 --- a/scene/2d/ray_cast_2d.h +++ b/scene/2d/ray_cast_2d.h @@ -41,6 +41,7 @@ class RayCast2D : public Node2D { bool enabled = true; bool collided = false; ObjectID against; + RID against_rid; int against_shape = 0; Vector2 collision_point; Vector2 collision_normal; @@ -91,6 +92,7 @@ public: bool is_colliding() const; Object *get_collider() const; + RID get_collider_rid() const; int get_collider_shape() const; Vector2 get_collision_point() const; Vector2 get_collision_normal() const; diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index 5ba8c95a06..13bdd2bd5f 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -2063,6 +2063,18 @@ int TileMap::get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bo return E->value.alternative_tile; } +TileData *TileMap::get_cell_tile_data(int p_layer, const Vector2i &p_coords, bool p_use_proxies) const { + int source_id = get_cell_source_id(p_layer, p_coords, p_use_proxies); + ERR_FAIL_COND_V_MSG(source_id == TileSet::INVALID_SOURCE, nullptr, vformat("Invalid TileSetSource at cell %s. Make sure a tile exists at this cell.", p_coords)); + + Ref<TileSetAtlasSource> source = tile_set->get_source(source_id); + if (source.is_valid()) { + return source->get_tile_data(get_cell_atlas_coords(p_layer, p_coords, p_use_proxies), get_cell_alternative_tile(p_layer, p_coords, p_use_proxies)); + } + + return nullptr; +} + Ref<TileMapPattern> TileMap::get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array) { ERR_FAIL_INDEX_V(p_layer, (int)layers.size(), nullptr); ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); @@ -3846,9 +3858,10 @@ void TileMap::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell", "layer", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMap::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(0)); ClassDB::bind_method(D_METHOD("erase_cell", "layer", "coords"), &TileMap::erase_cell); - ClassDB::bind_method(D_METHOD("get_cell_source_id", "layer", "coords", "use_proxies"), &TileMap::get_cell_source_id); - ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "layer", "coords", "use_proxies"), &TileMap::get_cell_atlas_coords); - ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "layer", "coords", "use_proxies"), &TileMap::get_cell_alternative_tile); + ClassDB::bind_method(D_METHOD("get_cell_source_id", "layer", "coords", "use_proxies"), &TileMap::get_cell_source_id, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "layer", "coords", "use_proxies"), &TileMap::get_cell_atlas_coords, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "layer", "coords", "use_proxies"), &TileMap::get_cell_alternative_tile, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("get_cell_tile_data", "layer", "coords", "use_proxies"), &TileMap::get_cell_tile_data, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_coords_for_body_rid", "body"), &TileMap::get_coords_for_body_rid); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 012bf01df9..ecc6ee1d59 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -343,6 +343,8 @@ public: int get_cell_source_id(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; Vector2i get_cell_atlas_coords(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; int get_cell_alternative_tile(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; + // Helper method to make accessing the data easier. + TileData *get_cell_tile_data(int p_layer, const Vector2i &p_coords, bool p_use_proxies = false) const; // Patterns. Ref<TileMapPattern> get_pattern(int p_layer, TypedArray<Vector2i> p_coords_array); diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp index 712a37e745..ed2bce253a 100644 --- a/scene/3d/label_3d.cpp +++ b/scene/3d/label_3d.cpp @@ -163,7 +163,17 @@ void Label3D::_bind_methods() { } void Label3D::_validate_property(PropertyInfo &property) const { - if (property.name == "material_override" || property.name == "material_overlay") { + if ( + property.name == "material_override" || + property.name == "material_overlay" || + property.name == "lod_bias" || + property.name == "gi_mode" || + property.name == "gi_lightmap_scale") { + property.usage = PROPERTY_USAGE_NO_EDITOR; + } + + if (property.name == "cast_shadow" && alpha_cut == ALPHA_CUT_DISABLED) { + // Alpha-blended materials can't cast shadows. property.usage = PROPERTY_USAGE_NO_EDITOR; } } @@ -889,6 +899,7 @@ void Label3D::set_alpha_cut_mode(AlphaCutMode p_mode) { if (alpha_cut != p_mode) { alpha_cut = p_mode; _queue_update(); + notify_property_list_changed(); } } @@ -927,7 +938,12 @@ Label3D::Label3D() { mesh = RenderingServer::get_singleton()->mesh_create(); + // Disable shadow casting by default to improve performance and avoid unintended visual artifacts. set_cast_shadows_setting(SHADOW_CASTING_SETTING_OFF); + + // Label3D can't contribute to GI in any way, so disable it to improve performance. + set_gi_mode(GI_MODE_DISABLED); + set_base(mesh); } diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp index 1de85d57a3..4ae834cf7a 100644 --- a/scene/3d/node_3d.cpp +++ b/scene/3d/node_3d.cpp @@ -897,7 +897,7 @@ void Node3D::_validate_property(PropertyInfo &property) const { } } -bool Node3D::property_can_revert(const String &p_name) { +bool Node3D::_property_can_revert(const StringName &p_name) const { if (p_name == "basis") { return true; } else if (p_name == "scale") { @@ -912,47 +912,48 @@ bool Node3D::property_can_revert(const String &p_name) { return false; } -Variant Node3D::property_get_revert(const String &p_name) { - Variant r_ret; +bool Node3D::_property_get_revert(const StringName &p_name, Variant &r_property) const { bool valid = false; if (p_name == "basis") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { - r_ret = Transform3D(variant).get_basis(); + r_property = Transform3D(variant).get_basis(); } else { - r_ret = Basis(); + r_property = Basis(); } } else if (p_name == "scale") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { - r_ret = Transform3D(variant).get_basis().get_scale(); + r_property = Transform3D(variant).get_basis().get_scale(); } else { - return Vector3(1.0, 1.0, 1.0); + r_property = Vector3(1.0, 1.0, 1.0); } } else if (p_name == "quaternion") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { - r_ret = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion()); + r_property = Quaternion(Transform3D(variant).get_basis().get_rotation_quaternion()); } else { - return Quaternion(); + r_property = Quaternion(); } } else if (p_name == "rotation") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid && variant.get_type() == Variant::Type::TRANSFORM3D) { - r_ret = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order); + r_property = Transform3D(variant).get_basis().get_euler_normalized(data.euler_rotation_order); } else { - return Vector3(); + r_property = Vector3(); } } else if (p_name == "position") { Variant variant = PropertyUtils::get_property_default_value(this, "transform", &valid); if (valid) { - r_ret = Transform3D(variant).get_origin(); + r_property = Transform3D(variant).get_origin(); } else { - return Vector3(); + r_property = Vector3(); } + } else { + return false; } - return r_ret; + return true; } void Node3D::_bind_methods() { @@ -1032,9 +1033,6 @@ void Node3D::_bind_methods() { ClassDB::bind_method(D_METHOD("to_local", "global_point"), &Node3D::to_local); ClassDB::bind_method(D_METHOD("to_global", "local_point"), &Node3D::to_global); - ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &Node3D::property_can_revert); - ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &Node3D::property_get_revert); - BIND_CONSTANT(NOTIFICATION_TRANSFORM_CHANGED); BIND_CONSTANT(NOTIFICATION_ENTER_WORLD); BIND_CONSTANT(NOTIFICATION_EXIT_WORLD); diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index b1e129798d..3d1c59445b 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -157,8 +157,8 @@ protected: virtual void _validate_property(PropertyInfo &property) const override; - bool property_can_revert(const String &p_name); - Variant property_get_revert(const String &p_name); + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; public: enum { diff --git a/scene/3d/ray_cast_3d.cpp b/scene/3d/ray_cast_3d.cpp index 2db5ab2d4e..a45ef52452 100644 --- a/scene/3d/ray_cast_3d.cpp +++ b/scene/3d/ray_cast_3d.cpp @@ -88,6 +88,10 @@ Object *RayCast3D::get_collider() const { return ObjectDB::get_instance(against); } +RID RayCast3D::get_collider_rid() const { + return against_rid; +} + int RayCast3D::get_collider_shape() const { return against_shape; } @@ -224,12 +228,14 @@ void RayCast3D::_update_raycast_state() { if (dss->intersect_ray(ray_params, rr)) { collided = true; against = rr.collider_id; + against_rid = rr.rid; collision_point = rr.position; collision_normal = rr.normal; against_shape = rr.shape; } else { collided = false; against = ObjectID(); + against_rid = RID(); against_shape = 0; } } @@ -302,6 +308,7 @@ void RayCast3D::_bind_methods() { ClassDB::bind_method(D_METHOD("force_raycast_update"), &RayCast3D::force_raycast_update); ClassDB::bind_method(D_METHOD("get_collider"), &RayCast3D::get_collider); + ClassDB::bind_method(D_METHOD("get_collider_rid"), &RayCast3D::get_collider_rid); ClassDB::bind_method(D_METHOD("get_collider_shape"), &RayCast3D::get_collider_shape); ClassDB::bind_method(D_METHOD("get_collision_point"), &RayCast3D::get_collision_point); ClassDB::bind_method(D_METHOD("get_collision_normal"), &RayCast3D::get_collision_normal); diff --git a/scene/3d/ray_cast_3d.h b/scene/3d/ray_cast_3d.h index aa62f6927e..eb5c3ee90a 100644 --- a/scene/3d/ray_cast_3d.h +++ b/scene/3d/ray_cast_3d.h @@ -41,6 +41,7 @@ class RayCast3D : public Node3D { bool enabled = true; bool collided = false; ObjectID against; + RID against_rid; int against_shape = 0; Vector3 collision_point; Vector3 collision_normal; @@ -113,6 +114,7 @@ public: void force_raycast_update(); bool is_colliding() const; Object *get_collider() const; + RID get_collider_rid() const; int get_collider_shape() const; Vector3 get_collision_point() const; Vector3 get_collision_normal() const; diff --git a/scene/3d/velocity_tracker_3d.h b/scene/3d/velocity_tracker_3d.h index 6b27cdffc2..d3b92ab766 100644 --- a/scene/3d/velocity_tracker_3d.h +++ b/scene/3d/velocity_tracker_3d.h @@ -34,8 +34,6 @@ #include "scene/3d/node_3d.h" class VelocityTracker3D : public RefCounted { - GDCLASS(VelocityTracker3D, RefCounted); - struct PositionHistory { uint64_t frame = 0; Vector3 position; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 09efee71a3..8d3a61b52e 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -2381,7 +2381,7 @@ void GraphEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("begin_node_move")); ADD_SIGNAL(MethodInfo("end_node_move")); ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset"))); - ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::STRING, "slot"), PropertyInfo(Variant::BOOL, "is_output"))); + ADD_SIGNAL(MethodInfo("connection_drag_started", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "slot"), PropertyInfo(Variant::BOOL, "is_output"))); ADD_SIGNAL(MethodInfo("connection_drag_ended")); BIND_ENUM_CONSTANT(SCROLL_ZOOMS); diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index e7f48beb00..4ef1e48a32 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -764,13 +764,17 @@ int Label::get_visible_characters() const { void Label::set_percent_visible(float p_percent) { if (percent_visible != p_percent) { - if (p_percent < 0 || p_percent >= 1) { + if (percent_visible >= 1.0) { visible_chars = -1; - percent_visible = 1; + percent_visible = 1.0; + } else if (percent_visible < 0.0) { + visible_chars = 0; + percent_visible = 0.0; } else { visible_chars = get_total_character_count() * p_percent; percent_visible = p_percent; } + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { dirty = true; } diff --git a/scene/gui/menu_bar.cpp b/scene/gui/menu_bar.cpp new file mode 100644 index 0000000000..ebe833c6e0 --- /dev/null +++ b/scene/gui/menu_bar.cpp @@ -0,0 +1,866 @@ +/*************************************************************************/ +/* menu_bar.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 "menu_bar.h" + +#include "core/os/keyboard.h" +#include "scene/main/window.h" + +void MenuBar::gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + if (is_native_menu()) { + // Handled by OS. + return; + } + + MutexLock lock(mutex); + if (p_event->is_action("ui_left") && p_event->is_pressed()) { + int new_sel = selected_menu; + int old_sel = (selected_menu < 0) ? 0 : selected_menu; + do { + new_sel--; + if (new_sel < 0) { + new_sel = menu_cache.size() - 1; + } + if (old_sel == new_sel) { + return; + } + } while (menu_cache[new_sel].hidden || menu_cache[new_sel].disabled); + + if (selected_menu != new_sel) { + selected_menu = new_sel; + focused_menu = selected_menu; + if (active_menu >= 0) { + get_menu_popup(active_menu)->hide(); + } + _open_popup(selected_menu); + } + return; + } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { + int new_sel = selected_menu; + int old_sel = (selected_menu < 0) ? menu_cache.size() - 1 : selected_menu; + do { + new_sel++; + if (new_sel >= menu_cache.size()) { + new_sel = 0; + } + if (old_sel == new_sel) { + return; + } + } while (menu_cache[new_sel].hidden || menu_cache[new_sel].disabled); + + if (selected_menu != new_sel) { + selected_menu = new_sel; + focused_menu = selected_menu; + if (active_menu >= 0) { + get_menu_popup(active_menu)->hide(); + } + _open_popup(selected_menu); + } + return; + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + int old_sel = selected_menu; + focused_menu = _get_index_at_point(mm->get_position()); + if (focused_menu >= 0) { + selected_menu = focused_menu; + } + if (selected_menu != old_sel) { + update(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->is_pressed() && (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT)) { + int index = _get_index_at_point(mb->get_position()); + if (index >= 0) { + _open_popup(index); + } + } + } +} + +void MenuBar::_open_popup(int p_index) { + ERR_FAIL_INDEX(p_index, menu_cache.size()); + + PopupMenu *pm = get_menu_popup(p_index); + if (pm->is_visible()) { + pm->hide(); + return; + } + + Rect2 item_rect = _get_menu_item_rect(p_index); + Point2 screen_pos = get_screen_position() + item_rect.position * get_viewport()->get_canvas_transform().get_scale(); + Size2 screen_size = item_rect.size * get_viewport()->get_canvas_transform().get_scale(); + + active_menu = p_index; + + pm->set_size(Size2(screen_size.x, 0)); + screen_pos.y += screen_size.y; + if (is_layout_rtl()) { + screen_pos.x += screen_size.x - pm->get_size().width; + } + pm->set_position(screen_pos); + pm->set_parent_rect(Rect2(Point2(screen_pos - pm->get_position()), Size2(screen_size.x, screen_pos.y))); + pm->popup(); + + update(); +} + +void MenuBar::shortcut_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + + if (is_native_menu()) { + return; + } + + if (!_is_focus_owner_in_shortcut_context()) { + return; + } + + if (disable_shortcuts) { + return; + } + + if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event) || Object::cast_to<InputEventShortcut>(*p_event))) { + if (!get_parent() || !is_visible_in_tree()) { + return; + } + + Vector<PopupMenu *> popups = _get_popups(); + for (int i = 0; i < popups.size(); i++) { + if (menu_cache[i].hidden || menu_cache[i].disabled) { + continue; + } + if (popups[i]->activate_item_by_event(p_event, false)) { + accept_event(); + return; + } + } + } +} + +void MenuBar::set_shortcut_context(Node *p_node) { + if (p_node != nullptr) { + shortcut_context = p_node->get_instance_id(); + } else { + shortcut_context = ObjectID(); + } +} + +Node *MenuBar::get_shortcut_context() const { + Object *ctx_obj = ObjectDB::get_instance(shortcut_context); + Node *ctx_node = Object::cast_to<Node>(ctx_obj); + + return ctx_node; +} + +bool MenuBar::_is_focus_owner_in_shortcut_context() const { + if (shortcut_context == ObjectID()) { + // No context, therefore global - always "in" context. + return true; + } + + Node *ctx_node = get_shortcut_context(); + Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr; + + // If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it. + return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus)); +} + +void MenuBar::_popup_visibility_changed(bool p_visible) { + if (!p_visible) { + active_menu = -1; + focused_menu = -1; + set_process_internal(false); + update(); + return; + } + + if (switch_on_hover) { + Window *window = Object::cast_to<Window>(get_viewport()); + if (window) { + mouse_pos_adjusted = window->get_position(); + + if (window->is_embedded()) { + Window *window_parent = Object::cast_to<Window>(window->get_parent()->get_viewport()); + while (window_parent) { + if (!window_parent->is_embedded()) { + mouse_pos_adjusted += window_parent->get_position(); + break; + } + + window_parent = Object::cast_to<Window>(window_parent->get_parent()->get_viewport()); + } + } + + set_process_internal(true); + } + } +} + +void MenuBar::_update_submenu(const String &p_menu_name, PopupMenu *p_child) { + int count = p_child->get_item_count(); + global_menus.insert(p_menu_name); + for (int i = 0; i < count; i++) { + if (p_child->is_item_separator(i)) { + DisplayServer::get_singleton()->global_menu_add_separator(p_menu_name); + } else if (!p_child->get_item_submenu(i).is_empty()) { + Node *n = p_child->get_node(p_child->get_item_submenu(i)); + ERR_FAIL_COND_MSG(!n, "Item subnode does not exist: " + p_child->get_item_submenu(i) + "."); + PopupMenu *pm = Object::cast_to<PopupMenu>(n); + ERR_FAIL_COND_MSG(!pm, "Item subnode is not a PopupMenu: " + p_child->get_item_submenu(i) + "."); + + DisplayServer::get_singleton()->global_menu_add_submenu_item(p_menu_name, p_child->get_item_text(i), p_menu_name + "/" + itos(i)); + _update_submenu(p_menu_name + "/" + itos(i), pm); + } else { + int index = DisplayServer::get_singleton()->global_menu_add_item(p_menu_name, p_child->get_item_text(i), callable_mp(p_child, &PopupMenu::activate_item), i); + + if (p_child->is_item_checkable(i)) { + DisplayServer::get_singleton()->global_menu_set_item_checkable(p_menu_name, index, true); + } + if (p_child->is_item_radio_checkable(i)) { + DisplayServer::get_singleton()->global_menu_set_item_radio_checkable(p_menu_name, index, true); + } + DisplayServer::get_singleton()->global_menu_set_item_checked(p_menu_name, index, p_child->is_item_checked(i)); + DisplayServer::get_singleton()->global_menu_set_item_disabled(p_menu_name, index, p_child->is_item_disabled(i)); + DisplayServer::get_singleton()->global_menu_set_item_max_states(p_menu_name, index, p_child->get_item_max_states(i)); + DisplayServer::get_singleton()->global_menu_set_item_icon(p_menu_name, index, p_child->get_item_icon(i)); + DisplayServer::get_singleton()->global_menu_set_item_state(p_menu_name, index, p_child->get_item_state(i)); + DisplayServer::get_singleton()->global_menu_set_item_indentation_level(p_menu_name, index, p_child->get_item_indent(i)); + DisplayServer::get_singleton()->global_menu_set_item_tooltip(p_menu_name, index, p_child->get_item_tooltip(i)); + if (!p_child->is_item_shortcut_disabled(i) && p_child->get_item_shortcut(i).is_valid() && p_child->get_item_shortcut(i)->has_valid_event()) { + Array events = p_child->get_item_shortcut(i)->get_events(); + for (int j = 0; j < events.size(); j++) { + Ref<InputEventKey> ie = events[j]; + if (ie.is_valid()) { + DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, index, ie->get_keycode_with_modifiers()); + break; + } + } + } else if (p_child->get_item_accelerator(i) != Key::NONE) { + DisplayServer::get_singleton()->global_menu_set_item_accelerator(p_menu_name, i, p_child->get_item_accelerator(i)); + } + } + } +} + +bool MenuBar::is_native_menu() const { + if (!is_visible_in_tree()) { + return false; + } + if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && (get_tree()->get_edited_scene_root()->is_ancestor_of(this) || get_tree()->get_edited_scene_root() == this)) { + return false; + } + + return (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU) && is_native); +} + +void MenuBar::_clear_menu() { + if (!DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_GLOBAL_MENU)) { + return; + } + + // Remove root menu items. + int count = DisplayServer::get_singleton()->global_menu_get_item_count("_main"); + for (int i = count - 1; i >= 0; i--) { + if (global_menus.has(DisplayServer::get_singleton()->global_menu_get_item_submenu("_main", i))) { + DisplayServer::get_singleton()->global_menu_remove_item("_main", i); + } + } + // Erase submenu contents. + for (const String &E : global_menus) { + DisplayServer::get_singleton()->global_menu_clear(E); + } + global_menus.clear(); +} + +void MenuBar::_update_menu() { + _clear_menu(); + + if (!is_inside_tree()) { + return; + } + + int index = start_index; + if (is_native_menu()) { + Vector<PopupMenu *> popups = _get_popups(); + String root_name = "MenuBar<" + String::num_int64((uint64_t)this, 16) + ">"; + for (int i = 0; i < popups.size(); i++) { + if (menu_cache[i].hidden) { + continue; + } + String menu_name = String(popups[i]->get_meta("_menu_name", popups[i]->get_name())); + + index = DisplayServer::get_singleton()->global_menu_add_submenu_item("_main", menu_name, root_name + "/" + itos(i), index); + if (menu_cache[i].disabled) { + DisplayServer::get_singleton()->global_menu_set_item_disabled("_main", index, true); + } + _update_submenu(root_name + "/" + itos(i), popups[i]); + index++; + } + } + update_minimum_size(); + update(); +} + +void MenuBar::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (get_menu_count() > 0) { + _refresh_menu_names(); + } + } break; + case NOTIFICATION_EXIT_TREE: { + _clear_menu(); + } break; + case NOTIFICATION_MOUSE_EXIT: { + focused_menu = -1; + update(); + } break; + case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_THEME_CHANGED: { + for (int i = 0; i < menu_cache.size(); i++) { + shape(menu_cache.write[i]); + } + _update_menu(); + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + _update_menu(); + } break; + case NOTIFICATION_DRAW: { + if (is_native_menu()) { + return; + } + for (int i = 0; i < menu_cache.size(); i++) { + _draw_menu_item(i); + } + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + MutexLock lock(mutex); + + Vector2 pos = DisplayServer::get_singleton()->mouse_get_position() - mouse_pos_adjusted - get_global_position(); + int index = _get_index_at_point(pos); + if (index >= 0 && index != active_menu) { + selected_menu = index; + focused_menu = selected_menu; + get_menu_popup(active_menu)->hide(); + _open_popup(index); + } + } break; + } +} + +int MenuBar::_get_index_at_point(const Point2 &p_point) const { + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + int hsep = get_theme_constant(SNAME("h_separation")); + int offset = 0; + for (int i = 0; i < menu_cache.size(); i++) { + if (menu_cache[i].hidden) { + continue; + } + Size2 size = menu_cache[i].text_buf->get_size() + style->get_minimum_size(); + if (p_point.x > offset && p_point.x < offset + size.x) { + if (p_point.y > 0 && p_point.y < size.y) { + return i; + } + } + offset += size.x + hsep; + } + return -1; +} + +Rect2 MenuBar::_get_menu_item_rect(int p_index) const { + ERR_FAIL_INDEX_V(p_index, menu_cache.size(), Rect2()); + + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + int hsep = get_theme_constant(SNAME("h_separation")); + + int offset = 0; + for (int i = 0; i < p_index; i++) { + if (menu_cache[i].hidden) { + continue; + } + Size2 size = menu_cache[i].text_buf->get_size() + style->get_minimum_size(); + offset += size.x + hsep; + } + + return Rect2(Point2(offset, 0), menu_cache[p_index].text_buf->get_size() + style->get_minimum_size()); +} + +void MenuBar::_draw_menu_item(int p_index) { + ERR_FAIL_INDEX(p_index, menu_cache.size()); + + RID ci = get_canvas_item(); + bool hovered = (focused_menu == p_index); + bool pressed = (active_menu == p_index); + bool rtl = is_layout_rtl(); + + if (menu_cache[p_index].hidden) { + return; + } + + Color color; + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + Rect2 item_rect = _get_menu_item_rect(p_index); + + if (menu_cache[p_index].disabled) { + if (rtl && has_theme_stylebox(SNAME("disabled_mirrored"))) { + style = get_theme_stylebox(SNAME("disabled_mirrored")); + } else { + style = get_theme_stylebox(SNAME("disabled")); + } + if (!flat) { + style->draw(ci, item_rect); + } + color = get_theme_color(SNAME("font_disabled_color")); + } else if (hovered && pressed && has_theme_stylebox("hover_pressed")) { + if (rtl && has_theme_stylebox(SNAME("hover_pressed_mirrored"))) { + style = get_theme_stylebox(SNAME("hover_pressed_mirrored")); + } else { + style = get_theme_stylebox(SNAME("hover_pressed")); + } + if (!flat) { + style->draw(ci, item_rect); + } + if (has_theme_color(SNAME("font_hover_pressed_color"))) { + color = get_theme_color(SNAME("font_hover_pressed_color")); + } + } else if (pressed) { + if (rtl && has_theme_stylebox(SNAME("pressed_mirrored"))) { + style = get_theme_stylebox(SNAME("pressed_mirrored")); + } else { + style = get_theme_stylebox(SNAME("pressed")); + } + if (!flat) { + style->draw(ci, item_rect); + } + if (has_theme_color(SNAME("font_pressed_color"))) { + color = get_theme_color(SNAME("font_pressed_color")); + } else { + color = get_theme_color(SNAME("font_color")); + } + } else if (hovered) { + if (rtl && has_theme_stylebox(SNAME("hover_mirrored"))) { + style = get_theme_stylebox(SNAME("hover_mirrored")); + } else { + style = get_theme_stylebox(SNAME("hover")); + } + if (!flat) { + style->draw(ci, item_rect); + } + color = get_theme_color(SNAME("font_hover_color")); + } else { + if (rtl && has_theme_stylebox(SNAME("normal_mirrored"))) { + style = get_theme_stylebox(SNAME("normal_mirrored")); + } else { + style = get_theme_stylebox(SNAME("normal")); + } + if (!flat) { + style->draw(ci, item_rect); + } + // Focus colors only take precedence over normal state. + if (has_focus()) { + color = get_theme_color(SNAME("font_focus_color")); + } else { + color = get_theme_color(SNAME("font_color")); + } + } + + Point2 text_ofs = item_rect.position + Point2(style->get_margin(SIDE_LEFT), style->get_margin(SIDE_TOP)); + + Color font_outline_color = get_theme_color(SNAME("font_outline_color")); + int outline_size = get_theme_constant(SNAME("outline_size")); + if (outline_size > 0 && font_outline_color.a > 0) { + menu_cache[p_index].text_buf->draw_outline(ci, text_ofs, outline_size, font_outline_color); + } + menu_cache[p_index].text_buf->draw(ci, text_ofs, color); +} + +void MenuBar::shape(Menu &p_menu) { + Ref<Font> font = get_theme_font(SNAME("font")); + int font_size = get_theme_font_size(SNAME("font_size")); + + p_menu.text_buf->clear(); + if (text_direction == Control::TEXT_DIRECTION_INHERITED) { + p_menu.text_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); + } else { + p_menu.text_buf->set_direction((TextServer::Direction)text_direction); + } + p_menu.text_buf->add_string(p_menu.name, font, font_size, language); +} + +void MenuBar::_refresh_menu_names() { + Vector<PopupMenu *> popups = _get_popups(); + for (int i = 0; i < popups.size(); i++) { + if (!popups[i]->has_meta("_menu_name") && String(popups[i]->get_name()) != get_menu_title(i)) { + menu_cache.write[i].name = popups[i]->get_name(); + shape(menu_cache.write[i]); + } + } + _update_menu(); +} + +Vector<PopupMenu *> MenuBar::_get_popups() const { + Vector<PopupMenu *> popups; + for (int i = 0; i < get_child_count(); i++) { + PopupMenu *pm = Object::cast_to<PopupMenu>(get_child(i)); + if (!pm) { + continue; + } + popups.push_back(pm); + } + return popups; +} + +int MenuBar::get_menu_idx_from_control(PopupMenu *p_child) const { + ERR_FAIL_NULL_V(p_child, -1); + ERR_FAIL_COND_V(p_child->get_parent() != this, -1); + + Vector<PopupMenu *> popups = _get_popups(); + for (int i = 0; i < popups.size(); i++) { + if (popups[i] == p_child) { + return i; + } + } + + return -1; +} + +void MenuBar::add_child_notify(Node *p_child) { + Control::add_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + Menu menu = Menu(p_child->get_name()); + shape(menu); + + menu_cache.push_back(menu); + p_child->connect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names)); + p_child->connect("menu_changed", callable_mp(this, &MenuBar::_update_menu)); + p_child->connect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(true)); + p_child->connect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed).bind(false)); + + _update_menu(); +} + +void MenuBar::move_child_notify(Node *p_child) { + Control::move_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + + int old_idx = -1; + String menu_name = String(pm->get_meta("_menu_name", pm->get_name())); + // Find the previous menu index of the control. + for (int i = 0; i < get_menu_count(); i++) { + if (get_menu_title(i) == menu_name) { + old_idx = i; + break; + } + } + Menu menu = menu_cache[old_idx]; + menu_cache.remove_at(old_idx); + menu_cache.insert(get_menu_idx_from_control(pm), menu); + + _update_menu(); +} + +void MenuBar::remove_child_notify(Node *p_child) { + Control::remove_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + + int idx = get_menu_idx_from_control(pm); + + menu_cache.remove_at(idx); + + p_child->remove_meta("_menu_name"); + p_child->remove_meta("_menu_tooltip"); + + p_child->disconnect("renamed", callable_mp(this, &MenuBar::_refresh_menu_names)); + p_child->disconnect("menu_changed", callable_mp(this, &MenuBar::_update_menu)); + p_child->disconnect("about_to_popup", callable_mp(this, &MenuBar::_popup_visibility_changed)); + p_child->disconnect("popup_hide", callable_mp(this, &MenuBar::_popup_visibility_changed)); + + _update_menu(); +} + +void MenuBar::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_switch_on_hover", "enable"), &MenuBar::set_switch_on_hover); + ClassDB::bind_method(D_METHOD("is_switch_on_hover"), &MenuBar::is_switch_on_hover); + ClassDB::bind_method(D_METHOD("set_disable_shortcuts", "disabled"), &MenuBar::set_disable_shortcuts); + + ClassDB::bind_method(D_METHOD("set_prefer_global_menu", "enabled"), &MenuBar::set_prefer_global_menu); + ClassDB::bind_method(D_METHOD("is_prefer_global_menu"), &MenuBar::is_prefer_global_menu); + ClassDB::bind_method(D_METHOD("is_native_menu"), &MenuBar::is_native_menu); + + ClassDB::bind_method(D_METHOD("get_menu_count"), &MenuBar::get_menu_count); + + ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &MenuBar::set_text_direction); + ClassDB::bind_method(D_METHOD("get_text_direction"), &MenuBar::get_text_direction); + ClassDB::bind_method(D_METHOD("set_language", "language"), &MenuBar::set_language); + ClassDB::bind_method(D_METHOD("get_language"), &MenuBar::get_language); + ClassDB::bind_method(D_METHOD("set_flat", "enabled"), &MenuBar::set_flat); + ClassDB::bind_method(D_METHOD("is_flat"), &MenuBar::is_flat); + ClassDB::bind_method(D_METHOD("set_start_index", "enabled"), &MenuBar::set_start_index); + ClassDB::bind_method(D_METHOD("get_start_index"), &MenuBar::get_start_index); + + ClassDB::bind_method(D_METHOD("set_menu_title", "menu", "title"), &MenuBar::set_menu_title); + ClassDB::bind_method(D_METHOD("get_menu_title", "menu"), &MenuBar::get_menu_title); + + ClassDB::bind_method(D_METHOD("set_menu_tooltip", "menu", "tooltip"), &MenuBar::set_menu_tooltip); + ClassDB::bind_method(D_METHOD("get_menu_tooltip", "menu"), &MenuBar::get_menu_tooltip); + + ClassDB::bind_method(D_METHOD("set_menu_disabled", "menu", "disabled"), &MenuBar::set_menu_disabled); + ClassDB::bind_method(D_METHOD("is_menu_disabled", "menu"), &MenuBar::is_menu_disabled); + + ClassDB::bind_method(D_METHOD("set_menu_hidden", "menu", "hidden"), &MenuBar::set_menu_hidden); + ClassDB::bind_method(D_METHOD("is_menu_hidden", "menu"), &MenuBar::is_menu_hidden); + + ClassDB::bind_method(D_METHOD("set_shortcut_context", "node"), &MenuBar::set_shortcut_context); + ClassDB::bind_method(D_METHOD("get_shortcut_context"), &MenuBar::get_shortcut_context); + + ClassDB::bind_method(D_METHOD("get_menu_popup", "menu"), &MenuBar::get_menu_popup); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flat"), "set_flat", "is_flat"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "start_index"), "set_start_index", "get_start_index"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "switch_on_hover"), "set_switch_on_hover", "is_switch_on_hover"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "prefer_global_menu"), "set_prefer_global_menu", "is_prefer_global_menu"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shortcut_context", PROPERTY_HINT_RESOURCE_TYPE, "Node"), "set_shortcut_context", "get_shortcut_context"); + + ADD_GROUP("BiDi", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); +} + +void MenuBar::set_switch_on_hover(bool p_enabled) { + switch_on_hover = p_enabled; +} + +bool MenuBar::is_switch_on_hover() { + return switch_on_hover; +} + +void MenuBar::set_disable_shortcuts(bool p_disabled) { + disable_shortcuts = p_disabled; +} + +void MenuBar::set_text_direction(Control::TextDirection p_text_direction) { + ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); + if (text_direction != p_text_direction) { + text_direction = p_text_direction; + _update_menu(); + } +} + +Control::TextDirection MenuBar::get_text_direction() const { + return text_direction; +} + +void MenuBar::set_language(const String &p_language) { + if (language != p_language) { + language = p_language; + _update_menu(); + } +} + +String MenuBar::get_language() const { + return language; +} + +void MenuBar::set_flat(bool p_enabled) { + if (flat != p_enabled) { + flat = p_enabled; + update(); + } +} + +bool MenuBar::is_flat() const { + return flat; +} + +void MenuBar::set_start_index(int p_index) { + if (start_index != p_index) { + start_index = p_index; + _update_menu(); + } +} + +int MenuBar::get_start_index() const { + return start_index; +} + +void MenuBar::set_prefer_global_menu(bool p_enabled) { + if (is_native != p_enabled) { + if (is_native) { + _clear_menu(); + } + is_native = p_enabled; + _update_menu(); + } +} + +bool MenuBar::is_prefer_global_menu() const { + return is_native; +} + +Size2 MenuBar::get_minimum_size() const { + if (is_native_menu()) { + return Size2(); + } + + Ref<StyleBox> style = get_theme_stylebox(SNAME("normal")); + int hsep = get_theme_constant(SNAME("h_separation")); + + Vector2 size; + for (int i = 0; i < menu_cache.size(); i++) { + if (menu_cache[i].hidden) { + continue; + } + Size2 sz = menu_cache[i].text_buf->get_size() + style->get_minimum_size(); + size.y = MAX(size.y, sz.y); + size.x += sz.x + hsep; + } + return size; +} + +int MenuBar::get_menu_count() const { + return menu_cache.size(); +} + +void MenuBar::set_menu_title(int p_menu, const String &p_title) { + ERR_FAIL_INDEX(p_menu, menu_cache.size()); + PopupMenu *pm = get_menu_popup(p_menu); + if (p_title == pm->get_name()) { + pm->remove_meta("_menu_name"); + } else { + pm->set_meta("_menu_name", p_title); + } + menu_cache.write[p_menu].name = p_title; + shape(menu_cache.write[p_menu]); + _update_menu(); +} + +String MenuBar::get_menu_title(int p_menu) const { + ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), String()); + return menu_cache[p_menu].name; +} + +void MenuBar::set_menu_tooltip(int p_menu, const String &p_tooltip) { + ERR_FAIL_INDEX(p_menu, menu_cache.size()); + PopupMenu *pm = get_menu_popup(p_menu); + pm->set_meta("_menu_tooltip", p_tooltip); + menu_cache.write[p_menu].name = p_tooltip; +} + +String MenuBar::get_menu_tooltip(int p_menu) const { + ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), String()); + return menu_cache[p_menu].tooltip; +} + +void MenuBar::set_menu_disabled(int p_menu, bool p_disabled) { + ERR_FAIL_INDEX(p_menu, menu_cache.size()); + menu_cache.write[p_menu].disabled = p_disabled; + _update_menu(); +} + +bool MenuBar::is_menu_disabled(int p_menu) const { + ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), false); + return menu_cache[p_menu].disabled; +} + +void MenuBar::set_menu_hidden(int p_menu, bool p_hidden) { + ERR_FAIL_INDEX(p_menu, menu_cache.size()); + menu_cache.write[p_menu].hidden = p_hidden; + _update_menu(); +} + +bool MenuBar::is_menu_hidden(int p_menu) const { + ERR_FAIL_INDEX_V(p_menu, menu_cache.size(), false); + return menu_cache[p_menu].hidden; +} + +PopupMenu *MenuBar::get_menu_popup(int p_idx) const { + Vector<PopupMenu *> controls = _get_popups(); + if (p_idx >= 0 && p_idx < controls.size()) { + return controls[p_idx]; + } else { + return nullptr; + } +} + +String MenuBar::get_tooltip(const Point2 &p_pos) const { + int index = _get_index_at_point(p_pos); + if (index >= 0 && index < menu_cache.size()) { + return menu_cache[index].tooltip; + } else { + return String(); + } +} + +void MenuBar::get_translatable_strings(List<String> *p_strings) const { + Vector<PopupMenu *> popups = _get_popups(); + for (int i = 0; i < popups.size(); i++) { + PopupMenu *pm = popups[i]; + + if (!pm->has_meta("_menu_name") && !pm->has_meta("_menu_tooltip")) { + continue; + } + + String name = pm->get_meta("_menu_name"); + if (!name.is_empty()) { + p_strings->push_back(name); + } + + String tooltip = pm->get_meta("_menu_tooltip"); + if (!tooltip.is_empty()) { + p_strings->push_back(tooltip); + } + } +} + +MenuBar::MenuBar() { + set_process_shortcut_input(true); +} + +MenuBar::~MenuBar() { +} diff --git a/scene/gui/menu_bar.h b/scene/gui/menu_bar.h new file mode 100644 index 0000000000..3c4a25fd06 --- /dev/null +++ b/scene/gui/menu_bar.h @@ -0,0 +1,156 @@ +/*************************************************************************/ +/* menu_bar.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 MENU_BAR_H +#define MENU_BAR_H + +#include "scene/gui/button.h" +#include "scene/gui/popup_menu.h" + +class MenuBar : public Control { + GDCLASS(MenuBar, Control); + + Mutex mutex; + + bool switch_on_hover = true; + bool disable_shortcuts = false; + bool is_native = true; + bool flat = false; + int start_index = -1; + + String language; + TextDirection text_direction = TEXT_DIRECTION_AUTO; + + struct Menu { + String name; + String tooltip; + + Ref<TextLine> text_buf; + bool hidden = false; + bool disabled = false; + + Menu(const String &p_name) { + name = p_name; + text_buf.instantiate(); + } + + Menu() { + text_buf.instantiate(); + } + }; + Vector<Menu> menu_cache; + HashSet<String> global_menus; + + int focused_menu = -1; + int selected_menu = -1; + int active_menu = -1; + + Vector2i mouse_pos_adjusted; + ObjectID shortcut_context; + + int _get_index_at_point(const Point2 &p_point) const; + Rect2 _get_menu_item_rect(int p_index) const; + void _draw_menu_item(int p_index); + + void shape(Menu &p_menu); + void _refresh_menu_names(); + Vector<PopupMenu *> _get_popups() const; + int get_menu_idx_from_control(PopupMenu *p_child) const; + + void _open_popup(int p_index); + void _popup_visibility_changed(bool p_visible); + void _update_submenu(const String &p_menu_name, PopupMenu *p_child); + void _clear_menu(); + void _update_menu(); + + bool _is_focus_owner_in_shortcut_context() const; + +protected: + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; + + void _notification(int p_what); + virtual void add_child_notify(Node *p_child) override; + virtual void move_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; + static void _bind_methods(); + +public: + virtual void gui_input(const Ref<InputEvent> &p_event) override; + + void set_switch_on_hover(bool p_enabled); + bool is_switch_on_hover(); + void set_disable_shortcuts(bool p_disabled); + + void set_prefer_global_menu(bool p_enabled); + bool is_prefer_global_menu() const; + + bool is_native_menu() const; + + virtual Size2 get_minimum_size() const override; + + int get_menu_count() const; + + void set_text_direction(TextDirection p_text_direction); + TextDirection get_text_direction() const; + + void set_language(const String &p_language); + String get_language() const; + + void set_start_index(int p_index); + int get_start_index() const; + + void set_flat(bool p_enabled); + bool is_flat() const; + + void set_menu_title(int p_menu, const String &p_title); + String get_menu_title(int p_menu) const; + + void set_menu_tooltip(int p_menu, const String &p_tooltip); + String get_menu_tooltip(int p_menu) const; + + void set_menu_disabled(int p_menu, bool p_disabled); + bool is_menu_disabled(int p_menu) const; + + void set_menu_hidden(int p_menu, bool p_hidden); + bool is_menu_hidden(int p_menu) const; + + void set_shortcut_context(Node *p_node); + Node *get_shortcut_context() const; + + PopupMenu *get_menu_popup(int p_menu) const; + + virtual void get_translatable_strings(List<String> *p_strings) const override; + virtual String get_tooltip(const Point2 &p_pos) const override; + + MenuBar(); + ~MenuBar(); +}; + +#endif // MENU_BAR_H diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp index 069a31d9d2..0252f25888 100644 --- a/scene/gui/menu_button.cpp +++ b/scene/gui/menu_button.cpp @@ -105,7 +105,11 @@ void MenuButton::pressed() { popup->set_current_index(0); } - popup->popup(); + if (popup->is_visible()) { + popup->hide(); + } else { + popup->popup(); + } } void MenuButton::gui_input(const Ref<InputEvent> &p_event) { diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index cd0d437051..6ef8158302 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -36,6 +36,7 @@ #include "core/os/os.h" #include "core/string/print_string.h" #include "core/string/translation.h" +#include "scene/gui/menu_bar.h" String PopupMenu::_get_accel_text(const Item &p_item) const { if (p_item.shortcut.is_valid()) { @@ -66,7 +67,7 @@ Size2 PopupMenu::_get_contents_minimum_size() const { size.height = _get_item_height(i); icon_w = MAX(icon_size.width, icon_w); - size.width += items[i].h_ofs; + size.width += items[i].indent * get_theme_constant(SNAME("indent")); if (items[i].checkable_type && !items[i].separator) { has_check = true; @@ -343,14 +344,27 @@ void PopupMenu::gui_input(const Ref<InputEvent> &p_event) { } } else if (p_event->is_action("ui_left") && p_event->is_pressed()) { Node *n = get_parent(); - if (n && Object::cast_to<PopupMenu>(n)) { - hide(); - set_input_as_handled(); + if (n) { + if (Object::cast_to<PopupMenu>(n)) { + hide(); + set_input_as_handled(); + } else if (Object::cast_to<MenuBar>(n)) { + Object::cast_to<MenuBar>(n)->gui_input(p_event); + set_input_as_handled(); + return; + } } } else if (p_event->is_action("ui_right") && p_event->is_pressed()) { if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator && !items[mouse_over].submenu.is_empty() && submenu_over != mouse_over) { _activate_submenu(mouse_over, true); set_input_as_handled(); + } else { + Node *n = get_parent(); + if (n && Object::cast_to<MenuBar>(n)) { + Object::cast_to<MenuBar>(n)->gui_input(p_event); + set_input_as_handled(); + return; + } } } else if (p_event->is_action("ui_accept") && p_event->is_pressed()) { if (mouse_over >= 0 && mouse_over < items.size() && !items[mouse_over].separator) { @@ -589,7 +603,7 @@ void PopupMenu::_draw_items() { String text = items[i].xl_text; // Separator - item_ofs.x += items[i].h_ofs; + item_ofs.x += items[i].indent * get_theme_constant(SNAME("indent")); if (items[i].separator) { if (!text.is_empty() || !items[i].icon.is_null()) { int content_size = items[i].text_buf->get_size().width + hseparation * 2; @@ -774,6 +788,32 @@ void PopupMenu::_shape_item(int p_item) { } } +void PopupMenu::_menu_changed() { + emit_signal(SNAME("menu_changed")); +} + +void PopupMenu::add_child_notify(Node *p_child) { + Window::add_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + p_child->connect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed)); + _menu_changed(); +} + +void PopupMenu::remove_child_notify(Node *p_child) { + Window::remove_child_notify(p_child); + + PopupMenu *pm = Object::cast_to<PopupMenu>(p_child); + if (!pm) { + return; + } + p_child->disconnect("menu_changed", callable_mp(this, &PopupMenu::_menu_changed)); + _menu_changed(); +} + void PopupMenu::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -795,6 +835,7 @@ void PopupMenu::_notification(int p_what) { } child_controls_changed(); + _menu_changed(); control->update(); } break; @@ -889,6 +930,7 @@ void PopupMenu::add_item(const String &p_label, int p_id, Key p_accel) { control->update(); child_controls_changed(); notify_property_list_changed(); + _menu_changed(); } void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { @@ -900,6 +942,7 @@ void PopupMenu::add_icon_item(const Ref<Texture2D> &p_icon, const String &p_labe control->update(); child_controls_changed(); notify_property_list_changed(); + _menu_changed(); } void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) { @@ -910,6 +953,7 @@ void PopupMenu::add_check_item(const String &p_label, int p_id, Key p_accel) { _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { @@ -931,6 +975,7 @@ void PopupMenu::add_radio_check_item(const String &p_label, int p_id, Key p_acce _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const String &p_label, int p_id, Key p_accel) { @@ -942,6 +987,7 @@ void PopupMenu::add_icon_radio_check_item(const Ref<Texture2D> &p_icon, const St _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int p_default_state, int p_id, Key p_accel) { @@ -953,6 +999,7 @@ void PopupMenu::add_multistate_item(const String &p_label, int p_max_states, int _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } #define ITEM_SETUP_WITH_SHORTCUT(p_shortcut, p_id, p_global) \ @@ -971,6 +1018,7 @@ void PopupMenu::add_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_g _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -981,6 +1029,7 @@ void PopupMenu::add_icon_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortc _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -991,6 +1040,7 @@ void PopupMenu::add_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bo _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -1002,6 +1052,7 @@ void PopupMenu::add_icon_check_shortcut(const Ref<Texture2D> &p_icon, const Ref< _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -1012,6 +1063,7 @@ void PopupMenu::add_radio_check_shortcut(const Ref<Shortcut> &p_shortcut, int p_ _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, const Ref<Shortcut> &p_shortcut, int p_id, bool p_global) { @@ -1023,6 +1075,7 @@ void PopupMenu::add_icon_radio_check_shortcut(const Ref<Texture2D> &p_icon, cons _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, int p_id) { @@ -1035,6 +1088,7 @@ void PopupMenu::add_submenu_item(const String &p_label, const String &p_submenu, _shape_item(items.size() - 1); control->update(); child_controls_changed(); + _menu_changed(); } #undef ITEM_SETUP_WITH_ACCEL @@ -1057,6 +1111,7 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) { control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_text_direction(int p_item, Control::TextDirection p_text_direction) { @@ -1093,6 +1148,7 @@ void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) { control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_checked(int p_idx, bool p_checked) { @@ -1105,6 +1161,7 @@ void PopupMenu::set_item_checked(int p_idx, bool p_checked) { control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_id(int p_idx, int p_id) { @@ -1116,6 +1173,7 @@ void PopupMenu::set_item_id(int p_idx, int p_id) { control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) { @@ -1128,6 +1186,7 @@ void PopupMenu::set_item_accelerator(int p_idx, Key p_accel) { control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) { @@ -1138,6 +1197,7 @@ void PopupMenu::set_item_metadata(int p_idx, const Variant &p_meta) { items.write[p_idx].metadata = p_meta; control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) { @@ -1148,6 +1208,7 @@ void PopupMenu::set_item_disabled(int p_idx, bool p_disabled) { items.write[p_idx].disabled = p_disabled; control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) { @@ -1158,6 +1219,7 @@ void PopupMenu::set_item_submenu(int p_idx, const String &p_submenu) { items.write[p_idx].submenu = p_submenu; control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::toggle_item_checked(int p_idx) { @@ -1165,6 +1227,7 @@ void PopupMenu::toggle_item_checked(int p_idx) { items.write[p_idx].checked = !items[p_idx].checked; control->update(); child_controls_changed(); + _menu_changed(); } String PopupMenu::get_item_text(int p_idx) const { @@ -1247,9 +1310,14 @@ Ref<Shortcut> PopupMenu::get_item_shortcut(int p_idx) const { return items[p_idx].shortcut; } -int PopupMenu::get_item_horizontal_offset(int p_idx) const { +int PopupMenu::get_item_indent(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, items.size(), 0); - return items[p_idx].h_ofs; + return items[p_idx].indent; +} + +int PopupMenu::get_item_max_states(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), -1); + return items[p_idx].max_states; } int PopupMenu::get_item_state(int p_idx) const { @@ -1278,6 +1346,7 @@ void PopupMenu::set_item_as_checkable(int p_idx, bool p_checkable) { ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].checkable_type = p_checkable ? Item::CHECKABLE_TYPE_CHECK_BOX : Item::CHECKABLE_TYPE_NONE; control->update(); + _menu_changed(); } void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) { @@ -1287,6 +1356,7 @@ void PopupMenu::set_item_as_radio_checkable(int p_idx, bool p_radio_checkable) { ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].checkable_type = p_radio_checkable ? Item::CHECKABLE_TYPE_RADIO_BUTTON : Item::CHECKABLE_TYPE_NONE; control->update(); + _menu_changed(); } void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) { @@ -1296,6 +1366,7 @@ void PopupMenu::set_item_tooltip(int p_idx, const String &p_tooltip) { ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].tooltip = p_tooltip; control->update(); + _menu_changed(); } void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global) { @@ -1315,16 +1386,18 @@ void PopupMenu::set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bo } control->update(); + _menu_changed(); } -void PopupMenu::set_item_horizontal_offset(int p_idx, int p_offset) { +void PopupMenu::set_item_indent(int p_idx, int p_indent) { if (p_idx < 0) { p_idx += get_item_count(); } ERR_FAIL_INDEX(p_idx, items.size()); - items.write[p_idx].h_ofs = p_offset; + items.write[p_idx].indent = p_indent; control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::set_item_multistate(int p_idx, int p_state) { @@ -1334,6 +1407,7 @@ void PopupMenu::set_item_multistate(int p_idx, int p_state) { ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].state = p_state; control->update(); + _menu_changed(); } void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) { @@ -1343,6 +1417,7 @@ void PopupMenu::set_item_shortcut_disabled(int p_idx, bool p_disabled) { ERR_FAIL_INDEX(p_idx, items.size()); items.write[p_idx].shortcut_is_disabled = p_disabled; control->update(); + _menu_changed(); } void PopupMenu::toggle_item_multistate(int p_idx) { @@ -1357,6 +1432,7 @@ void PopupMenu::toggle_item_multistate(int p_idx) { } control->update(); + _menu_changed(); } bool PopupMenu::is_item_checkable(int p_idx) const { @@ -1369,6 +1445,11 @@ bool PopupMenu::is_item_radio_checkable(int p_idx) const { return items[p_idx].checkable_type == Item::CHECKABLE_TYPE_RADIO_BUTTON; } +bool PopupMenu::is_item_shortcut_global(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, items.size(), false); + return items[p_idx].shortcut_is_global; +} + bool PopupMenu::is_item_shortcut_disabled(int p_idx) const { ERR_FAIL_INDEX_V(p_idx, items.size(), false); return items[p_idx].shortcut_is_disabled; @@ -1399,6 +1480,7 @@ void PopupMenu::set_item_count(int p_count) { control->update(); child_controls_changed(); notify_property_list_changed(); + _menu_changed(); } int PopupMenu::get_item_count() const { @@ -1540,6 +1622,7 @@ void PopupMenu::remove_item(int p_idx) { items.remove_at(p_idx); control->update(); child_controls_changed(); + _menu_changed(); } void PopupMenu::add_separator(const String &p_text, int p_id) { @@ -1552,6 +1635,7 @@ void PopupMenu::add_separator(const String &p_text, int p_id) { } items.push_back(sep); control->update(); + _menu_changed(); } void PopupMenu::clear() { @@ -1565,6 +1649,7 @@ void PopupMenu::clear() { control->update(); child_controls_changed(); notify_property_list_changed(); + _menu_changed(); } void PopupMenu::_ref_shortcut(Ref<Shortcut> p_sc) { @@ -1839,7 +1924,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("set_item_as_radio_checkable", "index", "enable"), &PopupMenu::set_item_as_radio_checkable); ClassDB::bind_method(D_METHOD("set_item_tooltip", "index", "tooltip"), &PopupMenu::set_item_tooltip); ClassDB::bind_method(D_METHOD("set_item_shortcut", "index", "shortcut", "global"), &PopupMenu::set_item_shortcut, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("set_item_horizontal_offset", "index", "offset"), &PopupMenu::set_item_horizontal_offset); + ClassDB::bind_method(D_METHOD("set_item_indent", "index", "indent"), &PopupMenu::set_item_indent); ClassDB::bind_method(D_METHOD("set_item_multistate", "index", "state"), &PopupMenu::set_item_multistate); ClassDB::bind_method(D_METHOD("set_item_shortcut_disabled", "index", "disabled"), &PopupMenu::set_item_shortcut_disabled); @@ -1863,7 +1948,7 @@ void PopupMenu::_bind_methods() { ClassDB::bind_method(D_METHOD("is_item_shortcut_disabled", "index"), &PopupMenu::is_item_shortcut_disabled); ClassDB::bind_method(D_METHOD("get_item_tooltip", "index"), &PopupMenu::get_item_tooltip); ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut); - ClassDB::bind_method(D_METHOD("get_item_horizontal_offset", "index"), &PopupMenu::get_item_horizontal_offset); + ClassDB::bind_method(D_METHOD("get_item_indent", "index"), &PopupMenu::get_item_indent); ClassDB::bind_method(D_METHOD("set_current_index", "index"), &PopupMenu::set_current_index); ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index); @@ -1903,6 +1988,7 @@ void PopupMenu::_bind_methods() { ADD_SIGNAL(MethodInfo("id_pressed", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("id_focused", PropertyInfo(Variant::INT, "id"))); ADD_SIGNAL(MethodInfo("index_pressed", PropertyInfo(Variant::INT, "index"))); + ADD_SIGNAL(MethodInfo("menu_changed")); } void PopupMenu::popup(const Rect2 &p_bounds) { diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index e203793c2e..d3ad0762e4 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -68,7 +68,7 @@ class PopupMenu : public Popup { Key accel = Key::NONE; int _ofs_cache = 0; int _height_cache = 0; - int h_ofs = 0; + int indent = 0; Ref<Shortcut> shortcut; bool shortcut_is_global = false; bool shortcut_is_disabled = false; @@ -134,8 +134,11 @@ class PopupMenu : public Popup { void _minimum_lifetime_timeout(); void _close_pressed(); + void _menu_changed(); protected: + virtual void add_child_notify(Node *p_child) override; + virtual void remove_child_notify(Node *p_child) override; void _notification(int p_what); bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -183,7 +186,7 @@ public: void set_item_as_radio_checkable(int p_idx, bool p_radio_checkable); void set_item_tooltip(int p_idx, const String &p_tooltip); void set_item_shortcut(int p_idx, const Ref<Shortcut> &p_shortcut, bool p_global = false); - void set_item_horizontal_offset(int p_idx, int p_offset); + void set_item_indent(int p_idx, int p_indent); void set_item_multistate(int p_idx, int p_state); void toggle_item_multistate(int p_idx); void set_item_shortcut_disabled(int p_idx, bool p_disabled); @@ -206,9 +209,11 @@ public: bool is_item_checkable(int p_idx) const; bool is_item_radio_checkable(int p_idx) const; bool is_item_shortcut_disabled(int p_idx) const; + bool is_item_shortcut_global(int p_idx) const; String get_item_tooltip(int p_idx) const; Ref<Shortcut> get_item_shortcut(int p_idx) const; - int get_item_horizontal_offset(int p_idx) const; + int get_item_indent(int p_idx) const; + int get_item_max_states(int p_idx) const; int get_item_state(int p_idx) const; void set_current_index(int p_idx); diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 984f20ee58..ab466089a7 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -4912,15 +4912,19 @@ void RichTextLabel::set_percent_visible(float p_percent) { if (percent_visible != p_percent) { _stop_thread(); - if (p_percent < 0 || p_percent >= 1) { + if (percent_visible >= 1.0) { visible_characters = -1; - percent_visible = 1; + percent_visible = 1.0; + } else if (percent_visible < 0.0) { + visible_characters = 0; + percent_visible = 0.0; } else { visible_characters = get_total_character_count() * p_percent; percent_visible = p_percent; } + if (visible_chars_behavior == TextServer::VC_CHARS_BEFORE_SHAPING) { - main->first_invalid_line.store(0); //invalidate ALL + main->first_invalid_line.store(0); // Invalidate ALL. _validate_line_caches(); } update(); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index a5842106fb..9979855232 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -100,6 +100,7 @@ #include "scene/gui/line_edit.h" #include "scene/gui/link_button.h" #include "scene/gui/margin_container.h" +#include "scene/gui/menu_bar.h" #include "scene/gui/menu_button.h" #include "scene/gui/nine_patch_rect.h" #include "scene/gui/option_button.h" @@ -351,6 +352,7 @@ void register_scene_types() { GDREGISTER_CLASS(VSlider); GDREGISTER_CLASS(Popup); GDREGISTER_CLASS(PopupPanel); + GDREGISTER_CLASS(MenuBar); GDREGISTER_CLASS(MenuButton); GDREGISTER_CLASS(CheckBox); GDREGISTER_CLASS(CheckButton); @@ -822,8 +824,6 @@ void register_scene_types() { ClassDB::register_class<SkeletonModification3DStackHolder>(); OS::get_singleton()->yield(); // may take time to init - - GDREGISTER_CLASS(VelocityTracker3D); #endif GDREGISTER_CLASS(PhysicsMaterial); @@ -843,7 +843,6 @@ void register_scene_types() { GDREGISTER_CLASS(CurveXYZTexture); GDREGISTER_CLASS(GradientTexture1D); GDREGISTER_CLASS(GradientTexture2D); - GDREGISTER_CLASS(ProxyTexture); GDREGISTER_CLASS(AnimatedTexture); GDREGISTER_CLASS(CameraTexture); GDREGISTER_VIRTUAL_CLASS(TextureLayered); @@ -1052,7 +1051,6 @@ void register_scene_types() { ClassDB::add_compatibility_class("Spatial", "Node3D"); ClassDB::add_compatibility_class("SpatialGizmo", "Node3DGizmo"); ClassDB::add_compatibility_class("SpatialMaterial", "StandardMaterial3D"); - ClassDB::add_compatibility_class("SpatialVelocityTracker", "VelocityTracker3D"); ClassDB::add_compatibility_class("SphereShape", "SphereShape3D"); ClassDB::add_compatibility_class("SpotLight", "SpotLight3D"); ClassDB::add_compatibility_class("SpringArm", "SpringArm3D"); diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index f29cfec92f..b96ee5c6c4 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -177,6 +177,27 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("h_separation", "Button", 2 * scale); + // MenuBar + theme->set_stylebox("normal", "MenuBar", button_normal); + theme->set_stylebox("hover", "MenuBar", button_hover); + theme->set_stylebox("pressed", "MenuBar", button_pressed); + theme->set_stylebox("disabled", "MenuBar", button_disabled); + theme->set_stylebox("focus", "MenuBar", focus); + + theme->set_font("font", "MenuBar", Ref<Font>()); + theme->set_font_size("font_size", "MenuBar", -1); + theme->set_constant("outline_size", "MenuBar", 0 * scale); + + theme->set_color("font_color", "MenuBar", control_font_color); + theme->set_color("font_pressed_color", "MenuBar", control_font_pressed_color); + theme->set_color("font_hover_color", "MenuBar", control_font_hover_color); + theme->set_color("font_focus_color", "MenuBar", control_font_focus_color); + theme->set_color("font_hover_pressed_color", "MenuBar", control_font_pressed_color); + theme->set_color("font_disabled_color", "MenuBar", control_font_disabled_color); + theme->set_color("font_outline_color", "MenuBar", Color(1, 1, 1)); + + theme->set_constant("h_separation", "MenuBar", 4 * scale); + // LinkButton theme->set_stylebox("focus", "LinkButton", focus); @@ -669,6 +690,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("font_outline_color", "PopupMenu", Color(1, 1, 1)); theme->set_color("font_separator_outline_color", "PopupMenu", Color(1, 1, 1)); + theme->set_constant("indent", "PopupMenu", 10 * scale); theme->set_constant("h_separation", "PopupMenu", 4 * scale); theme->set_constant("v_separation", "PopupMenu", 4 * scale); theme->set_constant("outline_size", "PopupMenu", 0); diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 880876baed..e7d5d40777 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -2714,6 +2714,9 @@ void SystemFont::_bind_methods() { ClassDB::bind_method(D_METHOD("set_subpixel_positioning", "subpixel_positioning"), &SystemFont::set_subpixel_positioning); ClassDB::bind_method(D_METHOD("get_subpixel_positioning"), &SystemFont::get_subpixel_positioning); + ClassDB::bind_method(D_METHOD("set_multichannel_signed_distance_field", "msdf"), &SystemFont::set_multichannel_signed_distance_field); + ClassDB::bind_method(D_METHOD("is_multichannel_signed_distance_field"), &SystemFont::is_multichannel_signed_distance_field); + ClassDB::bind_method(D_METHOD("set_oversampling", "oversampling"), &SystemFont::set_oversampling); ClassDB::bind_method(D_METHOD("get_oversampling"), &SystemFont::get_oversampling); @@ -2729,6 +2732,7 @@ void SystemFont::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "force_autohinter"), "set_force_autohinter", "is_force_autohinter"); ADD_PROPERTY(PropertyInfo(Variant::INT, "hinting", PROPERTY_HINT_ENUM, "None,Light,Normal"), "set_hinting", "get_hinting"); ADD_PROPERTY(PropertyInfo(Variant::INT, "subpixel_positioning", PROPERTY_HINT_ENUM, "Disabled,Auto,One half of a pixel,One quarter of a pixel"), "set_subpixel_positioning", "get_subpixel_positioning"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multichannel_signed_distance_field"), "set_multichannel_signed_distance_field", "is_multichannel_signed_distance_field"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oversampling", PROPERTY_HINT_RANGE, "0,10,0.1"), "set_oversampling", "get_oversampling"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "fallbacks", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "Font")), "set_fallbacks", "get_fallbacks"); } @@ -2805,6 +2809,7 @@ void SystemFont::_update_base_font() { file->set_force_autohinter(force_autohinter); file->set_hinting(hinting); file->set_subpixel_positioning(subpixel_positioning); + file->set_multichannel_signed_distance_field(msdf); file->set_oversampling(oversampling); base_font = file; @@ -2842,6 +2847,7 @@ void SystemFont::reset_state() { hinting = TextServer::HINTING_LIGHT; subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_DISABLED; oversampling = 0.f; + msdf = false; Font::reset_state(); } @@ -2971,6 +2977,20 @@ TextServer::SubpixelPositioning SystemFont::get_subpixel_positioning() const { return subpixel_positioning; } +void SystemFont::set_multichannel_signed_distance_field(bool p_msdf) { + if (msdf != p_msdf) { + msdf = p_msdf; + if (base_font.is_valid()) { + base_font->set_multichannel_signed_distance_field(msdf); + } + emit_changed(); + } +} + +bool SystemFont::is_multichannel_signed_distance_field() const { + return msdf; +} + void SystemFont::set_oversampling(real_t p_oversampling) { if (oversampling != p_oversampling) { oversampling = p_oversampling; diff --git a/scene/resources/font.h b/scene/resources/font.h index 260b4e521f..696152a23b 100644 --- a/scene/resources/font.h +++ b/scene/resources/font.h @@ -404,6 +404,7 @@ class SystemFont : public Font { TextServer::Hinting hinting = TextServer::HINTING_LIGHT; TextServer::SubpixelPositioning subpixel_positioning = TextServer::SUBPIXEL_POSITIONING_AUTO; real_t oversampling = 0.f; + bool msdf = false; protected: static void _bind_methods(); @@ -434,6 +435,9 @@ public: virtual void set_oversampling(real_t p_oversampling); virtual real_t get_oversampling() const; + virtual void set_multichannel_signed_distance_field(bool p_msdf); + virtual bool is_multichannel_signed_distance_field() const; + virtual void set_font_names(const PackedStringArray &p_names); virtual PackedStringArray get_font_names() const; diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index 88bc01fb25..5d887dae17 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -297,7 +297,7 @@ void ShaderMaterial::_get_property_list(List<PropertyInfo> *p_list) const { } } -bool ShaderMaterial::property_can_revert(const String &p_name) { +bool ShaderMaterial::_property_can_revert(const StringName &p_name) const { if (shader.is_valid()) { StringName pr = shader->remap_uniform(p_name); if (pr) { @@ -310,15 +310,15 @@ bool ShaderMaterial::property_can_revert(const String &p_name) { return false; } -Variant ShaderMaterial::property_get_revert(const String &p_name) { - Variant r_ret; +bool ShaderMaterial::_property_get_revert(const StringName &p_name, Variant &r_property) const { if (shader.is_valid()) { StringName pr = shader->remap_uniform(p_name); if (pr) { - r_ret = RenderingServer::get_singleton()->shader_get_param_default(shader->get_rid(), pr); + r_property = RenderingServer::get_singleton()->shader_get_param_default(shader->get_rid(), pr); + return true; } } - return r_ret; + return false; } void ShaderMaterial::set_shader(const Ref<Shader> &p_shader) { @@ -386,8 +386,6 @@ void ShaderMaterial::_bind_methods() { ClassDB::bind_method(D_METHOD("get_shader"), &ShaderMaterial::get_shader); ClassDB::bind_method(D_METHOD("set_shader_uniform", "param", "value"), &ShaderMaterial::set_shader_uniform); ClassDB::bind_method(D_METHOD("get_shader_uniform", "param"), &ShaderMaterial::get_shader_uniform); - ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &ShaderMaterial::property_can_revert); - ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &ShaderMaterial::property_get_revert); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "shader", PROPERTY_HINT_RESOURCE_TYPE, "Shader"), "set_shader", "get_shader"); } diff --git a/scene/resources/material.h b/scene/resources/material.h index ca5b17dd07..6049671582 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -99,8 +99,8 @@ protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - bool property_can_revert(const String &p_name); - Variant property_get_revert(const String &p_name); + bool _property_can_revert(const StringName &p_name) const; + bool _property_get_revert(const StringName &p_name, Variant &r_property) const; static void _bind_methods(); diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index ec9db89794..3f73df2e5d 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -201,7 +201,9 @@ Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { continue; } int len = (surface_get_format(i) & ARRAY_FORMAT_INDEX) ? surface_get_array_index_len(i) : surface_get_array_len(i); - if ((primitive == PRIMITIVE_TRIANGLES && (len == 0 || (len % 3) != 0)) || (primitive == PRIMITIVE_TRIANGLE_STRIP && len < 3)) { + if ((primitive == PRIMITIVE_TRIANGLES && (len == 0 || (len % 3) != 0)) || + (primitive == PRIMITIVE_TRIANGLE_STRIP && len < 3) || + (surface_get_format(i) & ARRAY_FLAG_USES_EMPTY_VERTEX_ARRAY)) { // Error was already shown, just skip (including zero). continue; } @@ -211,6 +213,7 @@ Ref<TriangleMesh> Mesh::generate_triangle_mesh() const { int vc = surface_get_array_len(i); Vector<Vector3> vertices = a[ARRAY_VERTEX]; + ERR_FAIL_COND_V(vertices.is_empty(), Ref<TriangleMesh>()); const Vector3 *vr = vertices.ptr(); int32_t from_index = widx / 3; diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 142373ce7f..63dbda92d0 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -144,6 +144,7 @@ public: ARRAY_FLAG_USE_DYNAMIC_UPDATE = RS::ARRAY_FLAG_USE_DYNAMIC_UPDATE, ARRAY_FLAG_USE_8_BONE_WEIGHTS = RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS, + ARRAY_FLAG_USES_EMPTY_VERTEX_ARRAY = RS::ARRAY_FLAG_USES_EMPTY_VERTEX_ARRAY, }; virtual int get_surface_count() const; diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 05ed9238b8..e5c4974967 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -2537,13 +2537,6 @@ void GradientTexture2D::_bind_methods() { ////////////////////////////////////// -void ProxyTexture::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_base", "base"), &ProxyTexture::set_base); - ClassDB::bind_method(D_METHOD("get_base"), &ProxyTexture::get_base); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "base", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_base", "get_base"); -} - void ProxyTexture::set_base(const Ref<Texture2D> &p_texture) { ERR_FAIL_COND(p_texture == this); diff --git a/scene/resources/texture.h b/scene/resources/texture.h index 36b193c5d4..7c5624bd09 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -883,8 +883,6 @@ VARIANT_ENUM_CAST(GradientTexture2D::Fill); VARIANT_ENUM_CAST(GradientTexture2D::Repeat); class ProxyTexture : public Texture2D { - GDCLASS(ProxyTexture, Texture2D); - private: mutable RID proxy_ph; mutable RID proxy; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index a67716d52b..fa24a95115 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -1698,13 +1698,13 @@ Error VisualShader::_write_node(Type type, StringBuilder *global_code, StringBui inputs[i] = "(" + src_var + " ? 1.0 : 0.0)"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - inputs[i] = "dot(" + src_var + ", vec2(0.5, 0.5))"; + inputs[i] = src_var + ".x"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - inputs[i] = "dot(" + src_var + ", vec3(0.333333, 0.333333, 0.333333))"; + inputs[i] = src_var + ".x"; } break; case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - inputs[i] = "dot(" + src_var + ", vec4(0.25, 0.25, 0.25, 0.25))"; + inputs[i] = src_var + ".x"; } break; default: break; diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp index b422d298b2..2911d726b4 100644 --- a/scene/resources/visual_shader_nodes.cpp +++ b/scene/resources/visual_shader_nodes.cpp @@ -1015,7 +1015,7 @@ Vector<StringName> VisualShaderNodeCurveTexture::get_editable_properties() const } String VisualShaderNodeCurveTexture::generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const { - return "uniform sampler2D " + make_unique_id(p_type, p_id, "curve") + ";\n"; + return "uniform sampler2D " + make_unique_id(p_type, p_id, "curve") + " : repeat_disable;\n"; } String VisualShaderNodeCurveTexture::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const { @@ -6830,23 +6830,23 @@ void VisualShaderNodeMultiplyAdd::set_op_type(OpType p_op_type) { switch (p_op_type) { case OP_TYPE_SCALAR: { set_input_port_default_value(0, 0.0, get_input_port_default_value(0)); - set_input_port_default_value(1, 0.0, get_input_port_default_value(1)); + set_input_port_default_value(1, 1.0, get_input_port_default_value(1)); set_input_port_default_value(2, 0.0, get_input_port_default_value(2)); } break; case OP_TYPE_VECTOR_2D: { set_input_port_default_value(0, Vector2(), get_input_port_default_value(0)); - set_input_port_default_value(1, Vector2(), get_input_port_default_value(1)); + set_input_port_default_value(1, Vector2(1.0, 1.0), get_input_port_default_value(1)); set_input_port_default_value(2, Vector2(), get_input_port_default_value(2)); } break; case OP_TYPE_VECTOR_3D: { set_input_port_default_value(0, Vector3(), get_input_port_default_value(0)); - set_input_port_default_value(1, Vector3(), get_input_port_default_value(1)); + set_input_port_default_value(1, Vector3(1.0, 1.0, 1.0), get_input_port_default_value(1)); set_input_port_default_value(2, Vector3(), get_input_port_default_value(2)); } break; case OP_TYPE_VECTOR_4D: { - set_input_port_default_value(0, Quaternion(), get_input_port_default_value(0)); - set_input_port_default_value(1, Quaternion(), get_input_port_default_value(1)); - set_input_port_default_value(2, Quaternion(), get_input_port_default_value(2)); + set_input_port_default_value(0, Vector4(), get_input_port_default_value(0)); + set_input_port_default_value(1, Vector4(1.0, 1.0, 1.0, 1.0), get_input_port_default_value(1)); + set_input_port_default_value(2, Vector4(), get_input_port_default_value(2)); } break; default: break; @@ -6880,7 +6880,7 @@ void VisualShaderNodeMultiplyAdd::_bind_methods() { VisualShaderNodeMultiplyAdd::VisualShaderNodeMultiplyAdd() { set_input_port_default_value(0, 0.0); - set_input_port_default_value(1, 0.0); + set_input_port_default_value(1, 1.0); set_input_port_default_value(2, 0.0); } diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 4e7db7d0a5..ff6d769a86 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -44,40 +44,49 @@ DisplayServer::DisplayServerCreate DisplayServer::server_create_functions[Displa int DisplayServer::server_create_count = 1; -void DisplayServer::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServer::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { WARN_PRINT("Global menus not supported by this display server."); + return -1; } -void DisplayServer::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServer::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { WARN_PRINT("Global menus not supported by this display server."); + return -1; } -void DisplayServer::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServer::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { WARN_PRINT("Global menus not supported by this display server."); + return -1; } -void DisplayServer::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServer::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { WARN_PRINT("Global menus not supported by this display server."); + return -1; } -void DisplayServer::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServer::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { WARN_PRINT("Global menus not supported by this display server."); + return -1; } -void DisplayServer::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServer::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { WARN_PRINT("Global menus not supported by this display server."); + return -1; } -void DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { +int DisplayServer::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) { WARN_PRINT("Global menus not supported by this display server."); + return -1; } -void DisplayServer::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { +int DisplayServer::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) { WARN_PRINT("Global menus not supported by this display server."); + return -1; } -void DisplayServer::global_menu_add_separator(const String &p_menu_root, int p_index) { +int DisplayServer::global_menu_add_separator(const String &p_menu_root, int p_index) { WARN_PRINT("Global menus not supported by this display server."); + return -1; } int DisplayServer::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const { @@ -159,6 +168,11 @@ Ref<Texture2D> DisplayServer::global_menu_get_item_icon(const String &p_menu_roo return Ref<Texture2D>(); } +int DisplayServer::global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const { + WARN_PRINT("Global menus not supported by this display server."); + return 0; +} + void DisplayServer::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) { WARN_PRINT("Global menus not supported by this display server."); } @@ -207,6 +221,10 @@ void DisplayServer::global_menu_set_item_icon(const String &p_menu_root, int p_i WARN_PRINT("Global menus not supported by this display server."); } +void DisplayServer::global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) { + WARN_PRINT("Global menus not supported by this display server."); +} + int DisplayServer::global_menu_get_item_count(const String &p_menu_root) const { WARN_PRINT("Global menus not supported by this display server."); return 0; @@ -535,6 +553,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("global_menu_get_item_state", "menu_root", "idx"), &DisplayServer::global_menu_get_item_state); ClassDB::bind_method(D_METHOD("global_menu_get_item_max_states", "menu_root", "idx"), &DisplayServer::global_menu_get_item_max_states); ClassDB::bind_method(D_METHOD("global_menu_get_item_icon", "menu_root", "idx"), &DisplayServer::global_menu_get_item_icon); + ClassDB::bind_method(D_METHOD("global_menu_get_item_indentation_level", "menu_root", "idx"), &DisplayServer::global_menu_get_item_indentation_level); ClassDB::bind_method(D_METHOD("global_menu_set_item_checked", "menu_root", "idx", "checked"), &DisplayServer::global_menu_set_item_checked); ClassDB::bind_method(D_METHOD("global_menu_set_item_checkable", "menu_root", "idx", "checkable"), &DisplayServer::global_menu_set_item_checkable); @@ -549,6 +568,7 @@ void DisplayServer::_bind_methods() { ClassDB::bind_method(D_METHOD("global_menu_set_item_state", "menu_root", "idx", "state"), &DisplayServer::global_menu_set_item_state); ClassDB::bind_method(D_METHOD("global_menu_set_item_max_states", "menu_root", "idx", "max_states"), &DisplayServer::global_menu_set_item_max_states); ClassDB::bind_method(D_METHOD("global_menu_set_item_icon", "menu_root", "idx", "icon"), &DisplayServer::global_menu_set_item_icon); + ClassDB::bind_method(D_METHOD("global_menu_set_item_indentation_level", "menu_root", "idx", "level"), &DisplayServer::global_menu_set_item_indentation_level); ClassDB::bind_method(D_METHOD("global_menu_remove_item", "menu_root", "idx"), &DisplayServer::global_menu_remove_item); ClassDB::bind_method(D_METHOD("global_menu_clear", "menu_root"), &DisplayServer::global_menu_clear); diff --git a/servers/display_server.h b/servers/display_server.h index 8632b53f7b..a5c42617af 100644 --- a/servers/display_server.h +++ b/servers/display_server.h @@ -127,15 +127,15 @@ public: virtual bool has_feature(Feature p_feature) const = 0; virtual String get_name() const = 0; - virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); - virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); - virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); - virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); - virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); - virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); - virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); - virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1); - virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1); + virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); + virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); + virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); + virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); + virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); + virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); + virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1); + virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1); + virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1); virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const; virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const; @@ -153,6 +153,7 @@ public: virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const; virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const; virtual Ref<Texture2D> global_menu_get_item_icon(const String &p_menu_root, int p_idx) const; + virtual int global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const; virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked); virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable); @@ -167,6 +168,7 @@ public: virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state); virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states); virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon); + virtual void global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level); virtual int global_menu_get_item_count(const String &p_menu_root) const; diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp index 126bb08c94..27b49014d8 100644 --- a/servers/navigation_server_2d.cpp +++ b/servers/navigation_server_2d.cpp @@ -204,7 +204,7 @@ void NavigationServer2D::_bind_methods() { ClassDB::bind_method(D_METHOD("map_create"), &NavigationServer2D::map_create); ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &NavigationServer2D::map_set_active); - ClassDB::bind_method(D_METHOD("map_is_active", "nap"), &NavigationServer2D::map_is_active); + ClassDB::bind_method(D_METHOD("map_is_active", "map"), &NavigationServer2D::map_is_active); ClassDB::bind_method(D_METHOD("map_set_cell_size", "map", "cell_size"), &NavigationServer2D::map_set_cell_size); ClassDB::bind_method(D_METHOD("map_get_cell_size", "map"), &NavigationServer2D::map_get_cell_size); ClassDB::bind_method(D_METHOD("map_set_edge_connection_margin", "map", "margin"), &NavigationServer2D::map_set_edge_connection_margin); diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp index 115eda7b30..206698f97c 100644 --- a/servers/navigation_server_3d.cpp +++ b/servers/navigation_server_3d.cpp @@ -41,7 +41,7 @@ void NavigationServer3D::_bind_methods() { ClassDB::bind_method(D_METHOD("map_create"), &NavigationServer3D::map_create); ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &NavigationServer3D::map_set_active); - ClassDB::bind_method(D_METHOD("map_is_active", "nap"), &NavigationServer3D::map_is_active); + ClassDB::bind_method(D_METHOD("map_is_active", "map"), &NavigationServer3D::map_is_active); ClassDB::bind_method(D_METHOD("map_set_up", "map", "up"), &NavigationServer3D::map_set_up); ClassDB::bind_method(D_METHOD("map_get_up", "map"), &NavigationServer3D::map_get_up); ClassDB::bind_method(D_METHOD("map_set_cell_size", "map", "cell_size"), &NavigationServer3D::map_set_cell_size); diff --git a/servers/rendering/renderer_rd/environment/sky.cpp b/servers/rendering/renderer_rd/environment/sky.cpp index 6433a39863..d3601274b5 100644 --- a/servers/rendering/renderer_rd/environment/sky.cpp +++ b/servers/rendering/renderer_rd/environment/sky.cpp @@ -114,12 +114,16 @@ void SkyRD::SkyShaderData::set_code(const String &p_code) { for (int i = 0; i < gen_code.defines.size(); i++) { print_line(gen_code.defines[i]); } + + HashMap<String, String>::Iterator el = gen_code.code.begin(); + while (el) { + print_line("\n**code " + el->key + ":\n" + el->value); + ++el; + } + print_line("\n**uniforms:\n" + gen_code.uniforms); - // print_line("\n**vertex_globals:\n" + gen_code.vertex_global); - // print_line("\n**vertex_code:\n" + gen_code.vertex); - print_line("\n**fragment_globals:\n" + gen_code.fragment_global); - print_line("\n**fragment_code:\n" + gen_code.fragment); - print_line("\n**light_code:\n" + gen_code.light); + print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX]); + print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]); #endif scene_singleton->sky.sky_shader.shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines); diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp index 556db086b2..0911ee595f 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp @@ -150,6 +150,7 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) { depth_draw = DepthDraw(depth_drawi); depth_test = DepthTest(depth_testi); cull_mode = Cull(cull_modei); + uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps; #if 0 print_line("**compiling shader:"); @@ -158,11 +159,10 @@ void SceneShaderForwardClustered::ShaderData::set_code(const String &p_code) { print_line(gen_code.defines[i]); } - RBMap<String, String>::Element *el = gen_code.code.front(); + HashMap<String, String>::Iterator el = gen_code.code.begin(); while (el) { - print_line("\n**code " + el->key() + ":\n" + el->value()); - - el = el->next(); + print_line("\n**code " + el->key + ":\n" + el->value); + ++el; } print_line("\n**uniforms:\n" + gen_code.uniforms); @@ -396,7 +396,11 @@ void SceneShaderForwardClustered::ShaderData::get_shader_uniform_list(List<Prope HashMap<int, StringName> order; for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) { - if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) { + if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { + // Don't expose any of these. continue; } diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h index fa9ebde1b2..d6b526fa4a 100644 --- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h +++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h @@ -174,6 +174,7 @@ public: bool uses_time = false; bool writes_modelview_or_projection = false; bool uses_world_coordinates = false; + bool uses_screen_texture_mipmaps = false; Cull cull_mode = CULL_DISABLED; uint64_t last_pass = 0; diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index 01b54607bc..85c9e1db2a 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -158,11 +158,10 @@ void SceneShaderForwardMobile::ShaderData::set_code(const String &p_code) { print_line(gen_code.defines[i]); } - RBMap<String, String>::Element * el = gen_code.code.front(); + HashMap<String, String>::Iterator el = gen_code.code.begin(); while (el) { - print_line("\n**code " + el->key() + ":\n" + el->value()); - - el = el->next(); + print_line("\n**code " + el->key + ":\n" + el->value); + ++el; } print_line("\n**uniforms:\n" + gen_code.uniforms); @@ -353,7 +352,10 @@ void SceneShaderForwardMobile::ShaderData::get_shader_uniform_list(List<Property HashMap<int, StringName> order; for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) { - if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) { + if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { continue; } diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp index c1b08ee4c9..38a2340d40 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp @@ -1361,6 +1361,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p Item *ci = p_item_list; Rect2 back_buffer_rect; bool backbuffer_copy = false; + bool backbuffer_gen_mipmaps = false; Item *canvas_group_owner = nullptr; @@ -1389,6 +1390,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p if (!material_screen_texture_found) { backbuffer_copy = true; back_buffer_rect = Rect2(); + backbuffer_gen_mipmaps = md->shader_data->uses_screen_texture_mipmaps; } } @@ -1474,9 +1476,10 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list); item_count = 0; - texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, true); + texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps); backbuffer_copy = false; + backbuffer_gen_mipmaps = false; material_screen_texture_found = true; //after a backbuffer copy, screen texture makes no further copies } @@ -1980,6 +1983,7 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) { ubo_size = 0; uniforms.clear(); uses_screen_texture = false; + uses_screen_texture_mipmaps = false; uses_sdf = false; uses_time = false; @@ -1990,7 +1994,6 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) { ShaderCompiler::GeneratedCode gen_code; int blend_mode = BLEND_MODE_MIX; - uses_screen_texture = false; ShaderCompiler::IdentifierActions actions; actions.entry_point_stages["vertex"] = ShaderCompiler::STAGE_VERTEX; @@ -2015,6 +2018,8 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) { Error err = canvas_singleton->shader.compiler.compile(RS::SHADER_CANVAS_ITEM, code, &actions, path, gen_code); ERR_FAIL_COND_MSG(err != OK, "Shader compilation failed."); + uses_screen_texture_mipmaps = gen_code.uses_screen_texture_mipmaps; + if (version.is_null()) { version = canvas_singleton->shader.canvas_shader.version_create(); } @@ -2025,12 +2030,16 @@ void RendererCanvasRenderRD::CanvasShaderData::set_code(const String &p_code) { for (int i = 0; i < gen_code.defines.size(); i++) { print_line(gen_code.defines[i]); } + + HashMap<String, String>::Iterator el = gen_code.code.begin(); + while (el) { + print_line("\n**code " + el->key + ":\n" + el->value); + ++el; + } + print_line("\n**uniforms:\n" + gen_code.uniforms); - print_line("\n**vertex_globals:\n" + gen_code.vertex_global); - print_line("\n**vertex_code:\n" + gen_code.vertex); - print_line("\n**fragment_globals:\n" + gen_code.fragment_global); - print_line("\n**fragment_code:\n" + gen_code.fragment); - print_line("\n**light_code:\n" + gen_code.light); + print_line("\n**vertex_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX]); + print_line("\n**fragment_globals:\n" + gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT]); #endif canvas_singleton->shader.canvas_shader.version_set_code(version, gen_code.code, gen_code.uniforms, gen_code.stage_globals[ShaderCompiler::STAGE_VERTEX], gen_code.stage_globals[ShaderCompiler::STAGE_FRAGMENT], gen_code.defines); ERR_FAIL_COND(!canvas_singleton->shader.canvas_shader.version_is_valid(version)); @@ -2175,7 +2184,11 @@ void RendererCanvasRenderRD::CanvasShaderData::get_shader_uniform_list(List<Prop HashMap<int, StringName> order; for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : uniforms) { - if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL) { + if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_LOCAL || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { + // Don't expose any of these. continue; } if (E.value.texture_order >= 0) { diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h index 5eb4cee4c6..bcbbbaa1a0 100644 --- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.h +++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.h @@ -174,6 +174,7 @@ class RendererCanvasRenderRD : public RendererCanvasRender { HashMap<StringName, HashMap<int, RID>> default_texture_params; bool uses_screen_texture = false; + bool uses_screen_texture_mipmaps = false; bool uses_sdf = false; bool uses_time = false; diff --git a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp index 6c219933b0..d8499681ad 100644 --- a/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp +++ b/servers/rendering/renderer_rd/renderer_scene_render_rd.cpp @@ -2868,7 +2868,9 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const WARN_PRINT_ONCE("The DirectionalLight3D PSSM splits debug draw mode is not reimplemented yet."); } - light_data.shadow_opacity = p_using_shadows && light_storage->light_has_shadow(base) ? light_storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_OPACITY) : 0.0; + light_data.shadow_opacity = (p_using_shadows && light_storage->light_has_shadow(base)) + ? light_storage->light_get_param(base, RS::LIGHT_PARAM_SHADOW_OPACITY) + : 0.0; float angular_diameter = light_storage->light_get_param(base, RS::LIGHT_PARAM_SIZE); if (angular_diameter > 0.0) { @@ -3122,7 +3124,11 @@ void RendererSceneRenderRD::_setup_lights(const PagedArray<RID> &p_lights, const light_data.projector_rect[3] = 0; } - const bool needs_shadow = shadow_atlas && shadow_atlas->shadow_owners.has(li->self); + const bool needs_shadow = + shadow_atlas && + shadow_atlas->shadow_owners.has(li->self) && + p_using_shadows && + light_storage->light_has_shadow(base); bool in_shadow_range = true; if (needs_shadow && light_storage->light_is_distance_fade_enabled(li->light)) { diff --git a/servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl b/servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl index 0438671dd2..0b43af7738 100644 --- a/servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/bokeh_dof.glsl @@ -30,7 +30,7 @@ layout(set = 1, binding = 0) uniform sampler2D source_bokeh; #ifdef MODE_GEN_BLUR_SIZE float get_depth_at_pos(vec2 uv) { - float depth = textureLod(source_depth, uv, 0.0).x; + float depth = textureLod(source_depth, uv, 0.0).x * 2.0 - 1.0; if (params.orthogonal) { depth = ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0; } else { diff --git a/servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl b/servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl index a3b3938ee9..a06cacfabe 100644 --- a/servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl +++ b/servers/rendering/renderer_rd/shaders/effects/bokeh_dof_raster.glsl @@ -52,7 +52,7 @@ layout(set = 2, binding = 0) uniform sampler2D original_weight; #ifdef MODE_GEN_BLUR_SIZE float get_depth_at_pos(vec2 uv) { - float depth = textureLod(source_depth, uv, 0.0).x; + float depth = textureLod(source_depth, uv, 0.0).x * 2.0 - 1.0; if (params.orthogonal) { depth = ((depth + (params.z_far + params.z_near) / (params.z_far - params.z_near)) * (params.z_far - params.z_near)) / 2.0; } else { diff --git a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp index 41dd1ccc40..fa8406e7a1 100644 --- a/servers/rendering/renderer_rd/storage_rd/material_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/material_storage.cpp @@ -941,6 +941,12 @@ void MaterialStorage::MaterialData::update_uniform_buffer(const HashMap<StringNa continue; //instance uniforms don't appear in the buffer } + if (E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + E.value.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { + continue; + } + if (E.value.scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_GLOBAL) { //this is a global variable, get the index to it GlobalShaderUniforms::Variable *gv = material_storage->global_shader_uniforms.variables.getptr(E.key); @@ -1052,6 +1058,12 @@ void MaterialStorage::MaterialData::update_textures(const HashMap<StringName, Va Vector<RID> textures; + if (p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + p_texture_uniforms[i].hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { + continue; + } + if (p_texture_uniforms[i].global) { uses_global_textures = true; @@ -1307,7 +1319,7 @@ bool MaterialStorage::MaterialData::update_parameters_uniform_set(const HashMap< update_textures(p_parameters, p_default_texture_params, p_texture_uniforms, texture_cache.ptrw(), true); } - if (p_ubo_size == 0 && p_texture_uniforms.size() == 0) { + if (p_ubo_size == 0 && (p_texture_uniforms.size() == 0)) { // This material does not require an uniform set, so don't create it. return false; } diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index dc3f35f942..1516e5e4fd 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -327,8 +327,10 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) bool use_as_storage = (p_surface.skin_data.size() || mesh->blend_shape_count > 0); - s->vertex_buffer = RD::get_singleton()->vertex_buffer_create(p_surface.vertex_data.size(), p_surface.vertex_data, use_as_storage); - s->vertex_buffer_size = p_surface.vertex_data.size(); + if (p_surface.vertex_data.size()) { + s->vertex_buffer = RD::get_singleton()->vertex_buffer_create(p_surface.vertex_data.size(), p_surface.vertex_data, use_as_storage); + s->vertex_buffer_size = p_surface.vertex_data.size(); + } if (p_surface.attribute_data.size()) { s->attribute_buffer = RD::get_singleton()->vertex_buffer_create(p_surface.attribute_data.size(), p_surface.attribute_data); @@ -345,7 +347,7 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) } if (p_surface.index_count) { - bool is_index_16 = p_surface.vertex_count <= 65536; + bool is_index_16 = p_surface.vertex_count <= 65536 && p_surface.vertex_count > 0; s->index_buffer = RD::get_singleton()->index_buffer_create(p_surface.index_count, is_index_16 ? RD::INDEX_BUFFER_FORMAT_UINT16 : RD::INDEX_BUFFER_FORMAT_UINT32, p_surface.index_data, false); s->index_count = p_surface.index_count; @@ -364,6 +366,8 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) } } + ERR_FAIL_COND_MSG(!p_surface.index_count && !p_surface.vertex_count, "Meshes must contain a vertex array, an index array, or both"); + s->aabb = p_surface.aabb; s->bone_aabbs = p_surface.bone_aabbs; //only really useful for returning them. @@ -377,7 +381,11 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) RD::Uniform u; u.binding = 0; u.uniform_type = RD::UNIFORM_TYPE_STORAGE_BUFFER; - u.append_id(s->vertex_buffer); + if (s->vertex_buffer.is_valid()) { + u.append_id(s->vertex_buffer); + } else { + u.append_id(default_rd_storage_buffer); + } uniforms.push_back(u); } { @@ -416,7 +424,10 @@ void MeshStorage::mesh_add_surface(RID p_mesh, const RS::SurfaceData &p_surface) mesh->bone_aabbs.resize(p_surface.bone_aabbs.size()); } for (int i = 0; i < p_surface.bone_aabbs.size(); i++) { - mesh->bone_aabbs.write[i].merge_with(p_surface.bone_aabbs[i]); + const AABB &bone = p_surface.bone_aabbs[i]; + if (!bone.has_no_volume()) { + mesh->bone_aabbs.write[i].merge_with(bone); + } } mesh->aabb.merge_with(p_surface.aabb); } @@ -467,6 +478,7 @@ void MeshStorage::mesh_surface_update_vertex_region(RID p_mesh, int p_surface, i ERR_FAIL_COND(!mesh); ERR_FAIL_UNSIGNED_INDEX((uint32_t)p_surface, mesh->surface_count); ERR_FAIL_COND(p_data.size() == 0); + ERR_FAIL_COND(mesh->surfaces[p_surface]->vertex_buffer.is_null()); uint64_t data_size = p_data.size(); const uint8_t *r = p_data.ptr(); @@ -524,7 +536,9 @@ RS::SurfaceData MeshStorage::mesh_get_surface(RID p_mesh, int p_surface) const { RS::SurfaceData sd; sd.format = s.format; - sd.vertex_data = RD::get_singleton()->buffer_get_data(s.vertex_buffer); + if (s.vertex_buffer.is_valid()) { + sd.vertex_data = RD::get_singleton()->buffer_get_data(s.vertex_buffer); + } if (s.attribute_buffer.is_valid()) { sd.attribute_data = RD::get_singleton()->buffer_get_data(s.attribute_buffer); } @@ -702,7 +716,9 @@ void MeshStorage::mesh_clear(RID p_mesh) { ERR_FAIL_COND(!mesh); for (uint32_t i = 0; i < mesh->surface_count; i++) { Mesh::Surface &s = *mesh->surfaces[i]; - RD::get_singleton()->free(s.vertex_buffer); //clears arrays as dependency automatically, including all versions + if (s.vertex_buffer.is_valid()) { + RD::get_singleton()->free(s.vertex_buffer); //clears arrays as dependency automatically, including all versions + } if (s.attribute_buffer.is_valid()) { RD::get_singleton()->free(s.attribute_buffer); } @@ -848,7 +864,7 @@ void MeshStorage::_mesh_instance_add_surface(MeshInstance *mi, Mesh *mesh, uint3 } MeshInstance::Surface s; - if (mesh->blend_shape_count > 0 || (mesh->surfaces[p_surface]->format & RS::ARRAY_FORMAT_BONES)) { + if ((mesh->blend_shape_count > 0 || (mesh->surfaces[p_surface]->format & RS::ARRAY_FORMAT_BONES)) && mesh->surfaces[p_surface]->vertex_buffer_size > 0) { //surface warrants transform s.vertex_buffer = RD::get_singleton()->vertex_buffer_create(mesh->surfaces[p_surface]->vertex_buffer_size, Vector<uint8_t>(), true); diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp index c2cf08812c..f14350305a 100644 --- a/servers/rendering/shader_compiler.cpp +++ b/servers/rendering/shader_compiler.cpp @@ -498,6 +498,11 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene for (const KeyValue<StringName, SL::ShaderNode::Uniform> &E : pnode->uniforms) { if (SL::is_sampler_type(E.value.type)) { + if (E.value.hint == SL::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + E.value.hint == SL::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + E.value.hint == SL::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { + continue; // Don't create uniforms in the generated code for these. + } max_texture_uniforms++; } else { if (E.value.scope == SL::ShaderNode::Uniform::SCOPE_INSTANCE) { @@ -537,6 +542,13 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene p_actions.uniforms->insert(uniform_name, uniform); continue; // Instances are indexed directly, don't need index uniforms. } + + if (uniform.hint == SL::ShaderNode::Uniform::HINT_SCREEN_TEXTURE || + uniform.hint == SL::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE || + uniform.hint == SL::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { + continue; // Don't create uniforms in the generated code for these. + } + if (SL::is_sampler_type(uniform.type)) { // Texture layouts are different for OpenGL GLSL and Vulkan GLSL if (!RS::get_singleton()->is_low_end()) { @@ -892,12 +904,39 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene if (p_default_actions.renames.has(vnode->name)) { code = p_default_actions.renames[vnode->name]; + if (vnode->name == "SCREEN_TEXTURE") { + r_gen_code.uses_screen_texture_mipmaps = true; + } } else { if (shader->uniforms.has(vnode->name)) { //its a uniform! const ShaderLanguage::ShaderNode::Uniform &u = shader->uniforms[vnode->name]; if (u.texture_order >= 0) { - code = _mkid(vnode->name); //texture, use as is + StringName name = vnode->name; + if (u.hint == ShaderLanguage::ShaderNode::Uniform::HINT_SCREEN_TEXTURE) { + name = "SCREEN_TEXTURE"; + if (u.filter >= ShaderLanguage::FILTER_NEAREST_MIPMAP) { + r_gen_code.uses_screen_texture_mipmaps = true; + } + } else if (u.hint == ShaderLanguage::ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE) { + name = "NORMAL_ROUGHNESS_TEXTURE"; + } else if (u.hint == ShaderLanguage::ShaderNode::Uniform::HINT_DEPTH_TEXTURE) { + name = "DEPTH_TEXTURE"; + } else { + name = _mkid(vnode->name); //texture, use as is + } + + if (p_default_actions.renames.has(name)) { + code = p_default_actions.renames[name]; + } else { + code = name; + } + + if (p_actions.usage_flag_pointers.has(name) && !used_flag_pointers.has(name)) { + *p_actions.usage_flag_pointers[name] = true; + used_flag_pointers.insert(name); + } + } else { //a scalar or vector if (u.scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_GLOBAL) { @@ -1155,6 +1194,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene } if (correct_texture_uniform) { + //TODO Needs to detect screen_texture hint as well is_screen_texture = (texture_uniform == "SCREEN_TEXTURE"); String sampler_name; @@ -1404,6 +1444,7 @@ Error ShaderCompiler::compile(RS::ShaderMode p_mode, const String &p_code, Ident r_gen_code.uses_fragment_time = false; r_gen_code.uses_vertex_time = false; r_gen_code.uses_global_textures = false; + r_gen_code.uses_screen_texture_mipmaps = false; used_name_defines.clear(); used_rmode_defines.clear(); diff --git a/servers/rendering/shader_compiler.h b/servers/rendering/shader_compiler.h index 06f42e9f0f..1ad43daf5f 100644 --- a/servers/rendering/shader_compiler.h +++ b/servers/rendering/shader_compiler.h @@ -80,6 +80,7 @@ public: bool uses_global_textures; bool uses_fragment_time; bool uses_vertex_time; + bool uses_screen_texture_mipmaps; }; struct DefaultIdentifierActions { diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index 81e4d5e217..cab92e6e20 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -200,6 +200,9 @@ const char *ShaderLanguage::token_names[TK_MAX] = { "HINT_ANISOTROPY_TEXTURE", "HINT_RANGE", "HINT_INSTANCE_INDEX", + "HINT_SCREEN_TEXTURE", + "HINT_NORMAL_ROUGHNESS_TEXTURE", + "HINT_DEPTH_TEXTURE", "FILTER_NEAREST", "FILTER_LINEAR", "FILTER_NEAREST_MIPMAP", @@ -363,6 +366,10 @@ const ShaderLanguage::KeyWord ShaderLanguage::keyword_list[] = { { TK_HINT_ROUGHNESS_A, "hint_roughness_a", CF_UNSPECIFIED, {}, {} }, { TK_HINT_ROUGHNESS_NORMAL_TEXTURE, "hint_roughness_normal", CF_UNSPECIFIED, {}, {} }, { TK_HINT_ROUGHNESS_GRAY, "hint_roughness_gray", CF_UNSPECIFIED, {}, {} }, + { TK_HINT_SCREEN_TEXTURE, "hint_screen_texture", CF_UNSPECIFIED, {}, {} }, + { TK_HINT_NORMAL_ROUGHNESS_TEXTURE, "hint_normal_roughness_texture", CF_UNSPECIFIED, {}, {} }, + { TK_HINT_DEPTH_TEXTURE, "hint_depth_texture", CF_UNSPECIFIED, {}, {} }, + { TK_FILTER_NEAREST, "filter_nearest", CF_UNSPECIFIED, {}, {} }, { TK_FILTER_LINEAR, "filter_linear", CF_UNSPECIFIED, {}, {} }, { TK_FILTER_NEAREST_MIPMAP, "filter_nearest_mipmap", CF_UNSPECIFIED, {}, {} }, @@ -1096,6 +1103,15 @@ String ShaderLanguage::get_uniform_hint_name(ShaderNode::Uniform::Hint p_hint) { case ShaderNode::Uniform::HINT_ANISOTROPY: { result = "hint_anisotropy"; } break; + case ShaderNode::Uniform::HINT_SCREEN_TEXTURE: { + result = "hint_screen_texture"; + } break; + case ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE: { + result = "hint_normal_roughness_texture"; + } break; + case ShaderNode::Uniform::HINT_DEPTH_TEXTURE: { + result = "hint_depth_texture"; + } break; default: break; } @@ -8605,6 +8621,15 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f return ERR_PARSE_ERROR; } } break; + case TK_HINT_SCREEN_TEXTURE: { + new_hint = ShaderNode::Uniform::HINT_SCREEN_TEXTURE; + } break; + case TK_HINT_NORMAL_ROUGHNESS_TEXTURE: { + new_hint = ShaderNode::Uniform::HINT_NORMAL_ROUGHNESS_TEXTURE; + } break; + case TK_HINT_DEPTH_TEXTURE: { + new_hint = ShaderNode::Uniform::HINT_DEPTH_TEXTURE; + } break; case TK_FILTER_NEAREST: { new_filter = FILTER_NEAREST; } break; @@ -8629,6 +8654,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f case TK_REPEAT_ENABLE: { new_repeat = REPEAT_ENABLE; } break; + default: break; } @@ -8653,9 +8679,9 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f if (new_filter != FILTER_DEFAULT) { if (uniform.filter != FILTER_DEFAULT) { if (uniform.filter == new_filter) { - _set_error(vformat(RTR("Duplicated hint: '%s'."), get_texture_filter_name(new_filter))); + _set_error(vformat(RTR("Duplicated filter mode: '%s'."), get_texture_filter_name(new_filter))); } else { - _set_error(vformat(RTR("Redefinition of hint: '%s'. The filter mode has already been set to '%s'."), get_texture_filter_name(new_filter), get_texture_filter_name(uniform.filter))); + _set_error(vformat(RTR("Redefinition of filter mode: '%s'. The filter mode has already been set to '%s'."), get_texture_filter_name(new_filter), get_texture_filter_name(uniform.filter))); } return ERR_PARSE_ERROR; } else { @@ -8666,9 +8692,9 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f if (new_repeat != REPEAT_DEFAULT) { if (uniform.repeat != REPEAT_DEFAULT) { if (uniform.repeat == new_repeat) { - _set_error(vformat(RTR("Duplicated hint: '%s'."), get_texture_repeat_name(new_repeat))); + _set_error(vformat(RTR("Duplicated repeat mode: '%s'."), get_texture_repeat_name(new_repeat))); } else { - _set_error(vformat(RTR("Redefinition of hint: '%s'. The repeat mode has already been set to '%s'."), get_texture_repeat_name(new_repeat), get_texture_repeat_name(uniform.repeat))); + _set_error(vformat(RTR("Redefinition of repeat mode: '%s'. The repeat mode has already been set to '%s'."), get_texture_repeat_name(new_repeat), get_texture_repeat_name(uniform.repeat))); } return ERR_PARSE_ERROR; } else { @@ -10309,6 +10335,9 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_ options.push_back("hint_roughness_gray"); options.push_back("hint_roughness_normal"); options.push_back("hint_roughness_r"); + options.push_back("hint_screen_texture"); + options.push_back("hint_normal_roughness_texture"); + options.push_back("hint_depth_texture"); options.push_back("source_color"); options.push_back("repeat_enable"); options.push_back("repeat_disable"); diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h index bfec6e1df6..75b713d167 100644 --- a/servers/rendering/shader_language.h +++ b/servers/rendering/shader_language.h @@ -176,6 +176,9 @@ public: TK_HINT_SOURCE_COLOR, TK_HINT_RANGE, TK_HINT_INSTANCE_INDEX, + TK_HINT_SCREEN_TEXTURE, + TK_HINT_NORMAL_ROUGHNESS_TEXTURE, + TK_HINT_DEPTH_TEXTURE, TK_FILTER_NEAREST, TK_FILTER_LINEAR, TK_FILTER_NEAREST_MIPMAP, @@ -667,6 +670,9 @@ public: HINT_DEFAULT_WHITE, HINT_DEFAULT_TRANSPARENT, HINT_ANISOTROPY, + HINT_SCREEN_TEXTURE, + HINT_NORMAL_ROUGHNESS_TEXTURE, + HINT_DEPTH_TEXTURE, HINT_MAX }; diff --git a/servers/rendering/shader_preprocessor.cpp b/servers/rendering/shader_preprocessor.cpp index d118c73c4a..3766477070 100644 --- a/servers/rendering/shader_preprocessor.cpp +++ b/servers/rendering/shader_preprocessor.cpp @@ -349,6 +349,8 @@ void ShaderPreprocessor::process_directive(Tokenizer *p_tokenizer) { process_ifdef(p_tokenizer); } else if (directive == "ifndef") { process_ifndef(p_tokenizer); + } else if (directive == "elif") { + process_elif(p_tokenizer); } else if (directive == "else") { process_else(p_tokenizer); } else if (directive == "endif") { @@ -415,10 +417,67 @@ void ShaderPreprocessor::process_define(Tokenizer *p_tokenizer) { } } +void ShaderPreprocessor::process_elif(Tokenizer *p_tokenizer) { + const int line = p_tokenizer->get_line(); + + if (state->current_branch == nullptr || state->current_branch->else_defined) { + set_error(RTR("Unmatched elif."), line); + return; + } + if (state->previous_region != nullptr) { + state->previous_region->to_line = line - 1; + } + + String body = tokens_to_string(p_tokenizer->advance('\n')).strip_edges(); + if (body.is_empty()) { + set_error(RTR("Missing condition."), line); + return; + } + + Error error = expand_condition(body, line, body); + if (error != OK) { + return; + } + + error = expand_macros(body, line, body); + if (error != OK) { + return; + } + + Expression expression; + Vector<String> names; + error = expression.parse(body, names); + if (error != OK) { + set_error(expression.get_error_text(), line); + return; + } + + Variant v = expression.execute(Array(), nullptr, false); + if (v.get_type() == Variant::NIL) { + set_error(RTR("Condition evaluation error."), line); + return; + } + + bool skip = false; + for (int i = 0; i < state->current_branch->conditions.size(); i++) { + if (state->current_branch->conditions[i]) { + skip = true; + break; + } + } + + bool success = !skip && v.booleanize(); + start_branch_condition(p_tokenizer, success, true); + + if (state->save_regions) { + add_region(line + 1, success, state->previous_region->parent); + } +} + void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) { const int line = p_tokenizer->get_line(); - if (state->skip_stack_else.is_empty()) { + if (state->current_branch == nullptr || state->current_branch->else_defined) { set_error(RTR("Unmatched else."), line); return; } @@ -428,17 +487,14 @@ void ShaderPreprocessor::process_else(Tokenizer *p_tokenizer) { p_tokenizer->advance('\n'); - bool skip = state->skip_stack_else[state->skip_stack_else.size() - 1]; - state->skip_stack_else.remove_at(state->skip_stack_else.size() - 1); - - Vector<SkippedCondition *> vec = state->skipped_conditions[state->current_filename]; - int index = vec.size() - 1; - if (index >= 0) { - SkippedCondition *cond = vec[index]; - if (cond->end_line == -1) { - cond->end_line = p_tokenizer->get_line(); + bool skip = false; + for (int i = 0; i < state->current_branch->conditions.size(); i++) { + if (state->current_branch->conditions[i]) { + skip = true; + break; } } + state->current_branch->else_defined = true; if (state->save_regions) { add_region(line + 1, !skip, state->previous_region->parent); @@ -462,16 +518,10 @@ void ShaderPreprocessor::process_endif(Tokenizer *p_tokenizer) { state->previous_region = state->previous_region->parent; } - Vector<SkippedCondition *> vec = state->skipped_conditions[state->current_filename]; - int index = vec.size() - 1; - if (index >= 0) { - SkippedCondition *cond = vec[index]; - if (cond->end_line == -1) { - cond->end_line = p_tokenizer->get_line(); - } - } - p_tokenizer->advance('\n'); + + state->current_branch = state->current_branch->parent; + state->branches.pop_back(); } void ShaderPreprocessor::process_if(Tokenizer *p_tokenizer) { @@ -483,7 +533,12 @@ void ShaderPreprocessor::process_if(Tokenizer *p_tokenizer) { return; } - Error error = expand_macros(body, line, body); + Error error = expand_condition(body, line, body); + if (error != OK) { + return; + } + + error = expand_macros(body, line, body); if (error != OK) { return; } @@ -703,24 +758,19 @@ void ShaderPreprocessor::add_region(int p_line, bool p_enabled, Region *p_parent state->previous_region = &state->regions[region.file].push_back(region)->get(); } -void ShaderPreprocessor::start_branch_condition(Tokenizer *p_tokenizer, bool p_success) { - state->condition_depth++; - - if (p_success) { - state->skip_stack_else.push_back(true); +void ShaderPreprocessor::start_branch_condition(Tokenizer *p_tokenizer, bool p_success, bool p_continue) { + if (!p_continue) { + state->condition_depth++; + state->current_branch = &state->branches.push_back(Branch(p_success, state->current_branch))->get(); } else { - SkippedCondition *cond = memnew(SkippedCondition()); - cond->start_line = p_tokenizer->get_line(); - state->skipped_conditions[state->current_filename].push_back(cond); - + state->current_branch->conditions.push_back(p_success); + } + if (!p_success) { Vector<String> ends; + ends.push_back("elif"); ends.push_back("else"); ends.push_back("endif"); - if (next_directive(p_tokenizer, ends) == "else") { - state->skip_stack_else.push_back(false); - } else { - state->skip_stack_else.push_back(true); - } + next_directive(p_tokenizer, ends); } } @@ -737,47 +787,173 @@ void ShaderPreprocessor::expand_output_macros(int p_start, int p_line_number) { add_to_output(line); } -Error ShaderPreprocessor::expand_macros(const String &p_string, int p_line, String &r_expanded) { - Vector<Pair<String, Define *>> active_defines; - active_defines.resize(state->defines.size()); - int index = 0; - for (const RBMap<String, Define *>::Element *E = state->defines.front(); E; E = E->next()) { - active_defines.set(index++, Pair<String, Define *>(E->key(), E->get())); +Error ShaderPreprocessor::expand_condition(const String &p_string, int p_line, String &r_expanded) { + // Checks bracket count to be even + check the cursor position. + { + int bracket_start_count = 0; + int bracket_end_count = 0; + + for (int i = 0; i < p_string.size(); i++) { + switch (p_string[i]) { + case CURSOR: + state->completion_type = COMPLETION_TYPE_CONDITION; + break; + case '(': + bracket_start_count++; + break; + case ')': + bracket_end_count++; + break; + } + } + if (bracket_start_count > bracket_end_count) { + _set_expected_error(")", p_line); + return FAILED; + } + if (bracket_end_count > bracket_start_count) { + _set_expected_error("(", p_line); + return FAILED; + } } - return expand_macros(p_string, p_line, active_defines, r_expanded); + String result = p_string; + + int index = 0; + int index_start = 0; + int index_end = 0; + + while (find_match(result, "defined", index, index_start)) { + bool open_bracket = false; + bool found_word = false; + bool word_completed = false; + + LocalVector<char32_t> text; + int post_bracket_index = -1; + int size = result.size(); + + for (int i = (index_start - 1); i < size; i++) { + char32_t c = result[i]; + if (c == 0) { + if (found_word) { + word_completed = true; + } + break; + } + char32_t cs[] = { c, '\0' }; + String s = String(cs); + bool is_space = is_char_space(c); + + if (word_completed) { + if (c == ')') { + continue; + } + if (c == '|' || c == '&') { + if (open_bracket) { + _set_unexpected_token_error(s, p_line); + return FAILED; + } + break; + } else if (!is_space) { + _set_unexpected_token_error(s, p_line); + return FAILED; + } + } else if (is_space) { + if (found_word && !open_bracket) { + index_end = i; + word_completed = true; + } + } else if (c == '(') { + if (open_bracket) { + _set_unexpected_token_error(s, p_line); + return FAILED; + } + open_bracket = true; + } else if (c == ')') { + if (open_bracket) { + if (!found_word) { + _set_unexpected_token_error(s, p_line); + return FAILED; + } + open_bracket = false; + post_bracket_index = i + 1; + } else { + index_end = i; + } + word_completed = true; + } else if (is_char_word(c)) { + text.push_back(c); + found_word = true; + } else { + _set_unexpected_token_error(s, p_line); + return FAILED; + } + } + + if (word_completed) { + if (open_bracket) { + _set_expected_error(")", p_line); + return FAILED; + } + if (post_bracket_index != -1) { + index_end = post_bracket_index; + } + + String body = state->defines.has(vector_to_string(text)) ? "true" : "false"; + String temp = result; + + result = result.substr(0, index) + body; + index_start = result.length(); + if (index_end > 0) { + result += temp.substr(index_end); + } + } else { + set_error(RTR("Invalid macro name."), p_line); + return FAILED; + } + } + r_expanded = result; + return OK; } -Error ShaderPreprocessor::expand_macros(const String &p_string, int p_line, Vector<Pair<String, Define *>> p_defines, String &r_expanded) { - r_expanded = p_string; - // When expanding macros we must only evaluate them once. - // Later we continue expanding but with the already - // evaluated macros removed. - for (int i = 0; i < p_defines.size(); i++) { - Pair<String, Define *> define_pair = p_defines[i]; - - Error error = expand_macros_once(r_expanded, p_line, define_pair, r_expanded); - if (error != OK) { - return error; +Error ShaderPreprocessor::expand_macros(const String &p_string, int p_line, String &r_expanded) { + String iterative = p_string; + int pass_count = 0; + bool expanded = true; + + while (expanded) { + expanded = false; + + // As long as we find something to expand, keep going. + for (const RBMap<String, Define *>::Element *E = state->defines.front(); E; E = E->next()) { + if (expand_macros_once(iterative, p_line, E, iterative)) { + expanded = true; + } } - // Remove expanded macro and recursively replace remaining. - p_defines.remove_at(i); - return expand_macros(r_expanded, p_line, p_defines, r_expanded); + pass_count++; + if (pass_count > 50) { + set_error(RTR("Macro expansion limit exceeded."), p_line); + break; + } } + r_expanded = iterative; + + if (!state->error.is_empty()) { + return FAILED; + } return OK; } -Error ShaderPreprocessor::expand_macros_once(const String &p_line, int p_line_number, Pair<String, Define *> p_define_pair, String &r_expanded) { +bool ShaderPreprocessor::expand_macros_once(const String &p_line, int p_line_number, const RBMap<String, Define *>::Element *p_define_pair, String &r_expanded) { String result = p_line; - const String &key = p_define_pair.first; - const Define *define = p_define_pair.second; + const String &key = p_define_pair->key(); + const Define *define = p_define_pair->value(); int index_start = 0; int index = 0; - while (find_match(result, key, index, index_start)) { + if (find_match(result, key, index, index_start)) { String body = define->body; if (define->arguments.size() > 0) { // Complex macro with arguments. @@ -785,14 +961,14 @@ Error ShaderPreprocessor::expand_macros_once(const String &p_line, int p_line_nu int args_end = p_line.find(")", args_start); if (args_start == -1 || args_end == -1) { set_error(RTR("Missing macro argument parenthesis."), p_line_number); - return FAILED; + return false; } String values = result.substr(args_start + 1, args_end - (args_start + 1)); Vector<String> args = values.split(","); if (args.size() != define->arguments.size()) { set_error(RTR("Invalid macro argument count."), p_line_number); - return FAILED; + return false; } // Insert macro arguments into the body. @@ -814,11 +990,13 @@ Error ShaderPreprocessor::expand_macros_once(const String &p_line, int p_line_nu // Manually reset index_start to where the body value of the define finishes. // This ensures we don't skip another instance of this macro in the string. index_start = index + body.length() + 1; - break; } + + r_expanded = result; + return true; } - r_expanded = result; - return OK; + + return false; } bool ShaderPreprocessor::find_match(const String &p_string, const String &p_value, int &r_index, int &r_index_start) { @@ -909,12 +1087,6 @@ void ShaderPreprocessor::clear() { memdelete(E->get()); } - for (const RBMap<String, Vector<SkippedCondition *>>::Element *E = state->skipped_conditions.front(); E; E = E->next()) { - for (SkippedCondition *condition : E->get()) { - memdelete(condition); - } - } - memdelete(state); } state_owner = false; @@ -1030,7 +1202,7 @@ Error ShaderPreprocessor::preprocess(const String &p_code, const String &p_filen switch (pp_state.completion_type) { case COMPLETION_TYPE_DIRECTIVE: { List<String> options; - get_keyword_list(&options, true); + get_keyword_list(&options, true, true); for (const String &E : options) { ScriptLanguage::CodeCompletionOption option(E, ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); @@ -1049,6 +1221,11 @@ Error ShaderPreprocessor::preprocess(const String &p_code, const String &p_filen } } break; + case COMPLETION_TYPE_CONDITION: { + ScriptLanguage::CodeCompletionOption option("defined", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + r_completion_options->push_back(option); + + } break; case COMPLETION_TYPE_INCLUDE_PATH: { if (p_include_completion_func && r_completion_options) { p_include_completion_func(r_completion_options); @@ -1062,8 +1239,12 @@ Error ShaderPreprocessor::preprocess(const String &p_code, const String &p_filen return err; } -void ShaderPreprocessor::get_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords) { +void ShaderPreprocessor::get_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords, bool p_ignore_context_keywords) { r_keywords->push_back("define"); + if (!p_ignore_context_keywords) { + r_keywords->push_back("defined"); + } + r_keywords->push_back("elif"); if (p_include_shader_keywords) { r_keywords->push_back("else"); } diff --git a/servers/rendering/shader_preprocessor.h b/servers/rendering/shader_preprocessor.h index 41b574298d..b4e7c7199f 100644 --- a/servers/rendering/shader_preprocessor.h +++ b/servers/rendering/shader_preprocessor.h @@ -50,6 +50,7 @@ public: COMPLETION_TYPE_DIRECTIVE, COMPLETION_TYPE_PRAGMA_DIRECTIVE, COMPLETION_TYPE_PRAGMA, + COMPLETION_TYPE_CONDITION, COMPLETION_TYPE_INCLUDE_PATH, }; @@ -130,14 +131,23 @@ private: String body; }; - struct SkippedCondition { - int start_line = -1; - int end_line = -1; + struct Branch { + Vector<bool> conditions; + Branch *parent = nullptr; + bool else_defined = false; + + Branch() {} + + Branch(bool p_condition, Branch *p_parent) : + parent(p_parent) { + conditions.push_back(p_condition); + } }; struct State { RBMap<String, Define *> defines; - Vector<bool> skip_stack_else; + List<Branch> branches; + Branch *current_branch = nullptr; int condition_depth = 0; RBSet<String> includes; List<uint64_t> cyclic_include_hashes; // Holds code hash of includes. @@ -149,7 +159,6 @@ private: bool save_regions = false; RBMap<String, List<Region>> regions; Region *previous_region = nullptr; - RBMap<String, Vector<SkippedCondition *>> skipped_conditions; bool disabled = false; CompletionType completion_type = COMPLETION_TYPE_NONE; HashSet<Ref<ShaderInclude>> shader_includes; @@ -167,8 +176,17 @@ private: static String vector_to_string(const LocalVector<char32_t> &p_v, int p_start = 0, int p_end = -1); static String tokens_to_string(const LocalVector<Token> &p_tokens); + void _set_expected_error(const String &p_what, int p_line) { + set_error(vformat(RTR("Expected a '%s'."), p_what), p_line); + } + + void _set_unexpected_token_error(const String &p_what, int p_line) { + set_error(vformat(RTR("Unexpected token '%s'."), p_what), p_line); + } + void process_directive(Tokenizer *p_tokenizer); void process_define(Tokenizer *p_tokenizer); + void process_elif(Tokenizer *p_tokenizer); void process_else(Tokenizer *p_tokenizer); void process_endif(Tokenizer *p_tokenizer); void process_if(Tokenizer *p_tokenizer); @@ -179,12 +197,12 @@ private: void process_undef(Tokenizer *p_tokenizer); void add_region(int p_line, bool p_enabled, Region *p_parent_region); - void start_branch_condition(Tokenizer *p_tokenizer, bool p_success); + void start_branch_condition(Tokenizer *p_tokenizer, bool p_success, bool p_continue = false); + Error expand_condition(const String &p_string, int p_line, String &r_result); void expand_output_macros(int p_start, int p_line); Error expand_macros(const String &p_string, int p_line, String &r_result); - Error expand_macros(const String &p_string, int p_line, Vector<Pair<String, Define *>> p_defines, String &r_result); - Error expand_macros_once(const String &p_line, int p_line_number, Pair<String, Define *> p_define_pair, String &r_expanded); + bool expand_macros_once(const String &p_line, int p_line_number, const RBMap<String, Define *>::Element *p_define_pair, String &r_expanded); bool find_match(const String &p_string, const String &p_value, int &r_index, int &r_index_start); String next_directive(Tokenizer *p_tokenizer, const Vector<String> &p_directives); @@ -202,7 +220,7 @@ public: Error preprocess(const String &p_code, const String &p_filename, String &r_result, String *r_error_text = nullptr, List<FilePosition> *r_error_position = nullptr, List<Region> *r_regions = nullptr, HashSet<Ref<ShaderInclude>> *r_includes = nullptr, List<ScriptLanguage::CodeCompletionOption> *r_completion_options = nullptr, IncludeCompletionFunction p_include_completion_func = nullptr); - static void get_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords); + static void get_keyword_list(List<String> *r_keywords, bool p_include_shader_keywords, bool p_ignore_context_keywords = false); static void get_pragma_list(List<String> *r_pragmas); ShaderPreprocessor(); diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index a54d4f0384..dcdc56d948 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -627,7 +627,7 @@ Error RenderingServer::_surface_set_data(Array p_arrays, uint32_t p_format, uint const int *src = indices.ptr(); for (int i = 0; i < p_index_array_len; i++) { - if (p_vertex_array_len < (1 << 16)) { + if (p_vertex_array_len < (1 << 16) && p_vertex_array_len > 0) { uint16_t v = src[i]; memcpy(&iw[i * 2], &v, 2); @@ -836,9 +836,8 @@ void RenderingServer::mesh_surface_make_offsets_from_format(uint32_t p_format, i break; } /* determine whether using 16 or 32 bits indices */ - if (p_vertex_len >= (1 << 16)) { + if (p_vertex_len >= (1 << 16) || p_vertex_len == 0) { elem_size = 4; - } else { elem_size = 2; } @@ -909,8 +908,6 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa } } - ERR_FAIL_COND_V((format & RS::ARRAY_FORMAT_VERTEX) == 0, ERR_INVALID_PARAMETER); // Mandatory - if (p_blend_shapes.size()) { // Validate format for morphs. for (int i = 0; i < p_blend_shapes.size(); i++) { @@ -944,6 +941,12 @@ Error RenderingServer::mesh_create_surface_data_from_arrays(SurfaceData *r_surfa uint32_t mask = (1 << ARRAY_MAX) - 1; format |= (~mask) & p_compress_format; // Make the full format. + if ((format & RS::ARRAY_FORMAT_VERTEX) == 0 && !(format & RS::ARRAY_FLAG_USES_EMPTY_VERTEX_ARRAY)) { + ERR_PRINT("Mesh created without vertex array. This mesh will not be visible with the default shader. If using an empty vertex array is intentional, create the mesh with the ARRAY_FLAG_USES_EMPTY_VERTEX_ARRAY flag to silence this error."); + // Set the flag here after warning to suppress errors down the pipeline. + format |= RS::ARRAY_FLAG_USES_EMPTY_VERTEX_ARRAY; + } + int vertex_array_size = vertex_element_size * array_len; int attrib_array_size = attrib_element_size * array_len; int skin_array_size = skin_element_size * array_len; @@ -1378,7 +1381,7 @@ Array RenderingServer::mesh_create_arrays_from_surface_data(const SurfaceData &p Vector<uint8_t> attrib_data = p_data.attribute_data; Vector<uint8_t> skin_data = p_data.skin_data; - ERR_FAIL_COND_V(vertex_data.size() == 0, Array()); + ERR_FAIL_COND_V(vertex_data.size() == 0 && (p_data.format & RS::ARRAY_FORMAT_VERTEX), Array()); int vertex_len = p_data.vertex_count; Vector<uint8_t> index_data = p_data.index_data; diff --git a/servers/rendering_server.h b/servers/rendering_server.h index a2fe9caf19..d04c62bfd2 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -244,7 +244,7 @@ public: enum ArrayFormat { /* ARRAY FORMAT FLAGS */ - ARRAY_FORMAT_VERTEX = 1 << ARRAY_VERTEX, // Mandatory + ARRAY_FORMAT_VERTEX = 1 << ARRAY_VERTEX, ARRAY_FORMAT_NORMAL = 1 << ARRAY_NORMAL, ARRAY_FORMAT_TANGENT = 1 << ARRAY_TANGENT, ARRAY_FORMAT_COLOR = 1 << ARRAY_COLOR, @@ -262,17 +262,19 @@ public: ARRAY_FORMAT_CUSTOM_BASE = (ARRAY_INDEX + 1), ARRAY_FORMAT_CUSTOM_BITS = 3, + ARRAY_FORMAT_CUSTOM_MASK = 0x7, ARRAY_FORMAT_CUSTOM0_SHIFT = (ARRAY_FORMAT_CUSTOM_BASE + 0), ARRAY_FORMAT_CUSTOM1_SHIFT = (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS), ARRAY_FORMAT_CUSTOM2_SHIFT = (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * 2), ARRAY_FORMAT_CUSTOM3_SHIFT = (ARRAY_FORMAT_CUSTOM_BASE + ARRAY_FORMAT_CUSTOM_BITS * 3), - ARRAY_FORMAT_CUSTOM_MASK = 0x7, ARRAY_COMPRESS_FLAGS_BASE = (ARRAY_INDEX + 1 + 12), ARRAY_FLAG_USE_2D_VERTICES = 1 << (ARRAY_COMPRESS_FLAGS_BASE + 0), ARRAY_FLAG_USE_DYNAMIC_UPDATE = 1 << (ARRAY_COMPRESS_FLAGS_BASE + 1), ARRAY_FLAG_USE_8_BONE_WEIGHTS = 1 << (ARRAY_COMPRESS_FLAGS_BASE + 2), + + ARRAY_FLAG_USES_EMPTY_VERTEX_ARRAY = 1 << (ARRAY_INDEX + 1 + 15), }; enum PrimitiveType { diff --git a/tests/core/object/test_object.h b/tests/core/object/test_object.h index 88a3e4ccad..f5c5de7fdf 100644 --- a/tests/core/object/test_object.h +++ b/tests/core/object/test_object.h @@ -82,6 +82,12 @@ public: Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override { return Variant::PACKED_FLOAT32_ARRAY; } + bool property_can_revert(const StringName &p_name) const override { + return false; + }; + bool property_get_revert(const StringName &p_name, Variant &r_ret) const override { + return false; + }; void get_method_list(List<MethodInfo> *p_list) const override { } bool has_method(const StringName &p_method) const override { |