VRChat 非公式日本語ドキュメント

Udon VMとUdon Assembly

info

このページはVRChatコミュニティのメンバーによって作成されました。貢献に感謝します! VRChatチームは、このページの情報が正確であることを保証できません。変更を提案したい場合は、このページの下部にある「Edit this page」をクリックしてください。

Udon VMの概要

Udon VMは、コンパイルされたUdon Graphプログラムを実行するために設計されたバイトコードインタープリタです。

そのため、以下の特徴を理解しておくことが重要です。

  • Udon VMは.NET環境内で実行されることが想定されています。関数へのアクセスにリフレクションは使用されませんが、Udon VMはリフレクションを使用するかのような「スタイル」になっています。
  • call/returnやサブルーチンは直接実装されていません(ただし、JUMP_INDIRECTが存在し、その目的で使用することは可能です)。
  • JUMPJUMP_INDIRECT、およびJUMP_IF_FALSEを介したフロー制御が可能です。
  • (許可されている場合に)C#から関数を呼び出すことができます。
  • ローカル変数は存在せず、オブジェクト上のフィールドのみが使用可能です。
  • 整数スタックが存在しますが、ほとんどの場合、この整数スタックはオペコードの「追加パラメータ」として扱うべきです。呼び出し/復帰メカニズムの一部として使用することは可能ですが、ローカル変数は存在しないため、再帰関数を実装する際には細心の注意が必要です。
ヒント

Udon GraphやUdonSharpのプログラムからUdon Assemblyをエクスポートできます。

これは、記述したコードがどのようにコンパイルされるかを把握するのに役立ち、extern名を調べる際にも役立ちます!

Udon型

「Udon型」とは、UdonにおけるC#型の呼び方です。

これらの型名を理解する最も簡単な方法は、System.Int32[]のような対応する.NET名から始めることです。

Udon型の構築にはいくつかのルールがあります:

  • すべての .+ が削除されます。つまり、VRC.SDKBase.VRCPlayerApi+TrackingDataVRCSDKBaseVRCPlayerApiTrackingData となります。
  • 型には Array が付加される場合があり、これは [] を表します。

そのため、上記の例は SystemInt32Array となります。

Udon Assembly

Udon Assemblyプログラムは、データセクションとコードセクションの2つのセクションで構成されています。

これらのセクションは、次のように開始/終了ディレクティブでマークされます:

.data_start
# Data goes here!
.data_end
.code_start
# Code goes here, instead!
.code_end

データセクション

dataセクションは、そのUdonBehaviourの変数と、それらがエクスポート(public)されているかどうかを定義します。

これらのセクションのデータは「Udon Heap」に格納されます。名前とは裏腹に、実際には型情報を持つ値のフラットな配列です。「heap index」とは、この配列におけるインデックスのことです。

Udon Assemblyで変数を定義する例を以下に示します。

message: %SystemString, "Hello, world!"

ここで、変数のシンボルは message、型は SystemString、内容は "Hello, world!" です。

変数は実行時に型が変更される可能性があるため(publicの場合は避けるべきですが)、この型はあくまで初期型と考えるのが適切であることに注意してください。

値には nullthistruefalse、文字列、文字定数、整数、符号なし整数(uで終わる整数)、浮動小数点数を指定できます。ただし、アセンブラはこれらをいつ指定できるかについて厳格です。

特に以下の点に注意してください。

  • SystemSingle および SystemDouble は、数値または null である必要があります。
  • SystemInt32 および SystemUInt32 は、いずれかの種類の整数または null である必要があります。
  • SystemString は、文字列リテラルまたは null である必要があります。
  • SystemObject を含むその他すべての型は、this または null しか指定できません。

this は、本来の「伝統的な」意味を持ちません。変数の型に応じて、以下のように扱われます。

  • GameObject: その UdonBehaviour が属する GameObject です。
  • Transform: GameObject.transform です。
  • UdonBehaviourIUdonBehaviour、または Object: その UdonBehaviour 自体です。

これら以外の型である場合、エラーが発生します。

caution

現在、Udon Assemblyで SystemType に null 以外の値を指定することはできませんが、Udon Graph や UdonSharp では可能です。同様の問題が SystemInt64SystemUInt64SystemSByteSystemByteSystemInt16SystemUInt16SystemBoolean にも存在します(そう、実際には SystemBooleantruefalse を指定することも不可能です)。

これらは Udon Assembly の制限です。これらを回避する唯一の方法は、Udon Assembly を使用しないことです。

浮動小数点数は、意図する型が double であったとしても、常に float として読み取られます。

これらの変数は、例えば .export message を使用してpublicとしてマークしたり、例えば .sync message, none のように同期メタデータを付与したりできます。

変数に同期メタデータを付与することは、 synced チェックボックスをオンにすることと同等です。詳細については、ネットワーキングを参照してください。

none は補間モードです。補間モードには nonelinearsmooth がありますが、すべての型ですべての補間モードが有効というわけではありません。

コードセクション

コードセクションは、ラベルとエクスポートの可能性があるオペコードのリストです。

.export _start
_start:
PUSH, message
EXTERN, "UnityEngineDebug.__Log__SystemObject__SystemVoid"
JUMP, 0xFFFFFFFC

