Udon VM と Udon Assembly
このページは VRChat コミュニティのメンバーによって執筆されました。貢献に感謝します! VRChat チームは、このページの情報が正確であることを保証するものではありません。変更を提案したい場合は、ページ下部にある「Edit this page」をクリックしてください。
Udon VMの概要
Udon VMは、コンパイルされたUdon Graphプログラムを実行するために設計されたバイトコードインタープリタです。
そのため、以下の仕様について留意しておくことが重要です。
- Udon VMは.NET環境内で実行されることを想定しています。関数へのアクセスにリフレクションは使用されませんが、Udon VMは「そのような形式」で構成されています。
- 直接的なcall/returnやサブルーチンは実装されていません(ただし、
JUMP_INDIRECTが存在し、その目的で使用できます)。 JUMP、JUMP_INDIRECT、およびJUMP_IF_FALSEを介したフロー制御が可能です。- (許可されている場合)C#の関数を呼び出すことができます。
- ローカル変数は存在せず、オブジェクト上のフィールドのみが使用可能です。
- 整数スタックがありますが、この整数スタックはほとんどの場合、オペコードの「追加パラメータ」として扱うべきです。call/return機構の一部として使用することも可能ですが、ローカル変数が存在しないため、再帰関数を実装する際は細心の注意が必要です。
Udon GraphプログラムやUdonSharpプログラムからUdon Assemblyをエクスポートできます。
これは、自分が書いたコードがどのようにコンパイルされるかを把握し、extern名を特定するのに役立ちます!
Udonの型
「Udonの型 (Udon Types)」とは、UdonにおけるC#の型の呼び方のことです。
これらの型名を理解する最も簡単な方法は、System.Int32[] のような対応する.NETの型名から始めることです。
Udonの型を構成するには、いくつかのルールがあります。
- すべての
.および+は削除されます。つまり、VRC.SDKBase.VRCPlayerApi+TrackingDataはVRCSDKBaseVRCPlayerApiTrackingDataとなります。 - 型には
Arrayが付加される場合があり、これは[]を表します。
そのため、上記の例は SystemInt32Array となります。
Udon Assembly
Udon Assemblyプログラムは、データセクションとコードセクションの2つのセクションで構成されています。 これらのセクションは、以下のように開始/終了ディレクティブでマークされます。
.data_start
# Data goes here!
.data_end
.code_start
# Code goes here, instead!
.code_end
データセクション
データセクションは、そのビヘイビアの変数を定義し、どれがエクスポート(public)されるかを指定します。 これらのセクション内のデータは「Udon Heap」に保存されます。名前とは裏腹に、実際には型情報を持つ値のフラットな配列です。「ヒープインデックス」とは、この配列内のインデックスのことです。 Udon Assemblyで変数を定義する例を以下に示します。
message: %SystemString, "Hello, world!"
ここで、変数のシンボルは message、型は SystemString、内容は "Hello, world!" です。
なお、変数は実行時に型を変更できる(ただしpublicな変数の場合は避けるべきです)ため、この型は_初期_型と表現する方が正確かもしれません。
値には null、this、true、false、文字列、文字定数、整数、符号なし整数(uで終わる整数)、および浮動小数点数が使用できます。ただし、アセンブラはこれらを指定可能なタイミングについて厳格です。
特に以下の点に注意してください。
SystemSingleおよびSystemDoubleは、数値またはnullである必要があります。SystemInt32およびSystemUInt32は、どちらかの種類の整数またはnullである必要があります。SystemStringは、文字列リテラルまたはnullである必要があります。- その他のすべての型(
SystemObjectを含む)は、thisまたはnullのいずれかである必要があります。
this は、いわゆる「伝統的な」意味では機能しません。変数の型に応じて、以下のように扱われます。
GameObject:UdonBehaviourのGameObject。Transform:GameObject.transform。UdonBehaviour、IUdonBehaviour、またはObject:UdonBehaviourそのもの。
これらに該当しない場合はエラーが発生します。
現時点では、Udon Assemblyで SystemType に非null値を指定することはできませんが、Udon GraphやUdonSharpでは可能です。同様の問題は、SystemInt64、SystemUInt64、SystemSByte、SystemByte、SystemInt16、SystemUInt16、および SystemBoolean にも存在します(実際、true や false を正しく指定することは不可能です)。
これらはUdon Assemblyの制限であり、Udon Assemblyを使用しないこと以外に回避策はありません。
浮動小数点数は、意図する型がdoubleであっても、常にfloatとして読み取られます。
これらの変数は、.export message などを使用してpublicとしてマークしたり、.sync message, none などを使用して同期メタデータを付与したりできます。
変数を同期メタデータでマークすることは、synced チェックボックスをオンにすることと同等です。詳細は Networking を参照してください。
ここでの none は補間モードです。補間モードには none、linear、smooth がありますが、すべての型ですべての補間モードが有効なわけではありません。
コードセクション
コードセクションは、ラベルとエクスポート可能な項目を持つオペコードのリストです。
.export _start
_start:
PUSH, message
EXTERN, "UnityEngineDebug.__Log__SystemObject__SystemVoid"
JUMP, 0xFFFFFFFC
.export _start(例です。_start をエクスポートするシンボルに置き換えてください)は、イベントハンドラ用のコードシンボルをエクスポートするために使用されます。
標準イベントは _ で始まり、そのパラメータはpublic変数ではなく、自分で作成する必要がある変数に渡されます。これは非常に長いリストであるため、Udon Graphを通じて確認することをお勧めします。
ただし、重要な点として、最初に実行される2つのイベントは _onEnable と _start であり、この順序で実行されます。この初期実行において両者の間に間隔はなく、他のどのイベントよりも先に必ず実行されます。もしこれを回避しようとする処理があった場合、その呼び出しは無視されます。詳細は Event Execution Order を参照してください。
カスタムイベントは(自身で定義した仕組みを除き)パラメータを受け取ることはなく、_ で始まることもありません。
実際のオペコードは非常に単純です。オペコード名が存在し、一部のオペコードにはパラメータが続きます。このパラメータには整数、シンボル(シンボルの整数値、つまりヒープインデックスやコードアドレス)、または文字列を指定できます。文字列が指定された場合、アセンブラはその文字列に対して隠し変数(名前なし)を作成し、実際の値はヒープインデックスとなります。
何らかの理由により、同一位置を指す2つのコードシンボルを定義することは許可されておらず、Address aliasing detected エラーが発生します。
Udon Opcodes
NOP
- Opcode: 0
- Parameters: 0
このオペコードは何もしません。Address aliasing detected: エラーが発生する場合を除き、これを使用する理由は通常ありません。
PUSH, parameter
- Opcode: 1
- Parameters: 1
このオペコードは、整数をスタックの最上位にプッシュします。
Udon Assemblyでは値がプッシュされているような印象を受けるかもしれませんが、実際にはそうではありません。
これらのケースでプッシュされているのはヒープアドレスです。
Udonプログラムのサイズ最適化に極めて注力する場合(場合によっては実行速度を犠牲にしてでも)、あるいは難読化を試みる場合を除き、条件付きでこれを使用する理由は一切ありません。EXTERN、COPY、または JUMP_IF_FALSE の直前にすべてをプッシュしてください。
POP
- Opcode: 2
- Parameters: 0
このオペコードは、スタックの最上位にある整数を取り除きますが、それ以外の影響はありません。
JUMP_IF_FALSE, parameter
- Opcode: 4
- Parameters: 1
スタックからヒープインデックスをポップし、そこから SystemBoolean を読み取ります。
この値が false である場合、パラメータをバイトコードの実行位置としてジャンプします。それ以外の場合は、次の命令へ進みます。
JUMP, parameter
- Opcode: 5
- Parameters: 1
パラメータで指定されたバイトコード実行位置へジャンプします。
JUMP, 0xFFFFFFFC は、実行を終了(すなわちUdonコードからの戻り)させるためにも使用されます。
EXTERN, parameter
- Opcode: 6
- Parameters: 1
このオペコードは、Udonが何らかの有用な操作を実行するための仕組みです。
まず注意すべき点は、パラメータがヒープインデックスであることです。これには最初、extern名(文字列として)が含まれていますが、ここには書き込みも行われます。
最適化として、Udonは特定のヒープインデックスでexternが最初に実行された後、その情報をキャッシュします。これらの値は依然としてヒープ値であり、コピーすることが可能です。
externへのパラメータは PUSH の順序で指定されます。つまり、最初にプッシュされた値が第1引数となります。
これらのヒープ値は、通常の(すなわち in)引数の場合は読み取られ、ref 引数の場合は読み取りと書き込みが行われ、out 引数の場合は書き込みが行われます。
externがstaticでない場合(すなわち this 引数を持つ場合)、this 引数が先頭に追加されます。戻り値がある場合(すなわち戻り値の型が SystemVoid でない場合)、末尾の out 引数のように扱われます。
ANNOTATION, parameter
- Opcode: 7
- Parameters: 1
これは実質的に「長いNOP(何もしない処理)」です。パラメータは無視されます。
JUMP_INDIRECT, parameter
- Opcode: 8
- Parameters: 1
パラメータからヒープインデックスを取得し、そこから SystemUInt32 を読み取ります。
これをバイトコードの実行位置として解釈し、そこにジャンプします。
COPY
- Opcode: 9
- Parameters: 0
2つのヒープインデックスをポップします。2番目にポップされたヒープインデックス(最初にプッシュされたヒープインデックス)からの値が、最初にポップされたヒープインデックス(2番目にプッシュされたヒープインデックス)にコピーされます。
Externs リファレンス
既知の extern に依存する場合を除き、extern シグネチャの正確な形式に依存することは推奨されません。
シグネチャの形式は極めて特殊な場合があり、ジェネリクスの詳細もさることながら、シグネチャから「これは静的メソッドか」といった属性を推測することは不可能です。
API の全容に依存する機能を作成しようとしている場合は、C# コードを記述して Udon Graph ノードのリストをスクレイピングする必要が出てくるでしょう。
Extern は SomeUdonTypeName.SomeSignature という形式をとります(Udon 型名が「偽装」されているケースが1つあり、それは VRCInstantiate です)。
例として SystemDateTimeOffset.__TryParseExact__SystemString_SystemStringArray_SystemIFormatProvider_SystemGlobalizationDateTimeStyles_SystemDateTimeOffsetRef__SystemBoolean を挙げます。
これは System.DateTimeOffset.TryParseExact(string, string[], System.IFormatProvider, System.Globalization.DateTimeStyles, out System.DateTimeOffset) です。これは静的メソッドです。なお、静的メソッドでない場合でも、this パラメータはシグネチャ内に明示されません。
シグネチャ自体は常に __ で始まり、その後にメソッド名、続いて __ が続きます。コンストラクターの場合、メソッド名は ctor となり、ここでは TryParseExact となっています。
その後、this 以外の各パラメータが、_ で区切られた Udon 型名として続きます。ref および out パラメータについては、Udon 型名に対して Ref サフィックスが付加されるという特別な修飾子があります。
最後に、シグネチャは __ とそれに続く戻り値の Udon 型名で終わります。
特に特殊なケースがいくつかあります:
- ジェネリクスは、シグネチャ内で
Tのような「Udon 型」として型パラメータをリストし、不可視のSystemTypeパラメータを持ちます。 VRCUdonUdonBehaviourはVRCUdonCommonInterfacesIUdonEventReceiverになります(関連する場合はArrayなどが末尾に付加されます)。
現在、extern の完全なリファレンスは存在しません。
ただし、以下のリソースが利用可能です:
- UdonSharp ドキュメントに VRChat メソッドの API リファレンスがあります。
- UdonSharp では、Class Exposure Tree を使用して利用可能な機能を確認できます。
現在、UdonSharp Class Exposure Tree 内でメンバー名をコピーする機能は正常に動作していません。利用可能な機能の概要を確認するには適していますが、実際の extern 名を取得するには Udon Graph を使用する必要があります。
最終更新: