Пример #1
0
    public void Execute(GeneratorExecutionContext context)
    {
        INamedTypeSymbol[] unmanagedCallbacksClasses = context
                                                       .Compilation.SyntaxTrees
                                                       .SelectMany(tree =>
                                                                   tree.GetRoot().DescendantNodes()
                                                                   .OfType <ClassDeclarationSyntax>()
                                                                   .SelectUnmanagedCallbacksClasses(context.Compilation)
                                                                   // Report and skip non-partial classes
                                                                   .Where(x =>
        {
            if (x.cds.IsPartial())
            {
                if (x.cds.IsNested() && !x.cds.AreAllOuterTypesPartial(out var typeMissingPartial))
                {
                    Common.ReportNonPartialUnmanagedCallbacksOuterClass(context, typeMissingPartial !);
                    return(false);
                }

                return(true);
            }

            Common.ReportNonPartialUnmanagedCallbacksClass(context, x.cds, x.symbol);
            return(false);
        })
                                                                   .Select(x => x.symbol)
                                                                   )
                                                       .Distinct <INamedTypeSymbol>(SymbolEqualityComparer.Default)
                                                       .ToArray();

        foreach (var symbol in unmanagedCallbacksClasses)
        {
            var attr = symbol.GetGenerateUnmanagedCallbacksAttribute();
            if (attr == null || attr.ConstructorArguments.Length != 1)
            {
                // TODO: Report error or throw exception, this is an invalid case and should never be reached
                System.Diagnostics.Debug.Fail("FAILED!");
                continue;
            }

            var funcStructType = (INamedTypeSymbol?)attr.ConstructorArguments[0].Value;
            if (funcStructType == null)
            {
                // TODO: Report error or throw exception, this is an invalid case and should never be reached
                System.Diagnostics.Debug.Fail("FAILED!");
                continue;
            }

            var data = new CallbacksData(symbol, funcStructType);
            GenerateInteropMethodImplementations(context, data);
            GenerateUnmanagedCallbacksStruct(context, data);
        }
    }