.export _start (例として挙げています。_start はエクスポートするシンボルに適宜置き換えてください)は、イベントハンドラ用にコードシンボルをエクスポートするために使用します。

標準イベントは _ で始まり、そのパラメータはパブリック変数ではなく、自身で作成する必要がある変数に渡されます。これらはリストが長いため、Udon Graphを通じて確認するのが最適です。

しかし重要な点として、最初に実行される2つのイベントは _onEnable_start であり、この順序で実行されます。この初期実行において両者の間に間隔はなく、これらは他のどのイベントよりも先に必ず実行されます。何らかの手段でこれを回避しようとしても、その呼び出しは無視されます。詳細は イベントの実行順序 を参照してください。

カスタムイベントはパラメータを取りません(あなたが定義した仕組みは除く)。また、_ で始まることもありません。

実際のオペコードは非常に単純です。オペコード名があり、一部のオペコードにはパラメータが存在します。このパラメータは整数、シンボル(シンボルの整数値、つまりヒープインデックスやコードアドレス)、または文字列になります。文字列の場合、アセンブラはその文字列に対して非表示の匿名変数を生成し、実際の値はヒープインデックスとなります。

注意

何らかの理由で、同じ位置を指す2つのコードシンボルを定義することは許可されておらず、Address aliasing detected エラーが発生します。

Udon オペコード

NOP

  • オペコード: 0
  • パラメータ: 0

このオペコードは何もしません。Address aliasing detected: エラーが発生する場合を除き、これを使用する理由は通常ありません。

PUSH, parameter

  • オペコード: 1
  • パラメータ: 1

このオペコードは、整数をスタックの最上部にプッシュします。

Udon Assemblyでは値がプッシュされているような印象を受けるかもしれませんが、実際はそうではありません。

これらの場合、プッシュされているのはヒープアドレスです。

Udonプログラムのサイズを徹底的に最適化しようとする場合(実行速度を犠牲にする場合であっても)、あるいは難読化を試みる場合を除き、条件付きでこれを使用する理由はありません。すべての値を EXTERNCOPY、または JUMP_IF_FALSE の直前にプッシュするだけで十分です。

POP

  • オペコード: 2
  • パラメータ: 0

このオペコードは、スタックの最上部から整数を削除します。それ以外の影響はありません。

JUMP_IF_FALSE, parameter

  • オペコード: 4
  • パラメータ: 1

スタックからヒープインデックスをポップし、そこから SystemBoolean を読み取ります。

この値が false の場合、パラメータで指定されたバイトコード位置へジャンプします。それ以外の場合は、次の命令に進みます。

JUMP, parameter

  • オペコード: 5
  • パラメータ: 1

パラメータで指定されたバイトコード位置へジャンプします。

JUMP, 0xFFFFFFFC は実行を終了(すなわち、Udonコードから戻る)するためにも使用されます。

EXTERN, parameter

  • オペコード: 6
  • パラメータ: 1

このオペコードは、Udonが何らかの有用な操作を実行するための仕組みです。

まず注意すべき点は、パラメータはヒープインデックスであり、最初は(文字列として)extern名が含まれていますが、ここには書き込みも行われます。

最適化として、Udonは特定のヒープインデックスでexternが最初に実行された後、そのexternに関する情報をキャッシュします。これらの値は依然としてヒープ値であり、コピーが可能です。

externへのパラメータは PUSH 順に指定されます。つまり、最初にプッシュされた値が第1引数になります。

これらのヒープ値は、通常の(つまり in)引数の場合は読み取られ、 ref 引数の場合は読み取りと書き込みが行われ、 out 引数の場合は書き込みが行われます。

externが静的でない(つまり this 引数を持つ)場合、 this 引数が先頭に追加されます。戻り値がある(つまり戻り値の型が SystemVoid ではない)場合、それは末尾の out 引数のように扱われます。

ANNOTATION, parameter

  • オペコード: 7
  • パラメータ: 1

これは実質的に「長いNOP」です。パラメータは無視されます。

JUMP_INDIRECT, parameter

  • オペコード: 8
  • パラメータ: 1

パラメータからヒープインデックスを取得し、そこから SystemUInt32 を読み取ります。

これをバイトコードの位置として解釈し、そこにジャンプします。

COPY

  • オペコード: 9
  • パラメータ: 0

ヒープインデックスを2つポップします。2番目にポップされた(=最初にプッシュされた)ヒープインデックスの値が、最初にポップされた(=2番目にプッシュされた)ヒープインデックスにコピーされます。

Externリファレンス

caution

特定の既存の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 パラメータを持ちます。
  • VRCUdonUdonBehaviourVRCUdonCommonInterfacesIUdonEventReceiver となります。(関連する場合は Array などが末尾に付加されます)

現時点では、externの完全なリファレンスは存在しません。

ただし、以下の通りです。

注意

現在、UdonSharpのClass Exposure Treeでメンバー名をコピーする機能は動作しません。何が利用可能かを素早く確認するには便利な方法ですが、最終的にはUdon Graphを使用して実際にextern名を取得する必要があります。

最終更新: