diff options
author | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2021-12-28 23:25:16 +0100 |
---|---|---|
committer | Ignacio Roldán Etcheverry <ignalfonsore@gmail.com> | 2022-08-22 03:36:51 +0200 |
commit | 4d710bf659c0bea5b8f3d6ec65eda047bada0e02 (patch) | |
tree | 72868f86b8507eddf706004175865dbefa81972a | |
parent | e5e7a795b14487e7eb0cfb011a8e0518769ce533 (diff) |
C#: Add initial implementation of source generator for script members
This replaces the way we invoke methods and set/get properties.
This first iteration rids us of runtime type checking in those
cases, as it's now done at compile time.
Later it will also stop needing the use of reflection. After that,
we will only depend on reflection for generic Godot Array and
Dictionary. We're stuck with reflection in generic collections
for now as C# doesn't support generic/template specialization.
This is only the initial implementation. Further iterations are
coming, specially once we switch to the native extension system
which completely changes the way members are accessed/invoked.
For example, with the native extension system we will likely need
to create `UnmanagedCallersOnly` invoke wrapper methods and return
function pointers to the engine.
Other kind of members, like event signals will be receiving the
same treatment in the future.
20 files changed, 908 insertions, 189 deletions
diff --git a/modules/mono/SdkPackageVersions.props b/modules/mono/SdkPackageVersions.props index bdec051625..2bd60bb719 100644 --- a/modules/mono/SdkPackageVersions.props +++ b/modules/mono/SdkPackageVersions.props @@ -1,7 +1,7 @@ <Project> <PropertyGroup> <PackageFloatingVersion_Godot>4.0.*-*</PackageFloatingVersion_Godot> - <PackageVersion_Godot_NET_Sdk>4.0.0-dev6</PackageVersion_Godot_NET_Sdk> - <PackageVersion_Godot_SourceGenerators>4.0.0-dev3</PackageVersion_Godot_SourceGenerators> + <PackageVersion_Godot_NET_Sdk>4.0.0-dev7</PackageVersion_Godot_NET_Sdk> + <PackageVersion_Godot_SourceGenerators>4.0.0-dev6</PackageVersion_Godot_SourceGenerators> </PropertyGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators.Sample/ScriptBoilerplate.cs new file mode 100644 index 0000000000..05723c8940 --- /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 4867c986e6..4dd67252ed 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 e16f72f43a..f8c50e66c8 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 f9b47ad5b1..5025215d34 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 @@ <ItemGroup> <!-- $(GodotProjectDir) is defined by Godot.NET.Sdk --> <CompilerVisibleProperty Include="GodotProjectDir" /> - <CompilerVisibleProperty Include="GodotScriptPathAttributeGenerator" /> + <CompilerVisibleProperty Include="EnableGodotGenerators" /> </ItemGroup> </Project> diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/GodotClasses.cs index 29e41d155a..7cc8fa17fc 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 0000000000..7c8345d16a --- /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 0000000000..a77e1800fb --- /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 0000000000..5ace809d95 --- /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<ClassDeclarationSyntax>() + .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<INamedTypeSymbol>(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<IMethodSymbol>() + .Where(m => m.MethodKind == MethodKind.Ordinary && !m.IsImplicitlyDeclared); + + var propertySymbols = members + .Where(s => s.Kind == SymbolKind.Property) + .Cast<IPropertySymbol>(); + + var fieldSymbols = members + .Where(s => s.Kind == SymbolKind.Field) + .Cast<IFieldSymbol>() + .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<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.Select(p => + MarshalUtils.ConvertManagedTypeToVariantType(p.Type, typeCache)) + .Where(t => t != null).Cast<MarshalType>().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<GodotPropertyInfo> WhereIsCompatibleGodotType( + IEnumerable<IPropertySymbol> 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<GodotFieldInfo> WhereIsCompatibleGodotType( + IEnumerable<IFieldSymbol> fields, + MarshalUtils.TypeCache typeCache + ) + { + foreach (var field in fields) + { + var marshalType = MarshalUtils.ConvertManagedTypeToVariantType(field.Type, typeCache); + + if (marshalType == null) + continue; + + yield return new GodotFieldInfo(field, marshalType.Value); + } + } + } +} diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPathAttributeGenerator.cs index fa65595290..f0b2758342 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<INamedTypeSymbol, IEnumerable<ClassDeclarationSyntax>> godotClasses = context + .Compilation.SyntaxTrees .SelectMany(tree => tree.GetRoot().DescendantNodes() .OfType<ClassDeclarationSyntax>() @@ -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 0000000000..ec04a319e2 --- /dev/null +++ b/modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptRegistrarGenerator.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; + +namespace Godot.SourceGenerators +{ + // Placeholder. Once we switch to native extensions this will act as the registrar for all + // user Godot classes in the assembly. Think of it as something similar to `register_types`. + public class ScriptRegistrarGenerator : ISourceGenerator + { + public void Initialize(GeneratorInitializationContext context) + { + throw new System.NotImplementedException(); + } + + public void Execute(GeneratorExecutionContext context) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index cce00a6ae1..89265d8f2b 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 "// 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 "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_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 0b00878e8c..0000000000 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Attributes/DisableGodotGeneratorsAttribute.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Godot -{ - /// <summary> - /// An attribute that disables Godot Generators. - /// </summary> - [AttributeUsage(AttributeTargets.Class)] - public class DisableGodotGeneratorsAttribute : Attribute { } -} diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/CSharpInstanceBridge.cs index 6bb1ba27e9..f28d7b1c51 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 ef20819d62..f15dc941c9 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 f1cccfed0a..c2812b8919 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<IntPtr> 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 5f4dc50c72..dbffd1d5d1 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) - { - // 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) + // 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 = 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 e8bda9b219..3a415d3deb 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 /// </summary> /// <returns>If the <see cref="StringName"/> is empty.</returns> 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 a06b448136..d5bbbfb7ca 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj +++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj @@ -32,7 +32,6 @@ <Compile Include="Core\AABB.cs" /> <Compile Include="Core\Array.cs" /> <Compile Include="Core\Attributes\AssemblyHasScriptsAttribute.cs" /> - <Compile Include="Core\Attributes\DisableGodotGeneratorsAttribute.cs" /> <Compile Include="Core\Attributes\ExportAttribute.cs" /> <Compile Include="Core\Attributes\GodotMethodAttribute.cs" /> <Compile Include="Core\Attributes\RPCAttribute.cs" /> diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index c5db869a05..c3d4f53048 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) { |