Xlinka / Project-Obsidian

Project Obsidian: A Resonite plugin adding extended Protoflux nodes and features for enhanced functionality and user experience
GNU General Public License v3.0
16 stars 6 forks source link

Binding Generator - Community #3

Closed Xlinka closed 11 months ago

Xlinka commented 1 year ago

Instead of waiting for the main team to make a Protoflux generator public we can make our own for the time being using a source generator this will take some time to figure out all the bugs but I'm sure we can do it. as this will help the plugin community as a whole.

Xlinka commented 1 year ago
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>9.0</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
  </ItemGroup>

</Project>

using System;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

[Generator]
public class ProtoFluxBindingGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var sourceBuilder = new StringBuilder();

        var nodes = context.Compilation.SyntaxTrees
            .SelectMany(tree => tree.GetRoot().DescendantNodes())
            .OfType<ClassDeclarationSyntax>()
            .Where(cls => cls.Identifier.Text.EndsWith("Node") && cls.BaseList != null);

        foreach (var node in nodes)
        {
            var nodeName = node.Identifier.Text;
            var bindingName = nodeName + "Binding";
            var namespaceName = ((NamespaceDeclarationSyntax)node.Parent).Name;
            var baseType = node.BaseList.Types.First().Type.ToString();

            // Input Properties
            var inputProperties = node.DescendantNodes()
                .OfType<PropertyDeclarationSyntax>()
                .Where(prop => prop.Modifiers.Any(mod => mod.Text == "public"));

            sourceBuilder.AppendLine("using System;");
            sourceBuilder.AppendLine("using FrooxEngine;");
            sourceBuilder.AppendLine("using FrooxEngine.ProtoFlux;");
            sourceBuilder.AppendLine($"namespace {namespaceName}");
            sourceBuilder.AppendLine("{");
            sourceBuilder.AppendLine($"    public class {bindingName} : FrooxEngine.ProtoFlux.Runtimes.Execution.{baseType}Binding<{nodeName}>");
            sourceBuilder.AppendLine("    {");

            // Generate SyncRef Fields
            foreach (var inputProperty in inputProperties)
            {
                var propertyType = inputProperty.Type.ToString();
                var propertyName = inputProperty.Identifier.Text;
                sourceBuilder.AppendLine($"        public readonly SyncRef<INodeValueOutput<{propertyType}>> {propertyName};");
            }

            // NodeType Property
            sourceBuilder.AppendLine($"        public override Type NodeType => typeof({nodeName});");

            // NodeInstance Property and Fields
            sourceBuilder.AppendLine($"        public {nodeName} TypedNodeInstance {{ get; private set; }}");
            sourceBuilder.AppendLine("        public override INode NodeInstance => TypedNodeInstance;");

            // Instantiate Method
            sourceBuilder.AppendLine($"        public override TN Instantiate<TN>()");
            sourceBuilder.AppendLine("        {");
            sourceBuilder.AppendLine($"            if (TypedNodeInstance != null) throw new InvalidOperationException(\"Node has already been instantiated\");");
            sourceBuilder.AppendLine($"            var instance = (TypedNodeInstance = new {nodeName}());");
            sourceBuilder.AppendLine("            return instance as TN;");
            sourceBuilder.AppendLine("        }");

            // AssociateInstanceInternal Method
            sourceBuilder.AppendLine($"        protected override void AssociateInstanceInternal(INode node)");
            sourceBuilder.AppendLine("        {");
            sourceBuilder.AppendLine($"            if (node is not {nodeName} typedNodeInstance) throw new ArgumentException(\"Node instance is not of type \" + typeof({nodeName}));");
            sourceBuilder.AppendLine("            TypedNodeInstance = typedNodeInstance;");
            sourceBuilder.AppendLine("        }");

            // ClearInstance Method
            sourceBuilder.AppendLine("        public override void ClearInstance() => TypedNodeInstance = null;");

            // GetInputInternal Method
            sourceBuilder.AppendLine($"        protected override ISyncRef GetInputInternal(ref int index)");
            sourceBuilder.AppendLine("        {");
            sourceBuilder.AppendLine("            var inputInternal = base.GetInputInternal(ref index);");
            sourceBuilder.AppendLine("            if (inputInternal != null) return inputInternal;");
            sourceBuilder.AppendLine("            switch (index)");
            sourceBuilder.AppendLine("            {");
            int index = 0;
            foreach (var inputProperty in inputProperties)
            {
                var propertyName = inputProperty.Identifier.Text;
                sourceBuilder.AppendLine($"                case {index}: return {propertyName};");
                index++;
            }
            sourceBuilder.AppendLine("                default:");
            sourceBuilder.AppendLine($"                    index -= {inputProperties.Count()};");
            sourceBuilder.AppendLine("                    return null;");
            sourceBuilder.AppendLine("            }");
            sourceBuilder.AppendLine("        }");

            sourceBuilder.AppendLine("    }");
            sourceBuilder.AppendLine("}");

        }

        var sourceText = SourceText.From(sourceBuilder.ToString(), Encoding.UTF8);
        context.AddSource("GeneratedBindings.cs", sourceText);
    }
}
Xlinka commented 1 year ago

