summaryrefslogtreecommitdiffstats
path: root/modules/mono
diff options
context:
space:
mode:
Diffstat (limited to 'modules/mono')
-rw-r--r--modules/mono/SdkPackageVersions.props2
-rw-r--r--modules/mono/csharp_script.cpp523
-rw-r--r--modules/mono/csharp_script.h16
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs133
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs133
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Generic.cs5
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/Godot.SourceGenerators.Sample.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs106
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs148
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.csproj2
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs134
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs46
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs3
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs176
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs (renamed from modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs)195
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs19
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs527
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs293
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs23
-rw-r--r--modules/mono/editor/bindings_generator.cpp7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/ExportAttribute.cs2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs4
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs26
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs255
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs13
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs7
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs21
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj2
-rw-r--r--modules/mono/glue/runtime_interop.cpp74
-rw-r--r--modules/mono/managed_callable.cpp2
-rw-r--r--modules/mono/mono_gc_handle.h9
-rw-r--r--modules/mono/mono_gd/gd_mono_cache.h28
34 files changed, 2175 insertions, 766 deletions
diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props
index 2bd60bb719..9947335b2f 100644
--- a/modules/mono/SdkPackageVersions.props
+++ b/modules/mono/SdkPackageVersions.props
@@ -2,6 +2,6 @@
<PropertyGroup>
<PackageFloatingVersion_Godot>4.0.*-*</PackageFloatingVersion_Godot>
<PackageVersion_Godot_NET_Sdk>4.0.0-dev7</PackageVersion_Godot_NET_Sdk>
- <PackageVersion_Godot_SourceGenerators>4.0.0-dev6</PackageVersion_Godot_SourceGenerators>
+ <PackageVersion_Godot_SourceGenerators>4.0.0-dev7</PackageVersion_Godot_SourceGenerators>
</PropertyGroup>
</Project>
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index ade436d3e0..99bd3edb9e 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -104,7 +104,7 @@ Error CSharpLanguage::execute_file(const String &p_path) {
return OK;
}
-extern void *godotsharp_pinvoke_funcs[178];
+extern void *godotsharp_pinvoke_funcs[179];
[[maybe_unused]] volatile void **do_not_strip_godotsharp_pinvoke_funcs;
#ifdef TOOLS_ENABLED
extern void *godotsharp_editor_pinvoke_funcs[30];
@@ -1263,17 +1263,24 @@ void CSharpLanguage::release_script_gchandle(MonoGCHandleData &p_gchandle) {
}
}
-void CSharpLanguage::release_script_gchandle(void *p_expected_mono_obj_unused, MonoGCHandleData &p_gchandle) {
-#warning KNOWN BUG. DO NOT USE THIS IN PRODUCTION
- // KNOWN BUG:
- // I removed the patch from commit e558e1ec09aa27852426bbd24dfa21e9b60cfbfc.
- // This may cause data races. Re-implementing it without the Mono embedding API would be
- // too painful and would make the code even more of a mess than it already was.
- // We will switch from scripts to the new extension system before a release with .NET 6 support.
- // The problem the old patch was working around won't be present at all with the new extension system.
+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);
+ if (!r_gchandle.is_released() && r_gchandle.get_intptr() == p_gchandle_to_free) {
+ r_gchandle.release();
+ }
+ }
+}
- (void)p_expected_mono_obj_unused;
- return release_script_gchandle(p_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() {
@@ -1309,6 +1316,10 @@ bool CSharpLanguage::setup_csharp_script_binding(CSharpScriptBinding &r_script_b
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() + "'.");
+#ifdef DEBUG_ENABLED
+ CRASH_COND(!r_script_binding.gchandle.is_released());
+#endif
+
GCHandleIntPtr strong_gchandle =
GDMonoCache::managed_callbacks.ScriptManagerBridge_CreateManagedForGodotObjectBinding(&type_name, p_object);
@@ -1419,9 +1430,9 @@ GDNativeBool CSharpLanguage::_instance_binding_reference_callback(void *p_token,
// Release the current weak handle and replace it with a strong handle.
GCHandleIntPtr old_gchandle = gchandle.get_intptr();
- gchandle.handle = GCHandleIntPtr(); // No longer owns the handle (released by swap function)
+ gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function)
- GCHandleIntPtr new_gchandle;
+ GCHandleIntPtr new_gchandle = { nullptr };
bool create_weak = false;
bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType(
old_gchandle, &new_gchandle, create_weak);
@@ -1443,9 +1454,9 @@ GDNativeBool CSharpLanguage::_instance_binding_reference_callback(void *p_token,
// Release the current strong handle and replace it with a weak handle.
GCHandleIntPtr old_gchandle = gchandle.get_intptr();
- gchandle.handle = GCHandleIntPtr(); // No longer owns the handle (released by swap function)
+ gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function)
- GCHandleIntPtr new_gchandle;
+ GCHandleIntPtr new_gchandle = { nullptr };
bool create_weak = true;
bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType(
old_gchandle, &new_gchandle, create_weak);
@@ -1569,10 +1580,10 @@ void CSharpLanguage::tie_user_managed_to_unmanaged(GCHandleIntPtr p_gchandle_int
Ref<CSharpScript> script = p_script;
- CSharpScript::initialize_for_managed_type(script);
-
CRASH_COND(script.is_null());
+ CSharpScript::initialize_for_managed_type(script);
+
CSharpInstance *csharp_instance = CSharpInstance::create_for_managed_type(p_unmanaged, script.ptr(), gchandle);
p_unmanaged->set_script_and_instance(script, csharp_instance);
@@ -1920,7 +1931,7 @@ bool CSharpInstance::_internal_new_managed() {
return true;
}
-void CSharpInstance::mono_object_disposed() {
+void CSharpInstance::mono_object_disposed(GCHandleIntPtr p_gchandle_to_free) {
// Must make sure event signals are not left dangling
disconnect_event_signals();
@@ -1928,10 +1939,10 @@ void CSharpInstance::mono_object_disposed() {
CRASH_COND(base_ref_counted);
CRASH_COND(gchandle.is_released());
#endif
- CSharpLanguage::get_singleton()->release_script_gchandle(nullptr, gchandle);
+ CSharpLanguage::get_singleton()->release_script_gchandle_thread_safe(p_gchandle_to_free, gchandle);
}
-void CSharpInstance::mono_object_disposed_baseref(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());
@@ -1947,7 +1958,7 @@ void CSharpInstance::mono_object_disposed_baseref(bool p_is_finalizer, bool &r_d
r_delete_owner = true;
} else {
r_delete_owner = false;
- CSharpLanguage::get_singleton()->release_script_gchandle(nullptr, 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
@@ -2000,9 +2011,9 @@ void CSharpInstance::refcount_incremented() {
// Release the current weak handle and replace it with a strong handle.
GCHandleIntPtr old_gchandle = gchandle.get_intptr();
- gchandle.handle = GCHandleIntPtr(); // No longer owns the handle (released by swap function)
+ gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function)
- GCHandleIntPtr new_gchandle;
+ GCHandleIntPtr new_gchandle = { nullptr };
bool create_weak = false;
bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType(
old_gchandle, &new_gchandle, create_weak);
@@ -2032,9 +2043,9 @@ bool CSharpInstance::refcount_decremented() {
// Release the current strong handle and replace it with a weak handle.
GCHandleIntPtr old_gchandle = gchandle.get_intptr();
- gchandle.handle = GCHandleIntPtr(); // No longer owns the handle (released by swap function)
+ gchandle.handle = { nullptr }; // No longer owns the handle (released by swap function)
- GCHandleIntPtr new_gchandle;
+ GCHandleIntPtr new_gchandle = { nullptr };
bool create_weak = true;
bool target_alive = GDMonoCache::managed_callbacks.ScriptManagerBridge_SwapGCHandleForType(
old_gchandle, &new_gchandle, create_weak);
@@ -2207,62 +2218,6 @@ void CSharpScript::_update_exports_values(HashMap<StringName, Variant> &values,
base_cache->_update_exports_values(values, propnames);
}
}
-
-void CSharpScript::_update_member_info_no_exports() {
- if (exports_invalidated) {
- exports_invalidated = false;
-
- member_info.clear();
-
-#warning TODO
-#if 0
- 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();
- }
-#endif
- }
-}
#endif
bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_update) {
@@ -2282,170 +2237,61 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
if (exports_invalidated)
#endif
{
-#warning TODO
-#if 0
- GD_MONO_SCOPE_THREAD_ATTACH;
+ exports_invalidated = false;
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 *>(GDMonoCache::cached_data.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
-
- MonoException *exc = nullptr;
- GDMonoUtils::dispose(tmp_object, &exc);
+ }
+ }
+ });
- if (exc) {
- ERR_PRINT("Exception thrown from method Dispose() of temporary MonoObject:");
- GDMonoUtils::debug_print_unhandled_exception(exc);
- }
+ 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];
- GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
- tmp_object = nullptr;
+ StringName name = *reinterpret_cast<const StringName *>(&def_val_pair.name);
+ Variant value = *reinterpret_cast<const Variant *>(&def_val_pair.value);
- 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
-
-#endif // #if 0
}
#ifdef TOOLS_ENABLED
@@ -2472,237 +2318,6 @@ bool CSharpScript::_update_exports(PlaceHolderScriptInstance *p_instance_to_upda
return changed;
}
-#warning TODO
-#if 0
-/**
- * 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(GDMonoCache::cached_data.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(GDMonoCache::cached_data.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(GDMonoCache::cached_data.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(GDMonoCache::cached_data.field_ExportAttribute_hint->get_int_value(attr));
- hint_string = GDMonoCache::cached_data.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 && GDMonoCache::cached_data.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
-#endif
-
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();
@@ -2742,9 +2357,7 @@ void CSharpScript::initialize_for_managed_type(Ref<CSharpScript> p_script) {
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.
@@ -3125,7 +2738,7 @@ void CSharpScript::get_script_property_list(List<PropertyInfo> *r_list) const {
for (const KeyValue<StringName, PropertyInfo> &E : member_info) {
props.push_front(E.value);
}
-#endif // TOOLS_ENABLED
+#endif
for (const PropertyInfo &prop : props) {
r_list->push_back(prop);
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 9be4c9c130..d4891395ba 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -116,7 +116,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
@@ -130,14 +129,6 @@ private:
bool _update_exports(PlaceHolderScriptInstance *p_instance_to_update = nullptr);
-#warning TODO
-#if 0
- 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
-#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);
@@ -261,13 +252,13 @@ public:
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();
+ 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(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_signal(const StringName &p_event_signal);
void disconnect_event_signals();
@@ -384,7 +375,8 @@ public:
#endif
static void release_script_gchandle(MonoGCHandleData &p_gchandle);
- static void release_script_gchandle(void *p_expected_mono_obj_unused, 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);
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..7b106ef63c
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedFields.cs
@@ -0,0 +1,133 @@
+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 object[] field_SystemObjectArray = { 0, 1f, 2d, "foo", Vector3i.Up };
+
+ // Generics
+ [Export] private Godot.Collections.Dictionary<string, string> field_GodotGenericDictionary =
+ new Godot.Collections.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private Godot.Collections.Array<string> field_GodotGenericArray =
+ new Godot.Collections.Array<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.Dictionary<string, string> field_SystemGenericDictionary =
+ new System.Collections.Generic.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private System.Collections.Generic.List<string> field_SystemGenericList =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.IDictionary<string, string> field_GenericIDictionary =
+ new System.Collections.Generic.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private System.Collections.Generic.ICollection<string> field_GenericICollection =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.IEnumerable<string> field_GenericIEnumerable =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ // Variant
+ [Export] private object field_SystemObject = "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 System.Collections.IDictionary field_IDictionary =
+ new System.Collections.Generic.Dictionary<object, object>
+ { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
+
+ [Export] private System.Collections.ICollection field_ICollection =
+ new System.Collections.Generic.List<object> { "foo", 10, Vector2.Up, Colors.Chocolate };
+
+ [Export] private System.Collections.IEnumerable field_IEnumerable =
+ new System.Collections.Generic.List<object> { "foo", 10, Vector2.Up, Colors.Chocolate };
+ }
+}
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..71025c1d43
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ExportedProperties.cs
@@ -0,0 +1,133 @@
+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 object[] property_SystemObjectArray { get; set; } = { 0, 1f, 2d, "foo", Vector3i.Up };
+
+ // Generics
+ [Export] private Godot.Collections.Dictionary<string, string> property_GodotGenericDictionary { get; set; } =
+ new Godot.Collections.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private Godot.Collections.Array<string> property_GodotGenericArray { get; set; } =
+ new Godot.Collections.Array<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.Dictionary<string, string> property_SystemGenericDictionary { get; set; } =
+ new System.Collections.Generic.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private System.Collections.Generic.List<string> property_SystemGenericList { get; set; } =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.IDictionary<string, string> property_GenericIDictionary { get; set; } =
+ new System.Collections.Generic.Dictionary<string, string> { { "key1", "value1" }, { "key2", "value2" } };
+
+ [Export] private System.Collections.Generic.ICollection<string> property_GenericICollection { get; set; } =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ [Export] private System.Collections.Generic.IEnumerable<string> property_GenericIEnumerable { get; set; } =
+ new System.Collections.Generic.List<string> { "elem1", "elem2", "elem3" };
+
+ // Variant
+ [Export] private object property_SystemObject { 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 System.Collections.IDictionary property_IDictionary { get; set; } =
+ new System.Collections.Generic.Dictionary<object, object>
+ { { "foo", 10 }, { Vector2.Up, Colors.Chocolate } };
+
+ [Export] private System.Collections.ICollection property_ICollection { get; set; } =
+ new System.Collections.Generic.List<object> { "foo", 10, Vector2.Up, Colors.Chocolate };
+
+ [Export] private System.Collections.IEnumerable property_IEnumerable { get; set; } =
+ new System.Collections.Generic.List<object> { "foo", 10, Vector2.Up, Colors.Chocolate };
+ }
+}
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 c5a29a53f7..a042fb313f 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
@@ -7,6 +7,8 @@
<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/ScriptBoilerplate.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
index 0c7328284e..bfc8ef2fb5 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs
@@ -1,4 +1,4 @@
-using System;
+#pragma warning disable CS0169
namespace Godot.SourceGenerators.Sample
{
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 fa41c85322..0b8a2777e5 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;
@@ -60,5 +61,110 @@ namespace Godot.SourceGenerators
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));
+ }
}
}
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 9586e71d02..2179aeea88 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -24,30 +25,55 @@ namespace Godot.SourceGenerators
toggle != null &&
toggle.Equals("true", StringComparison.OrdinalIgnoreCase);
- private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName)
+ public static bool InheritsFrom(this INamedTypeSymbol? symbol, string assemblyName, string typeFullName)
{
- if (symbol == null)
- return false;
-
- while (true)
+ 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
@@ -58,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;
@@ -129,7 +155,101 @@ namespace Godot.SourceGenerators
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 IsGodotClassNameAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.GodotClassNameAttr;
+
+ public static bool IsSystemFlagsAttribute(this INamedTypeSymbol symbol)
+ => symbol.ToString() == GodotClasses.SystemFlagsAttr;
+
+ public static IEnumerable<GodotMethodData> WhereHasGodotCompatibleSignature(
+ this IEnumerable<IMethodSymbol> methods,
+ MarshalUtils.TypeCache typeCache
+ )
+ {
+ foreach (var method in methods)
+ {
+ if (method.IsGenericMethod)
+ continue;
+
+ var retType = method.ReturnsVoid ?
+ null :
+ MarshalUtils.ConvertManagedTypeToMarshalType(method.ReturnType, typeCache);
+
+ if (retType == null && !method.ReturnsVoid)
+ continue;
+
+ 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)
+ continue;
+
+ yield return new GodotMethodData(method, paramTypes, parameters
+ .Select(p => p.Type).ToImmutableArray(), retType);
+ }
+ }
+
+ public static IEnumerable<GodotPropertyData> WhereIsGodotCompatibleType(
+ this IEnumerable<IPropertySymbol> properties,
+ MarshalUtils.TypeCache typeCache
+ )
+ {
+ foreach (var property in properties)
+ {
+ // Ignore properties without a getter. Godot properties must be readable.
+ if (property.IsWriteOnly)
+ 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)
+ {
+ 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 791ad85572..d61d9f7f14 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>
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 7cc8fa17fc..0ea1b2f5ce 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs
@@ -4,5 +4,8 @@ namespace Godot.SourceGenerators
{
public const string Object = "Godot.Object";
public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute";
+ public const string ExportAttr = "Godot.ExportAttribute";
+ 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..99d3a49546
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotEnums.cs
@@ -0,0 +1,134 @@
+using System;
+
+namespace Godot.SourceGenerators
+{
+ 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
+ }
+}
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..ff640a7a96
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotMemberData.cs
@@ -0,0 +1,46 @@
+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)
+ {
+ Method = method;
+ ParamTypes = paramTypes;
+ ParamTypeSymbols = paramTypeSymbols;
+ RetType = retType;
+ }
+
+ public IMethodSymbol Method { get; }
+ public ImmutableArray<MarshalType> ParamTypes { get; }
+ public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; }
+ public MarshalType? RetType { 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/MarshalType.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs
index 7c8345d16a..1c4c19569e 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs
@@ -30,6 +30,9 @@ namespace Godot.SourceGenerators
Basis,
Quaternion,
Transform3D,
+ Vector4,
+ Vector4i,
+ Projection,
AABB,
Color,
Plane,
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
index a77e1800fb..5a4badd66e 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs
@@ -1,9 +1,10 @@
using System;
+using System.Linq;
using Microsoft.CodeAnalysis;
namespace Godot.SourceGenerators
{
- public static class MarshalUtils
+ internal static class MarshalUtils
{
public class TypeCache
{
@@ -35,7 +36,73 @@ namespace Godot.SourceGenerators
}
}
- public static MarshalType? ConvertManagedTypeToVariantType(ITypeSymbol type, TypeCache typeCache)
+ 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.SingleArray => VariantType.PackedFloat32Array,
+ MarshalType.DoubleArray => VariantType.PackedFloat64Array,
+ MarshalType.StringArray => VariantType.PackedStringArray,
+ MarshalType.Vector2Array => VariantType.PackedVector2Array,
+ MarshalType.Vector3Array => VariantType.PackedVector3Array,
+ MarshalType.ColorArray => VariantType.PackedColorArray,
+ MarshalType.GodotObjectOrDerivedArray => VariantType.Array,
+ MarshalType.SystemObjectArray => VariantType.Array,
+ MarshalType.GodotGenericDictionary => VariantType.Dictionary,
+ MarshalType.GodotGenericArray => VariantType.Array,
+ MarshalType.SystemGenericDictionary => VariantType.Dictionary,
+ MarshalType.SystemGenericList => VariantType.Array,
+ MarshalType.GenericIDictionary => VariantType.Dictionary,
+ MarshalType.GenericICollection => VariantType.Array,
+ MarshalType.GenericIEnumerable => VariantType.Array,
+ MarshalType.SystemObject => 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.IDictionary => VariantType.Dictionary,
+ MarshalType.ICollection => VariantType.Array,
+ MarshalType.IEnumerable => VariantType.Array,
+ _ => null
+ };
+
+ public static MarshalType? ConvertManagedTypeToMarshalType(ITypeSymbol type, TypeCache typeCache)
{
var specialType = type.SpecialType;
@@ -69,39 +136,44 @@ namespace Godot.SourceGenerators
return MarshalType.String;
case SpecialType.System_Object:
return MarshalType.SystemObject;
- case SpecialType.System_ValueType:
+ default:
{
- if (type.ContainingAssembly.Name == "GodotSharp" &&
- type.ContainingNamespace.Name == "Godot")
+ var typeKind = type.TypeKind;
+
+ if (typeKind == TypeKind.Enum)
+ return MarshalType.Enum;
+
+ if (typeKind == TypeKind.Struct)
{
- return type switch
+ if (type.ContainingAssembly.Name == "GodotSharp" &&
+ type.ContainingNamespace.Name == "Godot")
{
- { 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: "AABB" } => MarshalType.AABB,
- { Name: "Color" } => MarshalType.Color,
- { Name: "Plane" } => MarshalType.Plane,
- { Name: "RID" } => MarshalType.RID,
- { Name: "Callable" } => MarshalType.Callable,
- { Name: "SignalInfo" } => MarshalType.SignalInfo,
- { TypeKind: TypeKind.Enum } => MarshalType.Enum,
- _ => null
- };
+ 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,
+ _ => null
+ };
+ }
}
-
- return null;
- }
- default:
- {
- if (type.TypeKind == TypeKind.Array)
+ else if (typeKind == TypeKind.Array)
{
var arrayType = (IArrayTypeSymbol)type;
var elementType = arrayType.ElementType;
@@ -127,17 +199,24 @@ namespace Godot.SourceGenerators
if (elementType.SimpleDerivesFrom(typeCache.GodotObjectType))
return MarshalType.GodotObjectOrDerivedArray;
- if (type.ContainingAssembly.Name == "GodotSharp" &&
- type.ContainingNamespace.Name == "Godot")
+ if (elementType.ContainingAssembly.Name == "GodotSharp" &&
+ elementType.ContainingNamespace.Name == "Godot")
{
- return elementType switch
+ switch (elementType)
{
- { Name: "Vector2" } => MarshalType.Vector2Array,
- { Name: "Vector3" } => MarshalType.Vector3Array,
- { Name: "Color" } => MarshalType.ColorArray,
- _ => null
- };
+ case { Name: "Vector2" }:
+ return MarshalType.Vector2Array;
+ case { Name: "Vector3" }:
+ return MarshalType.Vector3Array;
+ case { Name: "Color" }:
+ return MarshalType.ColorArray;
+ }
}
+
+ if (ConvertManagedTypeToMarshalType(elementType, typeCache) != null)
+ return MarshalType.GodotArray;
+
+ return null;
}
else if (type is INamedTypeSymbol { IsGenericType: true } genericType)
{
@@ -190,7 +269,10 @@ namespace Godot.SourceGenerators
{ Name: "NodePath" } => MarshalType.NodePath,
_ => null
};
- case "Godot.Collections" when !(type is INamedTypeSymbol { IsGenericType: true }):
+ case "Collections"
+ when !(type is INamedTypeSymbol { IsGenericType: true }) &&
+ type.ContainingNamespace.FullQualifiedName() ==
+ "Godot.Collections":
return type switch
{
{ Name: "Dictionary" } => MarshalType.GodotDictionary,
@@ -220,5 +302,19 @@ namespace Godot.SourceGenerators
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;
+ }
}
}
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs
index 51e9406c15..6d3d03c495 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptMemberInvokerGenerator.cs
@@ -1,5 +1,3 @@
-using System.Collections.Generic;
-using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
@@ -9,7 +7,7 @@ using Microsoft.CodeAnalysis.Text;
namespace Godot.SourceGenerators
{
[Generator]
- public class ScriptBoilerplateGenerator : ISourceGenerator
+ public class ScriptMemberInvokerGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
@@ -61,8 +59,6 @@ namespace Godot.SourceGenerators
INamedTypeSymbol symbol
)
{
- string className = symbol.Name;
-
INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
namespaceSymbol.FullQualifiedName() :
@@ -71,9 +67,8 @@ namespace Godot.SourceGenerators
bool isInnerClass = symbol.ContainingType != null;
- string uniqueName = hasNamespace ?
- classNs + "." + className + "_ScriptBoilerplate_Generated" :
- className + "_ScriptBoilerplate_Generated";
+ string uniqueHint = symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()
+ + "_ScriptMemberInvoker_Generated";
var source = new StringBuilder();
@@ -97,7 +92,7 @@ namespace Godot.SourceGenerators
source.Append("partial ");
source.Append(containingType.GetDeclarationKeyword());
source.Append(" ");
- source.Append(containingType.Name);
+ source.Append(containingType.NameWithTypeParameters());
source.Append("\n{\n");
containingType = containingType.ContainingType;
@@ -105,7 +100,7 @@ namespace Godot.SourceGenerators
}
source.Append("partial class ");
- source.Append(className);
+ source.Append(symbol.NameWithTypeParameters());
source.Append("\n{\n");
var members = symbol.GetMembers();
@@ -113,27 +108,28 @@ namespace Godot.SourceGenerators
// TODO: Static static marshaling (no reflection, no runtime type checks)
var methodSymbols = members
- .Where(s => s.Kind == SymbolKind.Method)
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Method && !s.IsImplicitlyDeclared)
.Cast<IMethodSymbol>()
- .Where(m => m.MethodKind == MethodKind.Ordinary && !m.IsImplicitlyDeclared);
+ .Where(m => m.MethodKind == MethodKind.Ordinary);
var propertySymbols = members
- .Where(s => s.Kind == SymbolKind.Property)
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
.Cast<IPropertySymbol>();
var fieldSymbols = members
- .Where(s => s.Kind == SymbolKind.Field)
- .Cast<IFieldSymbol>()
- .Where(p => !p.IsImplicitlyDeclared);
+ .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)
+ .Cast<IFieldSymbol>();
- var godotClassMethods = WhereHasCompatibleGodotType(methodSymbols, typeCache).ToArray();
- var godotClassProperties = WhereIsCompatibleGodotType(propertySymbols, typeCache).ToArray();
- var godotClassFields = WhereIsCompatibleGodotType(fieldSymbols, typeCache).ToArray();
+ var godotClassMethods = methodSymbols.WhereHasGodotCompatibleSignature(typeCache).ToArray();
+ var godotClassProperties = propertySymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
+ var godotClassFields = fieldSymbols.WhereIsGodotCompatibleType(typeCache).ToArray();
- source.Append(" private class GodotInternal {\n");
+ source.Append(" private partial class GodotInternal {\n");
// Generate cached StringNames for methods and properties, for fast lookup
+ // TODO: Move the generation of these cached StringNames to its own generator
+
foreach (var method in godotClassMethods)
{
string methodName = method.Method.Name;
@@ -144,26 +140,6 @@ namespace Godot.SourceGenerators
source.Append("\";\n");
}
- foreach (var property in godotClassProperties)
- {
- string propertyName = property.Property.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.Field.Name;
- source.Append(" public static readonly StringName PropName_");
- source.Append(fieldName);
- source.Append(" = \"");
- source.Append(fieldName);
- source.Append("\";\n");
- }
-
source.Append(" }\n"); // class GodotInternal
// Generate InvokeGodotClassMethod
@@ -191,8 +167,8 @@ namespace Godot.SourceGenerators
// Setters
- bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.Field.IsReadOnly) &&
- godotClassProperties.All(pi => pi.Property.IsReadOnly);
+ bool allPropertiesAreReadOnly = godotClassFields.All(fi => fi.FieldSymbol.IsReadOnly) &&
+ godotClassProperties.All(pi => pi.PropertySymbol.IsReadOnly);
if (!allPropertiesAreReadOnly)
{
@@ -202,21 +178,21 @@ namespace Godot.SourceGenerators
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
- if (property.Property.IsReadOnly)
+ if (property.PropertySymbol.IsReadOnly)
continue;
- GeneratePropertySetter(property.Property.Name,
- property.Property.Type.FullQualifiedName(), source, isFirstEntry);
+ GeneratePropertySetter(property.PropertySymbol.Name,
+ property.PropertySymbol.Type.FullQualifiedName(), source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
- if (field.Field.IsReadOnly)
+ if (field.FieldSymbol.IsReadOnly)
continue;
- GeneratePropertySetter(field.Field.Name,
- field.Field.Type.FullQualifiedName(), source, isFirstEntry);
+ GeneratePropertySetter(field.FieldSymbol.Name,
+ field.FieldSymbol.Type.FullQualifiedName(), source, isFirstEntry);
isFirstEntry = false;
}
@@ -233,13 +209,13 @@ namespace Godot.SourceGenerators
isFirstEntry = true;
foreach (var property in godotClassProperties)
{
- GeneratePropertyGetter(property.Property.Name, source, isFirstEntry);
+ GeneratePropertyGetter(property.PropertySymbol.Name, source, isFirstEntry);
isFirstEntry = false;
}
foreach (var field in godotClassFields)
{
- GeneratePropertyGetter(field.Field.Name, source, isFirstEntry);
+ GeneratePropertyGetter(field.FieldSymbol.Name, source, isFirstEntry);
isFirstEntry = false;
}
@@ -285,11 +261,11 @@ namespace Godot.SourceGenerators
source.Append("\n}\n");
}
- context.AddSource(uniqueName, SourceText.From(source.ToString(), Encoding.UTF8));
+ context.AddSource(uniqueHint, SourceText.From(source.ToString(), Encoding.UTF8));
}
private static void GenerateMethodInvoker(
- GodotMethodInfo method,
+ GodotMethodData method,
StringBuilder source
)
{
@@ -399,7 +375,7 @@ namespace Godot.SourceGenerators
}
private static void GenerateHasMethodEntry(
- GodotMethodInfo method,
+ GodotMethodData method,
StringBuilder source,
bool isFirstEntry
)
@@ -417,118 +393,5 @@ namespace Godot.SourceGenerators
public void Initialize(GeneratorInitializationContext context)
{
}
-
- private struct GodotMethodInfo
- {
- public GodotMethodInfo(IMethodSymbol method, ImmutableArray<MarshalType> paramTypes,
- ImmutableArray<ITypeSymbol> paramTypeSymbols, MarshalType? retType)
- {
- Method = method;
- ParamTypes = paramTypes;
- ParamTypeSymbols = paramTypeSymbols;
- RetType = retType;
- }
-
- public IMethodSymbol Method { get; }
- public ImmutableArray<MarshalType> ParamTypes { get; }
- public ImmutableArray<ITypeSymbol> ParamTypeSymbols { get; }
- public MarshalType? RetType { get; }
- }
-
- private struct GodotPropertyInfo
- {
- public GodotPropertyInfo(IPropertySymbol property, MarshalType type)
- {
- Property = property;
- Type = type;
- }
-
- public IPropertySymbol Property { get; }
- public MarshalType Type { get; }
- }
-
- private struct GodotFieldInfo
- {
- public GodotFieldInfo(IFieldSymbol field, MarshalType type)
- {
- Field = field;
- Type = type;
- }
-
- public IFieldSymbol Field { get; }
- public MarshalType Type { get; }
- }
-
- private static IEnumerable<GodotMethodInfo> WhereHasCompatibleGodotType(
- IEnumerable<IMethodSymbol> methods,
- MarshalUtils.TypeCache typeCache
- )
- {
- foreach (var method in methods)
- {
- if (method.IsGenericMethod)
- continue;
-
- var retType = method.ReturnsVoid ?
- null :
- MarshalUtils.ConvertManagedTypeToVariantType(method.ReturnType, typeCache);
-
- if (retType == null && !method.ReturnsVoid)
- continue;
-
- 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.ConvertManagedTypeToVariantType(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)
- continue; // Ignore incompatible method
-
- yield return new GodotMethodInfo(method, paramTypes, parameters
- .Select(p => p.Type).ToImmutableArray(), retType);
- }
- }
-
- private static IEnumerable<GodotPropertyInfo> WhereIsCompatibleGodotType(
- IEnumerable<IPropertySymbol> properties,
- MarshalUtils.TypeCache typeCache
- )
- {
- foreach (var property in properties)
- {
- // Ignore properties without a getter. Godot properties must be readable.
- if (property.IsWriteOnly)
- continue;
-
- var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(property.Type, typeCache);
-
- if (marshalType == null)
- continue;
-
- yield return new GodotPropertyInfo(property, marshalType.Value);
- }
- }
-
- private static IEnumerable<GodotFieldInfo> WhereIsCompatibleGodotType(
- IEnumerable<IFieldSymbol> fields,
- MarshalUtils.TypeCache typeCache
- )
- {
- foreach (var field in fields)
- {
- var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(field.Type, typeCache);
-
- if (marshalType == null)
- continue;
-
- yield return new GodotFieldInfo(field, marshalType.Value);
- }
- }
}
}
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 b625287087..e8a9e28d0c 100644
--- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs
@@ -90,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();
@@ -124,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)
@@ -135,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..85fa65d1af
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
@@ -0,0 +1,527 @@
+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
+
+ // Generate GetGodotPropertiesMetadata
+
+ if (godotClassProperties.Length > 0 || godotClassFields.Length > 0)
+ {
+ source.Append("#pragma warning disable CS0109 // Disable warning about redundant 'new' keyword\n");
+
+ string dictionaryType = "System.Collections.Generic.List<Godot.Bridge.PropertyInfo>";
+
+ source.Append(" internal new static ")
+ .Append(dictionaryType)
+ .Append(" GetGodotPropertiesMetadata()\n {\n");
+
+ source.Append(" var properties = new ")
+ .Append(dictionaryType)
+ .Append("();\n");
+
+ foreach (var property in godotClassProperties)
+ {
+ var propertyInfo = GetPropertyMetadata(context, typeCache,
+ property.PropertySymbol, property.Type);
+
+ if (propertyInfo == null)
+ continue;
+
+ AppendPropertyInfo(source, propertyInfo.Value);
+ }
+
+ foreach (var field in godotClassFields)
+ {
+ var propertyInfo = GetPropertyMetadata(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 AppendPropertyInfo(StringBuilder source, PropertyInfo propertyInfo)
+ {
+ source.Append(" properties.Add(new Godot.Bridge.PropertyInfo(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 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; }
+ }
+
+ private static PropertyInfo? GetPropertyMetadata(
+ 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/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index 4e27f4ed14..fe83e6a281 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -513,20 +513,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() as ExportPlugin)?.Dispose();
- _exportPluginWeak.Dispose();
+ _exportPluginWeak.Dispose();
+ }
+
+ GodotIdeManager?.Dispose();
}
- GodotIdeManager?.Dispose();
+ base.Dispose(disposing);
}
public void OnBeforeSerialize()
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 746cb8a142..6c805c605d 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -1407,6 +1407,13 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
}
}
+ // 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 << INDENT1 "[GodotClassName(\"" << itype.name << "\")]\n";
+ }
+
output.append(INDENT1 "public ");
if (itype.is_singleton) {
output.append("static partial class ");
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/Bridge/ManagedCallbacks.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
index fb1efa0ac8..7a6748b4eb 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ManagedCallbacks.cs
@@ -24,6 +24,8 @@ namespace Godot.Bridge
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_RemoveScriptBridge;
public delegate* unmanaged<IntPtr, godot_bool*, godot_dictionary*, 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;
@@ -57,6 +59,8 @@ namespace Godot.Bridge
ScriptManagerBridge_RemoveScriptBridge = &ScriptManagerBridge.RemoveScriptBridge,
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,
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..cfdfe2dab3
--- /dev/null
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/PropertyInfo.cs
@@ -0,0 +1,26 @@
+using System;
+using Godot.NativeInterop;
+
+namespace Godot.Bridge
+{
+ 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
index b86ced55cb..8348598b65 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -1,6 +1,8 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using Godot.Collections;
@@ -523,7 +525,7 @@ namespace Godot.Bridge
Dictionary<string, Dictionary> rpcFunctions = new();
- Type top = _scriptBridgeMap[scriptPtr];
+ Type top = scriptType;
Type native = Object.InternalGetClassNativeBase(top);
while (top != null && top != native)
@@ -602,5 +604,256 @@ namespace Godot.Bridge
return false.ToGodotBool();
}
}
+
+ // 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 = _scriptBridgeMap[scriptPtr];
+ GetPropertyInfoListForType(scriptType, scriptPtr, addPropInfoFunc);
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.DebugUnhandledException(e);
+ }
+ }
+
+ private static unsafe void GetPropertyInfoListForType(Type type, IntPtr scriptPtr,
+ delegate* unmanaged<IntPtr, godot_string*, void*, int, void> addPropInfoFunc)
+ {
+ try
+ {
+ var getGodotPropertiesMetadataMethod = type.GetMethod(
+ "GetGodotPropertiesMetadata",
+ BindingFlags.DeclaredOnly | BindingFlags.Static |
+ BindingFlags.NonPublic | BindingFlags.Public);
+
+ if (getGodotPropertiesMetadataMethod == null)
+ return;
+
+ var properties = (System.Collections.Generic.List<PropertyInfo>)
+ getGodotPropertiesMetadataMethod.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
+ {
+#if NET6_0_OR_GREATER
+ interopProperties = ((godotsharp_property_info*)NativeMemory.Alloc(length))!;
+#else
+ interopProperties = ((godotsharp_property_info*)Marshal.AllocHGlobal(length))!;
+#endif
+ }
+
+ 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, &currentClassName, 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)
+ {
+#if NET6_0_OR_GREATER
+ NativeMemory.Free(interopProperties);
+#else
+ Marshal.FreeHGlobal((IntPtr)interopProperties);
+#endif
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.DebugUnhandledException(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 = _scriptBridgeMap[scriptPtr];
+ Type native = Object.InternalGetClassNativeBase(top);
+
+ while (top != null && top != native)
+ {
+ GetPropertyDefaultValuesForType(top, scriptPtr, addDefValFunc);
+
+ top = top.BaseType;
+ }
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.DebugUnhandledException(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 = (System.Collections.Generic.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
+ {
+#if NET6_0_OR_GREATER
+ interopDefaultValues = ((godotsharp_property_def_val_pair*)NativeMemory.Alloc(length))!;
+#else
+ interopDefaultValues = ((godotsharp_property_def_val_pair*)Marshal.AllocHGlobal(length))!;
+#endif
+ }
+
+ 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)
+ {
+#if NET6_0_OR_GREATER
+ NativeMemory.Free(interopDefaultValues);
+#else
+ Marshal.FreeHGlobal((IntPtr)interopDefaultValues);
+#endif
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ ExceptionUtils.DebugUnhandledException(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 733a8ac114..0000000000
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/MarshalUtils.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace Godot
-{
- internal static class MarshalUtils
- {
- /// <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);
- }
-}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
index c2812b8919..01add1bf45 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs
@@ -36,10 +36,13 @@ namespace Godot.NativeInterop
public static extern IntPtr godotsharp_engine_get_singleton(in godot_string p_name);
[DllImport(GodotDllName)]
- internal static extern void godotsharp_internal_object_disposed(IntPtr ptr);
+ internal static extern IntPtr godotsharp_internal_object_get_associated_gchandle(IntPtr ptr);
[DllImport(GodotDllName)]
- internal static extern void godotsharp_internal_refcounted_disposed(IntPtr ptr, godot_bool isFinalizer);
+ internal static extern void godotsharp_internal_object_disposed(IntPtr ptr, IntPtr gcHandleToFree);
+
+ [DllImport(GodotDllName)]
+ internal static extern void godotsharp_internal_refcounted_disposed(IntPtr ptr, IntPtr gcHandleToFree, godot_bool isFinalizer);
[DllImport(GodotDllName)]
internal static extern void godotsharp_internal_object_connect_event_signal(IntPtr obj,
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
index 6255ffbbc7..71a620716f 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs
@@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reflection;
+using System.Runtime.InteropServices;
using Godot.NativeInterop;
namespace Godot
@@ -121,23 +122,35 @@ namespace Godot
if (_disposed)
return;
+ _disposed = true;
+
if (NativePtr != IntPtr.Zero)
{
+ 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)
{
- NativeFuncs.godotsharp_internal_refcounted_disposed(NativePtr, (!disposing).ToGodotBool());
+ NativeFuncs.godotsharp_internal_refcounted_disposed(NativePtr, gcHandleToFree,
+ (!disposing).ToGodotBool());
}
else
{
- NativeFuncs.godotsharp_internal_object_disposed(NativePtr);
+ NativeFuncs.godotsharp_internal_object_disposed(NativePtr, gcHandleToFree);
}
NativePtr = IntPtr.Zero;
}
DisposablesTracker.UnregisterGodotObject(_weakReferenceToSelf);
-
- _disposed = true;
}
/// <summary>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index b7fbb81f9c..8b0c421829 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -41,6 +41,7 @@
<Compile Include="Core\Bridge\CSharpInstanceBridge.cs" />
<Compile Include="Core\Bridge\GCHandleBridge.cs" />
<Compile Include="Core\Bridge\ManagedCallbacks.cs" />
+ <Compile Include="Core\Bridge\PropertyInfo.cs" />
<Compile Include="Core\Bridge\ScriptManagerBridge.cs" />
<Compile Include="Core\Callable.cs" />
<Compile Include="Core\Color.cs" />
@@ -63,7 +64,6 @@
<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\ExceptionUtils.cs" />
diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp
index c3d4f53048..7150e2e35b 100644
--- a/modules/mono/glue/runtime_interop.cpp
+++ b/modules/mono/glue/runtime_interop.cpp
@@ -81,7 +81,7 @@ GD_PINVOKE_EXPORT Object *godotsharp_engine_get_singleton(const String *p_name)
return Engine::get_singleton()->get_singleton_object(*p_name);
}
-GD_PINVOKE_EXPORT void godotsharp_internal_object_disposed(Object *p_ptr) {
+GD_PINVOKE_EXPORT GCHandleIntPtr godotsharp_internal_object_get_associated_gchandle(Object *p_ptr) {
#ifdef DEBUG_ENABLED
CRASH_COND(p_ptr == nullptr);
#endif
@@ -90,7 +90,35 @@ GD_PINVOKE_EXPORT void godotsharp_internal_object_disposed(Object *p_ptr) {
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();
+ 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 };
+}
+
+GD_PINVOKE_EXPORT 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;
@@ -102,16 +130,14 @@ GD_PINVOKE_EXPORT void godotsharp_internal_object_disposed(Object *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(nullptr, gchandle);
- script_binding.inited = false;
+ if (!script_binding.gchandle.is_released()) {
+ CSharpLanguage::release_binding_gchandle_thread_safe(p_gchandle_to_free, script_binding);
}
}
}
}
-GD_PINVOKE_EXPORT void godotsharp_internal_refcounted_disposed(Object *p_ptr, bool p_is_finalizer) {
+GD_PINVOKE_EXPORT 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
@@ -127,7 +153,8 @@ GD_PINVOKE_EXPORT void godotsharp_internal_refcounted_disposed(Object *p_ptr, bo
bool delete_owner;
bool remove_script_instance;
- cs_instance->mono_object_disposed_baseref(p_is_finalizer, delete_owner, 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);
@@ -150,10 +177,8 @@ GD_PINVOKE_EXPORT void godotsharp_internal_refcounted_disposed(Object *p_ptr, bo
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(nullptr, gchandle);
- script_binding.inited = false;
+ if (!script_binding.gchandle.is_released()) {
+ CSharpLanguage::release_binding_gchandle_thread_safe(p_gchandle_to_free, script_binding);
}
}
}
@@ -188,7 +213,7 @@ GD_PINVOKE_EXPORT GCHandleIntPtr godotsharp_internal_unmanaged_get_script_instan
}
*r_has_cs_script_instance = false;
- return GCHandleIntPtr();
+ return { nullptr };
}
GD_PINVOKE_EXPORT GCHandleIntPtr godotsharp_internal_unmanaged_get_instance_binding_managed(Object *p_unmanaged) {
@@ -197,9 +222,9 @@ GD_PINVOKE_EXPORT GCHandleIntPtr godotsharp_internal_unmanaged_get_instance_bind
#endif
void *data = CSharpLanguage::get_instance_binding(p_unmanaged);
- ERR_FAIL_NULL_V(data, GCHandleIntPtr());
+ ERR_FAIL_NULL_V(data, { nullptr });
CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->value();
- ERR_FAIL_COND_V(!script_binding.inited, GCHandleIntPtr());
+ ERR_FAIL_COND_V(!script_binding.inited, { nullptr });
return script_binding.gchandle.get_intptr();
}
@@ -210,9 +235,9 @@ GD_PINVOKE_EXPORT GCHandleIntPtr godotsharp_internal_unmanaged_instance_binding_
#endif
void *data = CSharpLanguage::get_instance_binding(p_unmanaged);
- ERR_FAIL_NULL_V(data, GCHandleIntPtr());
+ ERR_FAIL_NULL_V(data, { nullptr });
CSharpScriptBinding &script_binding = ((RBMap<Object *, CSharpScriptBinding>::Element *)data)->value();
- ERR_FAIL_COND_V(!script_binding.inited, GCHandleIntPtr());
+ ERR_FAIL_COND_V(!script_binding.inited, { nullptr });
MonoGCHandleData &gchandle = script_binding.gchandle;
@@ -229,14 +254,14 @@ GD_PINVOKE_EXPORT GCHandleIntPtr godotsharp_internal_unmanaged_instance_binding_
#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, GCHandleIntPtr(),
+ 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, GCHandleIntPtr());
+ ERR_FAIL_NULL_V(strong_gchandle.value, { nullptr });
gchandle = MonoGCHandleData(strong_gchandle, gdmono::GCHandleType::STRONG_HANDLE);
script_binding.inited = true;
@@ -420,25 +445,25 @@ GD_PINVOKE_EXPORT bool godotsharp_callable_get_data_for_marshalling(const Callab
return true;
} else if (compare_equal_func == SignalAwaiterCallable::compare_equal_func_ptr) {
SignalAwaiterCallable *signal_awaiter_callable = static_cast<SignalAwaiterCallable *>(custom);
- *r_delegate_handle = GCHandleIntPtr();
+ *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 = GCHandleIntPtr();
+ *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 = GCHandleIntPtr();
+ *r_delegate_handle = { nullptr };
*r_object = nullptr;
memnew_placement(r_name, StringName());
return false;
} else {
- *r_delegate_handle = GCHandleIntPtr();
+ *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;
@@ -1256,10 +1281,11 @@ GD_PINVOKE_EXPORT void godotsharp_object_to_string(Object *p_ptr, godot_string *
#endif
// We need this to prevent the functions from being stripped.
-void *godotsharp_pinvoke_funcs[178] = {
+void *godotsharp_pinvoke_funcs[179] = {
(void *)godotsharp_method_bind_get_method,
(void *)godotsharp_get_class_constructor,
(void *)godotsharp_engine_get_singleton,
+ (void *)godotsharp_internal_object_get_associated_gchandle,
(void *)godotsharp_internal_object_disposed,
(void *)godotsharp_internal_refcounted_disposed,
(void *)godotsharp_internal_object_connect_event_signal,
diff --git a/modules/mono/managed_callable.cpp b/modules/mono/managed_callable.cpp
index a1f94aa590..334de14a6b 100644
--- a/modules/mono/managed_callable.cpp
+++ b/modules/mono/managed_callable.cpp
@@ -96,7 +96,7 @@ void ManagedCallable::call(const Variant **p_arguments, int p_argcount, Variant
void ManagedCallable::release_delegate_handle() {
if (delegate_handle.value) {
GDMonoCache::managed_callbacks.GCHandleBridge_FreeGCHandle(delegate_handle);
- delegate_handle = GCHandleIntPtr();
+ delegate_handle = { nullptr };
}
}
diff --git a/modules/mono/mono_gc_handle.h b/modules/mono/mono_gc_handle.h
index a921b24103..4e4c13fee6 100644
--- a/modules/mono/mono_gc_handle.h
+++ b/modules/mono/mono_gc_handle.h
@@ -44,7 +44,12 @@ enum class GCHandleType : char {
extern "C" {
struct GCHandleIntPtr {
- void *value = nullptr;
+ 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;
};
}
@@ -52,7 +57,7 @@ static_assert(sizeof(GCHandleIntPtr) == sizeof(void *));
// Manual release of the GC handle must be done when using this struct
struct MonoGCHandleData {
- GCHandleIntPtr handle;
+ GCHandleIntPtr handle = { nullptr };
gdmono::GCHandleType type = gdmono::GCHandleType::NIL;
_FORCE_INLINE_ bool is_released() const { return !handle.value; }
diff --git a/modules/mono/mono_gd/gd_mono_cache.h b/modules/mono/mono_gd/gd_mono_cache.h
index b993facff9..7a75532fb7 100644
--- a/modules/mono/mono_gd/gd_mono_cache.h
+++ b/modules/mono/mono_gd/gd_mono_cache.h
@@ -34,6 +34,7 @@
#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"
@@ -52,16 +53,33 @@ namespace GDMonoCache {
#define GD_CLR_STDCALL
#endif
+struct godotsharp_property_info {
+ godot_string_name name; // Not owned
+ godot_string hint_string;
+ Variant::Type type;
+ PropertyHint hint;
+ PropertyUsageFlags usage;
+ bool exported;
+};
+
+struct godotsharp_property_def_val_pair {
+ godot_string_name name; // Not owned
+ godot_variant value;
+};
+
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 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 **, int);
+ 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 **, int, bool *);
+ using FuncScriptManagerBridge_RaiseEventSignal = void(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int32_t, bool *);
using FuncScriptManagerBridge_GetScriptSignalList = void(GD_CLR_STDCALL *)(const CSharpScript *, Dictionary *);
using FuncScriptManagerBridge_HasScriptSignal = bool(GD_CLR_STDCALL *)(const CSharpScript *, const String *);
using FuncScriptManagerBridge_ScriptIsOrInherits = bool(GD_CLR_STDCALL *)(const CSharpScript *, const CSharpScript *);
@@ -69,7 +87,9 @@ struct ManagedCallbacks {
using FuncScriptManagerBridge_RemoveScriptBridge = void(GD_CLR_STDCALL *)(const CSharpScript *);
using FuncScriptManagerBridge_UpdateScriptClassInfo = void(GD_CLR_STDCALL *)(const CSharpScript *, bool *, Dictionary *);
using FuncScriptManagerBridge_SwapGCHandleForType = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, GCHandleIntPtr *, bool);
- using FuncCSharpInstanceBridge_Call = bool(GD_CLR_STDCALL *)(GCHandleIntPtr, const StringName *, const Variant **, int, Callable::CallError *, Variant *);
+ 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);
@@ -96,6 +116,8 @@ struct ManagedCallbacks {
FuncScriptManagerBridge_RemoveScriptBridge ScriptManagerBridge_RemoveScriptBridge;
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;