Narazaka::Blog

奈良阪という人のなにか

Udon完全に理解したけど、C#からUdonGraphにするトランスパイラを誰か作ってくれ

Udon完全に理解したので超雑にメモしておく。

つまりこれがこう

f:id:narazaka:20191221180447p:plain

f:id:narazaka:20191221180426p:plain

基本的に

  • 制御がif, for, while, breakのみ
  • ローカル変数はなく全ての変数はフィールド
  • イベント=メソッド
  • SendCustomEvent=メソッドコール
  • メソッドは引数無し返値voidのみ
  • SubGraph=コード片(他言語のMixinみたいな感じ)
  • ジェネリクス無し(System.Collection.Genericが使えなさそう)

C#プログラミングだと思えばヨサソウ。

プログラマ勢としては上記の条件でC#をざらっとかいて、それをUdonノードに起こすのが今のところ楽そうな感じ。

ただここ変換があると嬉しい気はする。

ILでやるという手もあるのだろうけど、構文解析の概要 (Roslyn API) | Microsoft Docs のSyntaxWalkerを使って、

  • 制御はif, while, breakのみ(forは若干めんどそうなので)
  • 変数はフィールドのみ
  • メソッドは引数無し返値voidのみ

C#ソースをUdonGraphにトランスパイルすると楽そうと感じた。

途中まで(まだ何も始まってないレベルだが……)やったのでメモすると、

まず GitHub - mwahnish/Unity-Roslyn: The Microsoft C# binaries compiled for Unity をインポートしてMicrosoft.CodeAnalysisを使えるようにする。

その上で

using System.Collections.Generic;
using UnityEngine;

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

public class UdonCsharp : CSharpSyntaxWalker
{
    public class Error : Exception {
        public Error(string message) : base(message) { }
    }

    string Code { get; }
    Microsoft.CodeAnalysis.SyntaxTree SyntaxTree;

    public UdonCsharp(string code) {
        Code = code;
    }

    // Start is called before the first frame update
    public void Compile()
    {
        GetTree();
        Visit(SyntaxTree.GetCompilationUnitRoot());
    }

    void GetTree() {
        // パース
        SyntaxTree = CSharpSyntaxTree.ParseText(Code);
        var diag = SyntaxTree.GetDiagnostics();
        PrintDiagnostics(diag);
        if (diag.Any(item => item.Severity == DiagnosticSeverity.Error)) throw new Error("parse error");

        // アセンブリ参照 雑に全部突っ込む
        var assemblies =
            AppDomain.CurrentDomain.GetAssemblies().
            Where(assembly => !assembly.IsDynamic && !string.IsNullOrEmpty(assembly.Location));
        var assemblyLocations = assemblies.Select(assembly => assembly.Location);
        // コンパイル
        var comp = CSharpCompilation.Create(
            "asmname",
            syntaxTrees: new Microsoft.CodeAnalysis.SyntaxTree[] { SyntaxTree },
            references: assemblyLocations.Select(location => MetadataReference.CreateFromFile(location)),
            options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            );
        var diag2 = comp.GetDiagnostics();
        PrintDiagnostics(diag2);
        if (diag2.Any(item => item.Severity == DiagnosticSeverity.Error)) throw new Error("compile error");
    }

    void PrintDiagnostics(IEnumerable<Diagnostic> diagnostics) {
        foreach (var item in diagnostics) {
            var span = item.Location.SourceSpan;
            var sub = Code.Substring(span.Start, span.Length);
            var message = $"[{item.Severity.ToString()}:{item.Id}] {span} {item.GetMessage()}\n{sub}";
            switch (item.Severity) {
                case DiagnosticSeverity.Error:
                    Debug.LogError(message);
                    break;
                case DiagnosticSeverity.Warning:
                    Debug.LogWarning(message);
                    break;
                default:
                    Debug.Log(message);
                    break;
            }
        }
    }

    public override void VisitFieldDeclaration(FieldDeclarationSyntax node) => VisitNode(node);
    public override void VisitMethodDeclaration(MethodDeclarationSyntax node) => VisitNode(node);

    public void VisitNode(SyntaxNode syntax, int level = 0) {
        var kind = syntax.Kind();
        var str = syntax.ToFullString();
        Debug.Log($"{new string(' ', level)}{kind.ToString()}> {str}");
        // このへんでトークンを追ってUdonNodeに変換したい
        foreach (var node in syntax.ChildNodesAndTokens()) {
            if (node.IsNode) VisitNode(node.AsNode(), level + 1);
            if (node.IsToken) VisitToken(node.AsToken(), level + 1);
        }
    }

    public void VisitToken(SyntaxToken syntax, int level = 0) {
        var kind = syntax.Kind();
        var str = syntax.ToFullString();
        Debug.Log($"{new string(' ', level)}{kind.ToString()}> {str}");
    }
}

という感じでフィールド定義とメソッド定義を追って、出会った型はUdonNodeSearchMenuの型検索を駆使してUdonNodeDefinitionを取得し、良い感じにUdonGraphに積んでいく物を誰か作ってほしい(他力本願)。

追記

もうちょっと進めてみたけどノード定義はどうやらコード生成だし、グラフデータの値もGUIに強依存している感じがあり微妙だった……なんか良い方法はないか、それともasmアプローチを待つか。

github.com

asmなアプローチ↓

togetter.com