Пример #2
0
    private void GenerateInteropMethodImplementations(GeneratorExecutionContext context, CallbacksData data)
    {
        var symbol = data.NativeTypeSymbol;

        INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
        string           classNs         = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
                                           namespaceSymbol.FullQualifiedName() :
                                           string.Empty;
        bool hasNamespace = classNs.Length != 0;
        bool isInnerClass = symbol.ContainingType != null;

        var source                = new StringBuilder();
        var methodSource          = new StringBuilder();
        var methodCallArguments   = new StringBuilder();
        var methodSourceAfterCall = new StringBuilder();

        source.Append(
            @"using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Godot.Bridge;
using Godot.NativeInterop;

#pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores

");

        if (hasNamespace)
        {
            source.Append("namespace ");
            source.Append(classNs);
            source.Append("\n{\n");
        }

        if (isInnerClass)
        {
            var containingType = symbol.ContainingType;

            while (containingType != null)
            {
                source.Append("partial ");
                source.Append(containingType.GetDeclarationKeyword());
                source.Append(" ");
                source.Append(containingType.NameWithTypeParameters());
                source.Append("\n{\n");

                containingType = containingType.ContainingType;
            }
        }

        source.Append("[System.Runtime.CompilerServices.SkipLocalsInit]\n");
        source.Append($"unsafe partial class {symbol.Name}\n");
        source.Append("{\n");
        source.Append($"    private static {data.FuncStructSymbol.FullQualifiedName()} _unmanagedCallbacks;\n\n");

        foreach (var callback in data.Methods)
        {
            methodSource.Clear();
            methodCallArguments.Clear();
            methodSourceAfterCall.Clear();

            source.Append("    [global::System.Runtime.CompilerServices.MethodImpl(global::System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]\n");
            source.Append($"    {SyntaxFacts.GetText(callback.DeclaredAccessibility)} ");

            if (callback.IsStatic)
            {
                source.Append("static ");
            }

            source.Append("partial ");
            source.Append(callback.ReturnType.FullQualifiedName());
            source.Append(' ');
            source.Append(callback.Name);
            source.Append('(');

            for (int i = 0; i < callback.Parameters.Length; i++)
            {
                var parameter = callback.Parameters[i];

                source.Append(parameter.ToDisplayString());
                source.Append(' ');
                source.Append(parameter.Name);

                if (parameter.RefKind == RefKind.Out)
                {
                    // Only assign default if the parameter won't be passed by-ref or copied later.
                    if (IsGodotInteropStruct(parameter.Type))
                    {
                        methodSource.Append($"        {parameter.Name} = default;\n");
                    }
                }

                if (IsByRefParameter(parameter))
                {
                    if (IsGodotInteropStruct(parameter.Type))
                    {
                        methodSource.Append("        ");
                        AppendCustomUnsafeAsPointer(methodSource, parameter, out string varName);
                        methodCallArguments.Append(varName);
                    }
                    else if (parameter.Type.IsValueType)
                    {
                        methodSource.Append("        ");
                        AppendCopyToStackAndGetPointer(methodSource, parameter, out string varName);
                        methodCallArguments.Append($"&{varName}");

                        if (parameter.RefKind is RefKind.Out or RefKind.Ref)
                        {
                            methodSourceAfterCall.Append($"        {parameter.Name} = {varName};\n");
                        }
                    }
                    else
                    {
                        // If it's a by-ref param and we can't get the pointer
                        // just pass it by-ref and let it be pinned.
                        AppendRefKind(methodCallArguments, parameter.RefKind)
                        .Append(' ')
                        .Append(parameter.Name);
                    }
                }
                else
                {
                    methodCallArguments.Append(parameter.Name);
                }

                if (i < callback.Parameters.Length - 1)
                {
                    source.Append(", ");
                    methodCallArguments.Append(", ");
                }
            }

            source.Append(")\n");
            source.Append("    {\n");

            source.Append(methodSource);
            source.Append("        ");

            if (!callback.ReturnsVoid)
            {
                if (methodSourceAfterCall.Length != 0)
                {
                    source.Append($"{callback.ReturnType.FullQualifiedName()} ret = ");
                }
                else
                {
                    source.Append("return ");
                }
            }

            source.Append($"_unmanagedCallbacks.{callback.Name}(");
            source.Append(methodCallArguments);
            source.Append(");\n");

            if (methodSourceAfterCall.Length != 0)
            {
                source.Append(methodSourceAfterCall);

                if (!callback.ReturnsVoid)
                {
                    source.Append("        return ret;\n");
                }
            }

            source.Append("    }\n\n");
        }

        source.Append("}\n");

        if (isInnerClass)
        {
            var containingType = symbol.ContainingType;

            while (containingType != null)
            {
                source.Append("}\n"); // outer class

                containingType = containingType.ContainingType;
            }
        }

        if (hasNamespace)
        {
            source.Append("\n}");
        }

        source.Append("\n\n#pragma warning restore CA1707\n");

        context.AddSource($"{data.NativeTypeSymbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated",
                          SourceText.From(source.ToString(), Encoding.UTF8));
    }
Пример #3
0
    private void GenerateUnmanagedCallbacksStruct(GeneratorExecutionContext context, CallbacksData data)
    {
        var symbol = data.FuncStructSymbol;

        INamespaceSymbol namespaceSymbol = symbol.ContainingNamespace;
        string           classNs         = namespaceSymbol != null && !namespaceSymbol.IsGlobalNamespace ?
                                           namespaceSymbol.FullQualifiedName() :
                                           string.Empty;
        bool hasNamespace = classNs.Length != 0;
        bool isInnerClass = symbol.ContainingType != null;

        var source = new StringBuilder();

        source.Append(
            @"using System.Runtime.InteropServices;
using Godot.NativeInterop;

#pragma warning disable CA1707 // Disable warning: Identifiers should not contain underscores

");
        if (hasNamespace)
        {
            source.Append("namespace ");
            source.Append(classNs);
            source.Append("\n{\n");
        }

        if (isInnerClass)
        {
            var containingType = symbol.ContainingType;

            while (containingType != null)
            {
                source.Append("partial ");
                source.Append(containingType.GetDeclarationKeyword());
                source.Append(" ");
                source.Append(containingType.NameWithTypeParameters());
                source.Append("\n{\n");

                containingType = containingType.ContainingType;
            }
        }

        source.Append("[StructLayout(LayoutKind.Sequential)]\n");
        source.Append($"unsafe partial struct {symbol.Name}\n{{\n");

        foreach (var callback in data.Methods)
        {
            source.Append("    ");
            source.Append(callback.DeclaredAccessibility == Accessibility.Public ? "public " : "internal ");

            source.Append("delegate* unmanaged<");

            foreach (var parameter in callback.Parameters)
            {
                if (IsByRefParameter(parameter))
                {
                    if (IsGodotInteropStruct(parameter.Type) || parameter.Type.IsValueType)
                    {
                        AppendPointerType(source, parameter.Type);
                    }
                    else
                    {
                        // If it's a by-ref param and we can't get the pointer
                        // just pass it by-ref and let it be pinned.
                        AppendRefKind(source, parameter.RefKind)
                        .Append(' ')
                        .Append(parameter.Type.FullQualifiedName());
                    }
                }
                else
                {
                    source.Append(parameter.Type.FullQualifiedName());
                }

                source.Append(", ");
            }

            source.Append(callback.ReturnType.FullQualifiedName());
            source.Append($"> {callback.Name};\n");
        }

        source.Append("}\n");

        if (isInnerClass)
        {
            var containingType = symbol.ContainingType;

            while (containingType != null)
            {
                source.Append("}\n"); // outer class

                containingType = containingType.ContainingType;
            }
        }

        if (hasNamespace)
        {
            source.Append("}\n");
        }

        source.Append("\n#pragma warning restore CA1707\n");

        context.AddSource($"{symbol.FullQualifiedName().SanitizeQualifiedNameForUniqueHint()}.generated",
                          SourceText.From(source.ToString(), Encoding.UTF8));
    }