Udon完全に理解したけど、C#からUdonGraphにするトランスパイラを誰か作ってくれ
Udon完全に理解したので超雑にメモしておく。
つまりこれがこう
Udon完全に理解した。
— 奈良阪まぞく@VRChat Profile (@narazaka) 2019年12月20日
つまるところこれがこうですね?
三角が制御で丸が値#VRChat #MadeWithUdon pic.twitter.com/uHnYFHwnYQ
基本的に
- 制御が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アプローチを待つか。
asmなアプローチ↓