diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props index bdec051625d..2bd60bb7195 100644 --- a/modules/mono/SdkPackageVersions.props +++ b/modules/mono/SdkPackageVersions.props @@ -1,7 +1,7 @@ 4.0.*-* - 4.0.0-dev6 - 4.0.0-dev3 + 4.0.0-dev7 + 4.0.0-dev6 diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs new file mode 100644 index 00000000000..05723c89400 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs @@ -0,0 +1,21 @@ +namespace Godot.SourceGenerators.Sample +{ + public partial class ScriptBoilerplate : Godot.Node + { + private NodePath _nodePath; + private int _velocity; + + public override void _Process(float delta) + { + _ = delta; + + base._Process(delta); + } + + public int Bazz(StringName name) + { + _ = name; + return 1; + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs index 4867c986e6d..4dd67252edb 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs @@ -14,9 +14,8 @@ namespace Godot.SourceGenerators "Missing partial modifier on declaration of type '" + $"{symbol.FullQualifiedName()}' which is a subclass of '{GodotClasses.Object}'"; - string description = $"{message}. Subclasses of '{GodotClasses.Object}' must be " + - "declared with the partial modifier or annotated with the " + - $"attribute '{GodotClasses.DisableGodotGeneratorsAttr}'."; + string description = $"{message}. Subclasses of '{GodotClasses.Object}' " + + "must be declared with the partial modifier."; context.ReportDiagnostic(Diagnostic.Create( new DiagnosticDescriptor(id: "GODOT-G0001", diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs index e16f72f43af..f8c50e66c89 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ExtensionMethods.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; @@ -13,6 +14,11 @@ namespace Godot.SourceGenerators ) => context.AnalyzerConfigOptions.GlobalOptions .TryGetValue("build_property." + property, out value); + public static bool AreGodotSourceGeneratorsDisabled(this GeneratorExecutionContext context) + => context.TryGetGlobalAnalyzerProperty("GodotSourceGenerators", out string? toggle) && + toggle != null && + toggle.Equals("disabled", StringComparison.OrdinalIgnoreCase); + private static bool InheritsFrom(this INamedTypeSymbol? symbol, string baseName) { if (symbol == null) @@ -72,15 +78,11 @@ namespace Godot.SourceGenerators public static bool IsPartial(this ClassDeclarationSyntax cds) => cds.Modifiers.Any(SyntaxKind.PartialKeyword); - public static bool HasDisableGeneratorsAttribute(this INamedTypeSymbol symbol) - => symbol.GetAttributes().Any(attr => - attr.AttributeClass?.ToString() == GodotClasses.DisableGodotGeneratorsAttr); - private static SymbolDisplayFormat FullyQualifiedFormatOmitGlobal { get; } = SymbolDisplayFormat.FullyQualifiedFormat .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); - public static string FullQualifiedName(this INamedTypeSymbol symbol) + public static string FullQualifiedName(this ITypeSymbol symbol) => symbol.ToDisplayString(NullableFlowState.NotNull, FullyQualifiedFormatOmitGlobal); public static string FullQualifiedName(this INamespaceSymbol namespaceSymbol) diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props index f9b47ad5b19..5025215d347 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Godot.SourceGenerators.props @@ -2,6 +2,6 @@ - + diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs index 29e41d155a8..7cc8fa17fc6 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs @@ -3,7 +3,6 @@ namespace Godot.SourceGenerators public static class GodotClasses { public const string Object = "Godot.Object"; - public const string DisableGodotGeneratorsAttr = "Godot.DisableGodotGeneratorsAttribute"; public const string AssemblyHasScriptsAttr = "Godot.AssemblyHasScriptsAttribute"; } } diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs new file mode 100644 index 00000000000..7c8345d16ac --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalType.cs @@ -0,0 +1,78 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + public enum MarshalType + { + Boolean, + Char, + SByte, + Int16, + Int32, + Int64, + Byte, + UInt16, + UInt32, + UInt64, + Single, + Double, + String, + + // Godot structs + Vector2, + Vector2i, + Rect2, + Rect2i, + Transform2D, + Vector3, + Vector3i, + Basis, + Quaternion, + Transform3D, + AABB, + Color, + Plane, + Callable, + SignalInfo, + + // Enums + Enum, + + // Arrays + ByteArray, + Int32Array, + Int64Array, + SingleArray, + DoubleArray, + StringArray, + Vector2Array, + Vector3Array, + ColorArray, + GodotObjectOrDerivedArray, + SystemObjectArray, + + // Generics + GodotGenericDictionary, + GodotGenericArray, + SystemGenericDictionary, + SystemGenericList, + GenericIDictionary, + GenericICollection, + GenericIEnumerable, + + // Variant + SystemObject, + + // Classes + GodotObjectOrDerived, + StringName, + NodePath, + RID, + GodotDictionary, + GodotArray, + IDictionary, + ICollection, + IEnumerable, + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs new file mode 100644 index 00000000000..a77e1800fb5 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/MarshalUtils.cs @@ -0,0 +1,224 @@ +using System; +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + public static class MarshalUtils + { + public class TypeCache + { + public INamedTypeSymbol GodotObjectType { get; } + public INamedTypeSymbol GodotGenericDictionary { get; } + public INamedTypeSymbol GodotGenericArray { get; } + public INamedTypeSymbol IDictionary { get; } + public INamedTypeSymbol ICollection { get; } + public INamedTypeSymbol GenericIDictionary { get; } + public INamedTypeSymbol SystemGenericDictionary { get; } + public INamedTypeSymbol SystemGenericList { get; } + + public TypeCache(GeneratorExecutionContext context) + { + INamedTypeSymbol GetTypeByMetadataNameOrThrow(string fullyQualifiedMetadataName) + { + return context.Compilation.GetTypeByMetadataName(fullyQualifiedMetadataName) ?? + throw new InvalidOperationException("Type not found: " + fullyQualifiedMetadataName); + } + + GodotObjectType = GetTypeByMetadataNameOrThrow("Godot.Object"); + GodotGenericDictionary = GetTypeByMetadataNameOrThrow("Godot.Collections.Dictionary`2"); + GodotGenericArray = GetTypeByMetadataNameOrThrow("Godot.Collections.Array`1"); + IDictionary = GetTypeByMetadataNameOrThrow("System.Collections.IDictionary"); + ICollection = GetTypeByMetadataNameOrThrow("System.Collections.ICollection"); + GenericIDictionary = GetTypeByMetadataNameOrThrow("System.Collections.Generic.IDictionary`2"); + SystemGenericDictionary = GetTypeByMetadataNameOrThrow("System.Collections.Generic.Dictionary`2"); + SystemGenericList = GetTypeByMetadataNameOrThrow("System.Collections.Generic.List`1"); + } + } + + public static MarshalType? ConvertManagedTypeToVariantType(ITypeSymbol type, TypeCache typeCache) + { + var specialType = type.SpecialType; + + switch (specialType) + { + case SpecialType.System_Boolean: + return MarshalType.Boolean; + case SpecialType.System_Char: + return MarshalType.Char; + case SpecialType.System_SByte: + return MarshalType.SByte; + case SpecialType.System_Int16: + return MarshalType.Int16; + case SpecialType.System_Int32: + return MarshalType.Int32; + case SpecialType.System_Int64: + return MarshalType.Int64; + case SpecialType.System_Byte: + return MarshalType.Byte; + case SpecialType.System_UInt16: + return MarshalType.UInt16; + case SpecialType.System_UInt32: + return MarshalType.UInt32; + case SpecialType.System_UInt64: + return MarshalType.UInt64; + case SpecialType.System_Single: + return MarshalType.Single; + case SpecialType.System_Double: + return MarshalType.Double; + case SpecialType.System_String: + return MarshalType.String; + 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 arrayType = (IArrayTypeSymbol)type; + var elementType = arrayType.ElementType; + + switch (elementType.SpecialType) + { + case SpecialType.System_Byte: + return MarshalType.ByteArray; + case SpecialType.System_Int32: + return MarshalType.Int32Array; + case SpecialType.System_Int64: + return MarshalType.Int64Array; + case SpecialType.System_Single: + return MarshalType.SingleArray; + case SpecialType.System_Double: + return MarshalType.DoubleArray; + case SpecialType.System_String: + return MarshalType.StringArray; + case SpecialType.System_Object: + return MarshalType.SystemObjectArray; + } + + if (elementType.SimpleDerivesFrom(typeCache.GodotObjectType)) + return MarshalType.GodotObjectOrDerivedArray; + + if (type.ContainingAssembly.Name == "GodotSharp" && + type.ContainingNamespace.Name == "Godot") + { + return elementType switch + { + { Name: "Vector2" } => MarshalType.Vector2Array, + { Name: "Vector3" } => MarshalType.Vector3Array, + { Name: "Color" } => MarshalType.ColorArray, + _ => null + }; + } + } + else if (type is INamedTypeSymbol { IsGenericType: true } genericType) + { + var genericTypeDef = genericType.ConstructedFrom; + + if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.GodotGenericDictionary)) + return MarshalType.GodotGenericDictionary; + + if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.GodotGenericArray)) + return MarshalType.GodotGenericArray; + + if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.SystemGenericDictionary)) + return MarshalType.SystemGenericDictionary; + + if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.SystemGenericList)) + return MarshalType.SystemGenericList; + + if (SymbolEqualityComparer.Default.Equals(genericTypeDef, typeCache.GenericIDictionary)) + return MarshalType.GenericIDictionary; + + return genericTypeDef.SpecialType switch + { + SpecialType.System_Collections_Generic_ICollection_T => MarshalType.GenericICollection, + SpecialType.System_Collections_Generic_IEnumerable_T => MarshalType.GenericIEnumerable, + _ => null + }; + } + else + { + if (type.SimpleDerivesFrom(typeCache.GodotObjectType)) + return MarshalType.GodotObjectOrDerived; + + if (SymbolEqualityComparer.Default.Equals(type, typeCache.IDictionary)) + return MarshalType.IDictionary; + + if (SymbolEqualityComparer.Default.Equals(type, typeCache.ICollection)) + return MarshalType.ICollection; + + if (specialType == SpecialType.System_Collections_IEnumerable) + return MarshalType.IEnumerable; + + if (type.ContainingAssembly.Name == "GodotSharp") + { + switch (type.ContainingNamespace.Name) + { + case "Godot": + return type switch + { + { Name: "StringName" } => MarshalType.StringName, + { Name: "NodePath" } => MarshalType.NodePath, + _ => null + }; + case "Godot.Collections" when !(type is INamedTypeSymbol { IsGenericType: true }): + return type switch + { + { Name: "Dictionary" } => MarshalType.GodotDictionary, + { Name: "Array" } => MarshalType.GodotArray, + _ => null + }; + } + } + } + + break; + } + } + + return null; + } + + private static bool SimpleDerivesFrom(this ITypeSymbol? type, ITypeSymbol candidateBaseType) + { + while (type != null) + { + if (SymbolEqualityComparer.Default.Equals(type, candidateBaseType)) + return true; + + type = type.BaseType; + } + + return false; + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs new file mode 100644 index 00000000000..5ace809d959 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptBoilerplateGenerator.cs @@ -0,0 +1,423 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Godot.SourceGenerators +{ + [Generator] + public class ScriptBoilerplateGenerator : ISourceGenerator + { + public void Execute(GeneratorExecutionContext context) + { + if (context.AreGodotSourceGeneratorsDisabled()) + return; + + // False positive for RS1024. We're already using `SymbolEqualityComparer.Default`... +#pragma warning disable RS1024 + INamedTypeSymbol[] godotClasses = context + .Compilation.SyntaxTrees + .SelectMany(tree => + tree.GetRoot().DescendantNodes() + .OfType() + .SelectGodotScriptClasses(context.Compilation) + // Report and skip non-partial classes + .Where(x => + { + if (x.cds.IsPartial()) + return true; + Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); + return false; + }) + .Select(x => x.symbol) + ) + .Distinct(SymbolEqualityComparer.Default) + .ToArray(); +#pragma warning restore RS1024 + + 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 + ) + { + string className = symbol.Name; + + INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace; + string classNs = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ? + namespaceSymbol.FullQualifiedName() : + string.Empty; + bool hasNamespace = classNs.Length != 0; + + string uniqueName = hasNamespace ? + classNs + "." + className + "_ScriptBoilerplate_Generated" : + className + "_ScriptBoilerplate_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"); + } + + source.Append("partial class "); + source.Append(className); + source.Append("\n{\n"); + + var members = symbol.GetMembers(); + + // TODO: Static static marshaling (no reflection, no runtime type checks) + + var methodSymbols = members + .Where(s => s.Kind == SymbolKind.Method) + .Cast() + .Where(m => m.MethodKind == MethodKind.Ordinary && !m.IsImplicitlyDeclared); + + var propertySymbols = members + .Where(s => s.Kind == SymbolKind.Property) + .Cast(); + + var fieldSymbols = members + .Where(s => s.Kind == SymbolKind.Field) + .Cast() + .Where(p => !p.IsImplicitlyDeclared); + + var methods = WhereHasCompatibleGodotType(methodSymbols, typeCache).ToArray(); + var properties = WhereIsCompatibleGodotType(propertySymbols, typeCache).ToArray(); + var fields = WhereIsCompatibleGodotType(fieldSymbols, typeCache).ToArray(); + + source.Append(" private class GodotInternal {\n"); + + // Generate cached StringNames for methods and properties, for fast lookup + + foreach (var method in methods) + { + string methodName = method.Method.Name; + source.Append(" public static readonly StringName MethodName_"); + source.Append(methodName); + source.Append(" = \""); + source.Append(methodName); + source.Append("\";\n"); + } + + foreach (var property in properties) + { + 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 fields) + { + 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"); + + if (methods.Length > 0) + { + source.Append(" protected override bool InvokeGodotClassMethod(in godot_string_name method, "); + source.Append("NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n {\n"); + + foreach (var method in methods) + { + GenerateMethodInvoker(method, source); + } + + source.Append(" return base.InvokeGodotClassMethod(method, args, argCount, out ret);\n"); + + source.Append(" }\n"); + } + + if (properties.Length > 0 || fields.Length > 0) + { + // Setters + + source.Append(" protected override bool SetGodotClassPropertyValue(in godot_string_name name, "); + source.Append("in godot_variant value)\n {\n"); + + foreach (var property in properties) + { + GeneratePropertySetter(property.Property.Name, + property.Property.Type.FullQualifiedName(), source); + } + + foreach (var field in fields) + { + GeneratePropertySetter(field.Field.Name, + field.Field.Type.FullQualifiedName(), source); + } + + source.Append(" return base.SetGodotClassPropertyValue(name, value);\n"); + + source.Append(" }\n"); + + // Getters + + source.Append(" protected override bool GetGodotClassPropertyValue(in godot_string_name name, "); + source.Append("out godot_variant value)\n {\n"); + + foreach (var property in properties) + { + GeneratePropertyGetter(property.Property.Name, source); + } + + foreach (var field in fields) + { + GeneratePropertyGetter(field.Field.Name, source); + } + + source.Append(" return base.GetGodotClassPropertyValue(name, out value);\n"); + + source.Append(" }\n"); + } + + source.Append("}\n"); + + if (hasNamespace) + { + source.Append("\n}\n"); + } + + context.AddSource(uniqueName, SourceText.From(source.ToString(), Encoding.UTF8)); + } + + private static void GenerateMethodInvoker( + GodotMethodInfo method, + StringBuilder source + ) + { + string methodName = method.Method.Name; + + source.Append(" if (method == GodotInternal.MethodName_"); + source.Append(methodName); + source.Append(" && argCount == "); + source.Append(method.ParamTypes.Length); + source.Append(") {\n"); + + if (method.RetType != null) + source.Append(" object retBoxed = "); + else + source.Append(" "); + + source.Append(methodName); + source.Append("("); + + for (int i = 0; i < method.ParamTypes.Length; i++) + { + if (i != 0) + source.Append(", "); + + // TODO: static marshaling (no reflection, no runtime type checks) + + string paramTypeQualifiedName = method.ParamTypeSymbols[i].FullQualifiedName(); + + source.Append("("); + source.Append(paramTypeQualifiedName); + source.Append(")Marshaling.ConvertVariantToManagedObjectOfType(args["); + source.Append(i); + source.Append("], typeof("); + source.Append(paramTypeQualifiedName); + source.Append("))"); + } + + source.Append(");\n"); + + if (method.RetType != null) + { + // TODO: static marshaling (no reflection, no runtime type checks) + source.Append(" ret = Marshaling.ConvertManagedObjectToVariant(retBoxed);\n"); + source.Append(" return true;\n"); + } + else + { + source.Append(" ret = default;\n"); + source.Append(" return true;\n"); + } + + source.Append(" }\n"); + } + + private static void GeneratePropertySetter( + string propertyMemberName, + string propertyTypeQualifiedName, + StringBuilder source + ) + { + source.Append(" if (name == GodotInternal.PropName_"); + source.Append(propertyMemberName); + source.Append(") {\n"); + + source.Append(" "); + source.Append(propertyMemberName); + source.Append(" = "); + + // TODO: static marshaling (no reflection, no runtime type checks) + + source.Append("("); + source.Append(propertyTypeQualifiedName); + source.Append(")Marshaling.ConvertVariantToManagedObjectOfType(value, typeof("); + source.Append(propertyTypeQualifiedName); + source.Append("));\n"); + + source.Append(" return true;\n"); + + source.Append(" }\n"); + } + + private static void GeneratePropertyGetter( + string propertyMemberName, + StringBuilder source + ) + { + source.Append(" if (name == GodotInternal.PropName_"); + source.Append(propertyMemberName); + source.Append(") {\n"); + + // TODO: static marshaling (no reflection, no runtime type checks) + + source.Append(" value = Marshaling.ConvertManagedObjectToVariant("); + source.Append(propertyMemberName); + source.Append(");\n"); + source.Append(" return true;\n"); + + source.Append(" }\n"); + } + + public void Initialize(GeneratorInitializationContext context) + { + } + + private struct GodotMethodInfo + { + public GodotMethodInfo(IMethodSymbol method, ImmutableArray paramTypes, + ImmutableArray paramTypeSymbols, MarshalType? retType) + { + Method = method; + ParamTypes = paramTypes; + ParamTypeSymbols = paramTypeSymbols; + RetType = retType; + } + + public IMethodSymbol Method { get; } + public ImmutableArray ParamTypes { get; } + public ImmutableArray 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 WhereHasCompatibleGodotType( + IEnumerable 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.Select(p => + MarshalUtils.ConvertManagedTypeToVariantType(p.Type, typeCache)) + .Where(t => t != null).Cast().ToImmutableArray(); + + if (parameters.Length > paramTypes.Length) + continue; // Some param types weren't compatible + + yield return new GodotMethodInfo(method, paramTypes, parameters + .Select(p => p.Type).ToImmutableArray(), retType); + } + } + + private static IEnumerable WhereIsCompatibleGodotType( + IEnumerable properties, + MarshalUtils.TypeCache typeCache + ) + { + foreach (var property in properties) + { + var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(property.Type, typeCache); + + if (marshalType == null) + continue; + + yield return new GodotPropertyInfo(property, marshalType.Value); + } + } + + private static IEnumerable WhereIsCompatibleGodotType( + IEnumerable fields, + MarshalUtils.TypeCache typeCache + ) + { + foreach (var field in fields) + { + var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(field.Type, typeCache); + + if (marshalType == null) + continue; + + yield return new GodotFieldInfo(field, marshalType.Value); + } + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index fa65595290b..f0b27583424 100644 --- a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs @@ -14,13 +14,10 @@ namespace Godot.SourceGenerators { public void Execute(GeneratorExecutionContext context) { - if (context.TryGetGlobalAnalyzerProperty("GodotScriptPathAttributeGenerator", out string? toggle) - && toggle == "disabled") - { + if (context.AreGodotSourceGeneratorsDisabled()) return; - } - // NOTE: IsNullOrEmpty doesn't work well with nullable checks + // NOTE: NotNullWhen diagnostics don't work on projects targeting .NET Standard 2.0 // ReSharper disable once ReplaceWithStringIsNullOrEmpty if (!context.TryGetGlobalAnalyzerProperty("GodotProjectDir", out string? godotProjectDir) || godotProjectDir!.Length == 0) @@ -28,7 +25,8 @@ namespace Godot.SourceGenerators throw new InvalidOperationException("Property 'GodotProjectDir' is null or empty."); } - var godotClasses = context.Compilation.SyntaxTrees + Dictionary> godotClasses = context + .Compilation.SyntaxTrees .SelectMany(tree => tree.GetRoot().DescendantNodes() .OfType() @@ -38,7 +36,7 @@ namespace Godot.SourceGenerators // Report and skip non-partial classes .Where(x => { - if (x.cds.IsPartial() || x.symbol.HasDisableGeneratorsAttribute()) + if (x.cds.IsPartial()) return true; Common.ReportNonPartialGodotScriptClass(context, x.cds, x.symbol); return false; diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs new file mode 100644 index 00000000000..ec04a319e21 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + // Placeholder. Once we switch to native extensions this will act as the registrar for all + // user Godot classes in the assembly. Think of it as something similar to `register_types`. + public class ScriptRegistrarGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + throw new System.NotImplementedException(); + } + + public void Execute(GeneratorExecutionContext context) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index cce00a6ae13..89265d8f2b7 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -81,9 +81,12 @@ StringBuilder &operator<<(StringBuilder &r_sb, const char *p_cstring) { #define CS_STATIC_METHOD_GETINSTANCE "GetPtr" #define CS_METHOD_CALL "Call" #define CS_PROPERTY_SINGLETON "Singleton" +#define CS_METHOD_INVOKE_GODOT_CLASS_METHOD "InvokeGodotClassMethod" #define CS_STATIC_FIELD_NATIVE_CTOR "NativeCtor" #define CS_STATIC_FIELD_METHOD_BIND_PREFIX "MethodBind" +#define CS_STATIC_FIELD_METHOD_NAME_PREFIX "MethodName_" +#define CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX "MethodProxyName_" #define CS_STATIC_FIELD_SIGNAL_NAME_PREFIX "SignalName_" #define ICALL_PREFIX "godot_icall_" @@ -1604,11 +1607,27 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str // Script calls if (!itype.is_singleton && (is_derived_type || itype.has_virtual_methods)) { - // TODO: string is ok for now. But should be replaced with StringName in the future for performance. + for (const MethodInterface &imethod : itype.methods) { + if (!imethod.is_virtual) { + continue; + } - output << MEMBER_BEGIN "internal " << (is_derived_type ? "override" : "virtual") - << " bool InternalGodotScriptCall(string method, NativeVariantPtrArgs args, " - << "int argCount, out godot_variant ret)\n" + output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n" + << INDENT2 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT2 "private static readonly StringName " + << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name + << " = \"" << imethod.name << "\";\n"; + + output << MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n" + << INDENT2 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT2 "private static readonly StringName " + << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name + << " = \"" << imethod.proxy_name << "\";\n"; + } + + output << MEMBER_BEGIN "protected internal " << (is_derived_type ? "override" : "virtual") + << " bool " CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(in godot_string_name method, " + << "NativeVariantPtrArgs args, int argCount, out godot_variant ret)\n" << INDENT2 "{\n"; for (const MethodInterface &imethod : itype.methods) { @@ -1616,11 +1635,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str continue; } - // TODO: - // Compare with cached StringName. We already have a cached StringName - // field for the proxy name. We need one for the original snake_case name. - output << INDENT3 "if ((method == nameof(" << imethod.proxy_name << ") || method == \"" << imethod.name - << "\") && argCount == " << itos(imethod.arguments.size()) << ")\n" + output << INDENT3 "if ((method == " << CS_STATIC_FIELD_METHOD_PROXY_NAME_PREFIX << imethod.name + << " || method == " << CS_STATIC_FIELD_METHOD_NAME_PREFIX << imethod.name + << ") && argCount == " << itos(imethod.arguments.size()) << ")\n" << INDENT3 "{\n"; if (imethod.return_type.cname != name_cache.type_void) { @@ -1668,9 +1685,10 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str } if (is_derived_type) { - output << INDENT3 "return base.InternalGodotScriptCall(method, args, argCount, out ret);\n"; + output << INDENT3 "return base." CS_METHOD_INVOKE_GODOT_CLASS_METHOD "(method, args, argCount, out ret);\n"; } else { - output << INDENT3 "return InternalGodotScriptCallViaReflection(method, args, argCount, out ret);\n"; + output << INDENT3 "ret = default;\n" + << INDENT3 "return false;\n"; } output << INDENT2 "}\n"; @@ -1989,8 +2007,8 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf // Generate method { if (!p_imethod.is_virtual && !p_imethod.requires_object_call) { - p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN "private static readonly IntPtr " - << method_bind_field << " = "; + p_output << MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]\n" + << INDENT2 "private static readonly IntPtr " << method_bind_field << " = "; if (p_itype.is_singleton) { // Singletons are static classes. They don't derive Godot.Object, @@ -2024,16 +2042,6 @@ Error BindingsGenerator::_generate_cs_method(const BindingsGenerator::TypeInterf p_output.append(default_args_doc.as_string()); } - if (!p_imethod.is_internal) { - // TODO: This alone adds ~0.2 MB of bloat to the core API assembly. It would be - // better to generate a table in the C++ glue instead. That way the strings wouldn't - // add that much extra bloat as they're already used in engine code. Also, it would - // probably be much faster than looking up the attributes when fetching methods. - p_output.append(MEMBER_BEGIN "[GodotMethod(\""); - p_output.append(p_imethod.name); - p_output.append("\")]"); - } - if (p_imethod.is_deprecated) { if (p_imethod.deprecation_message.is_empty()) { WARN_PRINT("An empty deprecation message is discouraged. Method: '" + p_imethod.proxy_name + "'."); @@ -2196,8 +2204,9 @@ Error BindingsGenerator::_generate_cs_signal(const BindingsGenerator::TypeInterf // If so, we could store the pointer we get from `data_unique_pointer()` instead of allocating StringName here. // Cached signal name (StringName) - p_output.append(MEMBER_BEGIN "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN - "private static readonly StringName " CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); + p_output.append(MEMBER_BEGIN "// ReSharper disable once InconsistentNaming\n"); + p_output.append(INDENT2 "[DebuggerBrowsable(DebuggerBrowsableState.Never)]" MEMBER_BEGIN + "private static readonly StringName " CS_STATIC_FIELD_SIGNAL_NAME_PREFIX); p_output.append(p_isignal.name); p_output.append(" = \""); p_output.append(p_isignal.name); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs deleted file mode 100644 index 0b00878e8cd..00000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Godot -{ - /// - /// An attribute that disables Godot Generators. - /// - [AttributeUsage(AttributeTargets.Class)] - public class DisableGodotGeneratorsAttribute : Attribute { } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs index 6bb1ba27e93..f28d7b1c51e 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs @@ -12,7 +12,6 @@ namespace Godot.Bridge { try { - // Performance is not critical here as this will be replaced with source generators. var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; if (godotObject == null) @@ -27,7 +26,7 @@ namespace Godot.Bridge using (dest) methodStr = Marshaling.ConvertStringToManaged(dest); - bool methodInvoked = godotObject.InternalGodotScriptCall(methodStr, new NativeVariantPtrArgs(args), + bool methodInvoked = godotObject.InvokeGodotClassMethod(CustomUnsafe.AsRef(method), new NativeVariantPtrArgs(args), argCount, out godot_variant retValue); if (!methodInvoked) @@ -55,21 +54,19 @@ namespace Godot.Bridge { try { - // Performance is not critical here as this will be replaced with source generators. var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; if (godotObject == null) throw new InvalidOperationException(); - var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( - NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name))); - - if (godotObject.InternalGodotScriptSetFieldOrPropViaReflection( - nameManaged.ToString(), CustomUnsafe.AsRef(value))) + if (godotObject.SetGodotClassPropertyValue(CustomUnsafe.AsRef(name), CustomUnsafe.AsRef(value))) { return true.ToGodotBool(); } + var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name))); + object valueManaged = Marshaling.ConvertVariantToManagedObject(CustomUnsafe.AsRef(value)); return godotObject._Set(nameManaged, valueManaged).ToGodotBool(); @@ -87,22 +84,20 @@ namespace Godot.Bridge { try { - // Performance is not critical here as this will be replaced with source generators. var godotObject = (Object)GCHandle.FromIntPtr(godotObjectGCHandle).Target; if (godotObject == null) throw new InvalidOperationException(); - var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( - NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name))); - - if (godotObject.InternalGodotScriptGetFieldOrPropViaReflection(nameManaged.ToString(), - out godot_variant outRetValue)) + if (godotObject.GetGodotClassPropertyValue(CustomUnsafe.AsRef(name), out godot_variant outRetValue)) { *outRet = outRetValue; return true.ToGodotBool(); } + var nameManaged = StringName.CreateTakingOwnershipOfDisposableValue( + NativeFuncs.godotsharp_string_name_new_copy(CustomUnsafe.AsRef(name))); + object ret = godotObject._Get(nameManaged); if (ret == null) diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs index ef20819d62f..f15dc941c96 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/InteropStructs.cs @@ -487,6 +487,31 @@ namespace Godot.NativeInterop get => _data == IntPtr.Zero; } + public static bool operator ==(godot_string_name left, godot_string_name right) + { + return left._data == right._data; + } + + public static bool operator !=(godot_string_name left, godot_string_name right) + { + return !(left == right); + } + + public bool Equals(godot_string_name other) + { + return _data == other._data; + } + + public override bool Equals(object obj) + { + return obj is StringName s && s.Equals(this); + } + + public override int GetHashCode() + { + return _data.GetHashCode(); + } + [StructLayout(LayoutKind.Sequential)] // ReSharper disable once InconsistentNaming internal struct movable diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index f1cccfed0a7..c2812b89199 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -26,7 +26,7 @@ namespace Godot.NativeInterop [DllImport(GodotDllName)] public static extern IntPtr godotsharp_method_bind_get_method(in godot_string_name p_classname, - char* p_methodname); + in godot_string_name p_methodname); [DllImport(GodotDllName)] public static extern delegate* unmanaged godotsharp_get_class_constructor( diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs index 5f4dc50c729..dbffd1d5d1f 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Object.base.cs @@ -185,125 +185,20 @@ namespace Godot return assemblyName.Name == "GodotSharp" || assemblyName.Name == "GodotSharpEditor"; } - internal bool InternalGodotScriptCallViaReflection(string method, NativeVariantPtrArgs args, int argCount, - out godot_variant ret) + // ReSharper disable once VirtualMemberNeverOverridden.Global + protected internal virtual bool SetGodotClassPropertyValue(in godot_string_name name, in godot_variant value) { - // Performance is not critical here as this will be replaced with source generators. - Type top = GetType(); - Type native = InternalGetClassNativeBase(top); - - while (top != null && top != native) - { - var methodInfo = top.GetMethod(method, - BindingFlags.DeclaredOnly | BindingFlags.Instance | - BindingFlags.NonPublic | BindingFlags.Public); - - if (methodInfo != null) - { - var parameters = methodInfo.GetParameters(); - int paramCount = parameters.Length; - - if (argCount == paramCount) - { - object[] invokeParams = new object[paramCount]; - - for (int i = 0; i < paramCount; i++) - { - invokeParams[i] = Marshaling.ConvertVariantToManagedObjectOfType( - args[i], parameters[i].ParameterType); - } - - object retObj = methodInfo.Invoke(this, invokeParams); - - ret = Marshaling.ConvertManagedObjectToVariant(retObj); - return true; - } - } - - top = top.BaseType; - } - - ret = default; return false; } - internal bool InternalGodotScriptSetFieldOrPropViaReflection(string name, in godot_variant value) + // ReSharper disable once VirtualMemberNeverOverridden.Global + protected internal virtual bool GetGodotClassPropertyValue(in godot_string_name name, out godot_variant value) { - // Performance is not critical here as this will be replaced with source generators. - Type top = GetType(); - Type native = InternalGetClassNativeBase(top); - - while (top != null && top != native) - { - var fieldInfo = top.GetField(name, - BindingFlags.DeclaredOnly | BindingFlags.Instance | - BindingFlags.NonPublic | BindingFlags.Public); - - if (fieldInfo != null) - { - object valueManaged = Marshaling.ConvertVariantToManagedObjectOfType(value, fieldInfo.FieldType); - fieldInfo.SetValue(this, valueManaged); - - return true; - } - - var propertyInfo = top.GetProperty(name, - BindingFlags.DeclaredOnly | BindingFlags.Instance | - BindingFlags.NonPublic | BindingFlags.Public); - - if (propertyInfo != null) - { - object valueManaged = - Marshaling.ConvertVariantToManagedObjectOfType(value, propertyInfo.PropertyType); - propertyInfo.SetValue(this, valueManaged); - - return true; - } - - top = top.BaseType; - } - - return false; - } - - internal bool InternalGodotScriptGetFieldOrPropViaReflection(string name, out godot_variant value) - { - // Performance is not critical here as this will be replaced with source generators. - Type top = GetType(); - Type native = InternalGetClassNativeBase(top); - - while (top != null && top != native) - { - var fieldInfo = top.GetField(name, - BindingFlags.DeclaredOnly | BindingFlags.Instance | - BindingFlags.NonPublic | BindingFlags.Public); - - if (fieldInfo != null) - { - object valueManaged = fieldInfo.GetValue(this); - value = Marshaling.ConvertManagedObjectToVariant(valueManaged); - return true; - } - - var propertyInfo = top.GetProperty(name, - BindingFlags.DeclaredOnly | BindingFlags.Instance | - BindingFlags.NonPublic | BindingFlags.Public); - - if (propertyInfo != null) - { - object valueManaged = propertyInfo.GetValue(this); - value = Marshaling.ConvertManagedObjectToVariant(valueManaged); - return true; - } - - top = top.BaseType; - } - value = default; return false; } - internal unsafe void InternalRaiseEventSignal(in godot_string_name eventSignalName, NativeVariantPtrArgs args, + internal void InternalRaiseEventSignal(in godot_string_name eventSignalName, NativeVariantPtrArgs args, int argc) { // Performance is not critical here as this will be replaced with source generators. @@ -371,14 +266,11 @@ namespace Godot } } - internal static unsafe IntPtr ClassDB_get_method(StringName type, string method) + internal static IntPtr ClassDB_get_method(StringName type, StringName method) { - IntPtr methodBind; - fixed (char* methodChars = method) - { - var typeSelf = (godot_string_name)type.NativeValue; - methodBind = NativeFuncs.godotsharp_method_bind_get_method(typeSelf, methodChars); - } + var typeSelf = (godot_string_name)type.NativeValue; + var methodSelf = (godot_string_name)method.NativeValue; + IntPtr methodBind = NativeFuncs.godotsharp_method_bind_get_method(typeSelf, methodSelf); if (methodBind == IntPtr.Zero) throw new NativeMethodBindNotFoundException(type + "." + method); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs index e8bda9b219e..3a415d3deb3 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringName.cs @@ -92,5 +92,51 @@ namespace Godot /// /// If the is empty. public bool IsEmpty => NativeValue.DangerousSelfRef.IsEmpty; + + public static bool operator ==(StringName left, StringName right) + { + if (left is null) + return right is null; + return left.Equals(right); + } + + public static bool operator !=(StringName left, StringName right) + { + return !(left == right); + } + + public bool Equals(StringName other) + { + if (other is null) + return false; + return NativeValue.DangerousSelfRef == other.NativeValue.DangerousSelfRef; + } + + public static bool operator ==(StringName left, in godot_string_name right) + { + if (left is null) + return right.IsEmpty; + return left.Equals(right); + } + + public static bool operator !=(StringName left, in godot_string_name right) + { + return !(left == right); + } + + public static bool operator ==(in godot_string_name left, StringName right) + { + return right == left; + } + + public static bool operator !=(in godot_string_name left, StringName right) + { + return !(right == left); + } + + public bool Equals(in godot_string_name other) + { + return NativeValue.DangerousSelfRef == other; + } } } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj index a06b448136e..d5bbbfb7ca7 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -32,7 +32,6 @@ - diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index c5db869a05f..c3d4f53048d 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -65,8 +65,8 @@ static_assert(sizeof(SafeRefCount) == sizeof(uint32_t)); typedef Object *(*godotsharp_class_creation_func)(); -GD_PINVOKE_EXPORT MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, const char16_t *p_methodname) { - return ClassDB::get_method(*p_classname, StringName(String::utf16(p_methodname))); +GD_PINVOKE_EXPORT MethodBind *godotsharp_method_bind_get_method(const StringName *p_classname, const StringName *p_methodname) { + return ClassDB::get_method(*p_classname, *p_methodname); } GD_PINVOKE_EXPORT godotsharp_class_creation_func godotsharp_get_class_constructor(const StringName *p_classname) {