@moonheart08 this might interest you

Xlinka commented 1 year ago

also this doesn't function how id like it to for now so this is just a example precursor ?

Xlinka commented 11 months ago
using System;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.IO;

[Generator]
public class ProtoFluxBindingGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
    }

    public void Execute(GeneratorExecutionContext context)
    {
        var nodes = context.Compilation.SyntaxTrees
            .SelectMany(tree => tree.GetRoot().DescendantNodes())
            .OfType<ClassDeclarationSyntax>()
            .Where(cls => cls.Identifier.Text.EndsWith("Node") && cls.BaseList != null);

        foreach (var node in nodes)
        {
            string nodeName = node.Identifier.Text;
            string bindingName = nodeName + "Binding"; // Use the node name and append "Binding"
            var namespaceName = ((NamespaceDeclarationSyntax)node.Parent).Name;
            var baseType = node.BaseList.Types.First().Type.ToString();

            // Input Properties
            var inputProperties = node.DescendantNodes()
                .OfType<PropertyDeclarationSyntax>()
                .Where(prop => prop.Modifiers.Any(mod => mod.Text == "public"));

            var sourceBuilder = new StringBuilder();

            sourceBuilder.AppendLine("using System;");
            sourceBuilder.AppendLine("using FrooxEngine;");
            sourceBuilder.AppendLine("using FrooxEngine.ProtoFlux;");
            sourceBuilder.AppendLine($"namespace {namespaceName}");
            sourceBuilder.AppendLine("{");
            sourceBuilder.AppendLine($"    public class {bindingName} : FrooxEngine.ProtoFlux.Runtimes.Execution.{baseType}Binding<{nodeName}>");
            sourceBuilder.AppendLine("    {");

            // Generate SyncRef Fields
            foreach (var inputProperty in inputProperties)
            {
                var propertyType = inputProperty.Type.ToString();
                var propertyName = inputProperty.Identifier.Text;
                sourceBuilder.AppendLine($"        public readonly SyncRef<INodeValueOutput<{propertyType}>> {propertyName};");
            }

            // NodeType Property
            sourceBuilder.AppendLine($"        public override Type NodeType => typeof({nodeName});");

            // NodeInstance Property and Fields
            sourceBuilder.AppendLine($"        public {nodeName} TypedNodeInstance {{ get; private set; }}");
            sourceBuilder.AppendLine("        public override INode NodeInstance => TypedNodeInstance;");

            // Instantiate Method
            sourceBuilder.AppendLine($"        public override TN Instantiate<TN>()");
            sourceBuilder.AppendLine("        {");
            sourceBuilder.AppendLine($"            if (TypedNodeInstance != null) throw new InvalidOperationException(\"Node has already been instantiated\");");
            sourceBuilder.AppendLine($"            var instance = (TypedNodeInstance = new {nodeName}());");
            sourceBuilder.AppendLine("            return instance as TN;");
            sourceBuilder.AppendLine("        }");

            // AssociateInstanceInternal Method
            sourceBuilder.AppendLine($"        protected override void AssociateInstanceInternal(INode node)");
            sourceBuilder.AppendLine("        {");
            sourceBuilder.AppendLine($"            if (node is not {nodeName} typedNodeInstance) throw new ArgumentException(\"Node instance is not of type \" + typeof({nodeName}));");
            sourceBuilder.AppendLine("            TypedNodeInstance = typedNodeInstance;");
            sourceBuilder.AppendLine("        }");

            // ClearInstance Method
            sourceBuilder.AppendLine("        public override void ClearInstance() => TypedNodeInstance = null;");

            // GetInputInternal Method
            sourceBuilder.AppendLine($"        protected override ISyncRef GetInputInternal(ref int index)");
            sourceBuilder.AppendLine("        {");
            sourceBuilder.AppendLine("            var inputInternal = base.GetInputInternal(ref index);");
            sourceBuilder.AppendLine("            if (inputInternal != null) return inputInternal;");
            sourceBuilder.AppendLine("            switch (index)");
            sourceBuilder.AppendLine("            {");
            int index = 0;
            foreach (var inputProperty in inputProperties)
            {
                var propertyName = inputProperty.Identifier.Text;
                sourceBuilder.AppendLine($"                case {index}: return {propertyName};");
                index++;
            }
            sourceBuilder.AppendLine("                default:");
            sourceBuilder.AppendLine($"                    index -= {inputProperties.Count()};");
            sourceBuilder.AppendLine("                    return null;");
            sourceBuilder.AppendLine("            }");
            sourceBuilder.AppendLine("        }");

            sourceBuilder.AppendLine("    }");
            sourceBuilder.AppendLine("}");

            string sourceCode = sourceBuilder.ToString();
            string fileName = $"{bindingName}.cs";

            // Write the generated source code to a file
            File.WriteAllText(fileName, sourceCode);

            // Add the generated file to the compilation
            context.AddSource(fileName, SourceText.From(sourceCode, Encoding.UTF8));
        }
    }
}
Xlinka commented 11 months ago

Awaiting Official one for now will write bindings manually by hand