C#: Add source generator for properties and exports default values

The editor no longer needs to create temporary instances to get the
default values. The initializer values of the exported properties are
still evaluated at runtime. For example, in the following example,
`GetInitialValue()` will be called when first looks for default values:

```
[Export] int MyValue = GetInitialValue();
```

Exporting fields with a non-supported type now results in a compiler
error rather than a runtime error when the script is used.
This commit is contained in:
Ignacio Roldán Etcheverry 2022-02-27 21:57:30 +01:00
parent 88e367a406
commit 92503ae8db
34 changed files with 2182 additions and 773 deletions

View file

@ -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>

View file

@ -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);
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 (tmp_object) {
exported_members_defval_cache[member_name] = GDMonoMarshal::mono_object_to_variant(field->get_value(tmp_object));
}
}
for (int i = 0; i < p_count; i++) {
const GDMonoCache::godotsharp_property_info &prop = p_props[i];
StringName name = *reinterpret_cast<const StringName *>(&prop.name);
String hint_string = *reinterpret_cast<const String *>(&prop.hint_string);
PropertyInfo pinfo(prop.type, name, prop.hint, hint_string, prop.usage);
p_script->member_info[name] = pinfo;
if (prop.exported) {
#ifdef TOOLS_ENABLED
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
}
}
}
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: */ true, prop_info, exported)) {
StringName member_name = property->get_name();
member_info[member_name] = prop_info;
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);
}
}
}
#endif
});
#if defined(TOOLS_ENABLED) || defined(DEBUG_ENABLED)
exported_members_names.insert(member_name);
#endif
}
}
}
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];
#ifdef TOOLS_ENABLED
exported_members_cache.push_back(PropertyInfo(Variant::NIL, top->get_name(), PROPERTY_HINT_NONE, get_path(), PROPERTY_USAGE_CATEGORY));
StringName name = *reinterpret_cast<const StringName *>(&def_val_pair.name);
Variant value = *reinterpret_cast<const Variant *>(&def_val_pair.value);
for (const PropertyInfo &E : props) {
exported_members_cache.push_back(E);
}
props.clear();
#endif // TOOLS_ENABLED
top = top->get_parent_class();
p_script->exported_members_defval_cache[name] = value;
}
});
}
#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);
}
GDMonoUtils::free_gchandle(tmp_pinned_gchandle);
tmp_object = nullptr;
if (tmp_native && !base_ref_counted) {
Node *node = Object::cast_to<Node>(tmp_native);
if (node && node->is_inside_tree()) {
ERR_PRINT("Temporary instance was added to the scene tree.");
} else {
memdelete(tmp_native);
}
}
}
#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);

View file

@ -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);

View file

@ -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 };
}
}

View file

@ -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 };
}
}

View file

@ -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;
}
}

View file

@ -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>

View file

@ -1,4 +1,4 @@
using System;
#pragma warning disable CS0169
namespace Godot.SourceGenerators.Sample
{

View file

@ -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));
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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>

View file

@ -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";
}
}

View file

@ -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
}
}

View file

@ -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; }
}
}

View file

@ -30,6 +30,9 @@ namespace Godot.SourceGenerators
Basis,
Quaternion,
Transform3D,
Vector4,
Vector4i,
Projection,
AABB,
Color,
Plane,

View file

@ -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:
{
if (type.ContainingAssembly.Name == "GodotSharp" &&
type.ContainingNamespace.Name == "Godot")
{
return type switch
{
{ Name: "Vector2" } => MarshalType.Vector2,
{ Name: "Vector2i" } => MarshalType.Vector2i,
{ Name: "Rect2" } => MarshalType.Rect2,
{ Name: "Rect2i" } => MarshalType.Rect2i,
{ Name: "Transform2D" } => MarshalType.Transform2D,
{ Name: "Vector3" } => MarshalType.Vector3,
{ Name: "Vector3i" } => MarshalType.Vector3i,
{ Name: "Basis" } => MarshalType.Basis,
{ Name: "Quaternion" } => MarshalType.Quaternion,
{ Name: "Transform3D" } => MarshalType.Transform3D,
{ Name: "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 null;
}
default:
{
if (type.TypeKind == TypeKind.Array)
var typeKind = type.TypeKind;
if (typeKind == TypeKind.Enum)
return MarshalType.Enum;
if (typeKind == TypeKind.Struct)
{
if (type.ContainingAssembly.Name == "GodotSharp" &&
type.ContainingNamespace.Name == "Godot")
{
return type switch
{
{ Name: "Vector2" } => MarshalType.Vector2,
{ Name: "Vector2i" } => MarshalType.Vector2i,
{ Name: "Rect2" } => MarshalType.Rect2,
{ Name: "Rect2i" } => MarshalType.Rect2i,
{ Name: "Transform2D" } => MarshalType.Transform2D,
{ Name: "Vector3" } => MarshalType.Vector3,
{ Name: "Vector3i" } => MarshalType.Vector3i,
{ Name: "Basis" } => MarshalType.Basis,
{ Name: "Quaternion" } => MarshalType.Quaternion,
{ Name: "Transform3D" } => MarshalType.Transform3D,
{ Name: "Vector4" } => MarshalType.Vector4,
{ Name: "Vector4i" } => MarshalType.Vector4i,
{ Name: "Projection" } => MarshalType.Projection,
{ Name: "AABB" } => MarshalType.AABB,
{ Name: "Color" } => MarshalType.Color,
{ Name: "Plane" } => MarshalType.Plane,
{ Name: "RID" } => MarshalType.RID,
{ Name: "Callable" } => MarshalType.Callable,
{ Name: "SignalInfo" } => MarshalType.SignalInfo,
_ => null
};
}
}
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;
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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,

View file

@ -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;
}
}
}

View file

@ -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; }
}
}
}

View file

@ -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()

View file

@ -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 ");

View file

@ -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;

View file

@ -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,

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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>

View file

@ -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" />

View file

@ -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,

View file

@ -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 };
}
}

View file

@ -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; }

View file

@ -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;