UAssemblyを読んでみる

# はじめに
VRChatでノードを組んでギミックを作ることができるUdonのオープンアルファ版が公開されました。
https://twitter.com/VRDesignGuy/status/1207911393011191811

これで独自の言語が使われており、その仕様がここで公開されています。
https://ask.vrchat.com/t/getting-started-with-udon-assembly/84

チュートリアルで紹介されているノードを追いながら読んでいってみます。
気になるところがあればコメントください。

# Udonとは
プログラミングなしでノードを組んでVRChat上でオブジェクトを操作したりできるようになるプログラミング言語です。
https://ask.vrchat.com/t/getting-started-with-udon/80
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203750/175f9370-5af6-f222-2797-c3853e74d1cf.png)

ノードで組んだものを**UAssembly**という独自のコードにコンパイルして、
バイトコードにアセンブルされてUdonVMで実行されるようです。
AssemblyCodeはUdonBehaviourのCompiledGraphAssemblyを開くことで見ることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203750/1a9c0627-c57f-fec4-a530-107079a5a717.png)

ちなみにこのUAssemblyは実行時（再生ボタンを押したとき）に実行前に更新されるようです。


# UAssemblyを読む

Udonのチュートリアルがこちらで公開されています。
https://ask.vrchat.com/t/spinning-cube-example-series/81

この中の[Video 4 - Program Variables](https://www.youtube.com/watch?v=HSpvrfWICeI)で作られているノードで生成されるUAssemblyの内容を見ていきます。

このチュートリアルでは
２つのCubeにUdonBehaivorとノードが組まれており, 
片方のCube(Signal_Cube)で設定した回転速度(speed)をもう一方のCube(Rotating_Cube)に渡して,
Rotating_Cubeを回転させています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203750/1195da72-019d-e3bf-30aa-05fd4fe82ea5.png)

## Signal_Cube
Signal_Cubeのノードはこれです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203750/0c37256d-36f5-e56a-e9c6-c37067295e28.png)
実行したときにspeedの値を取得してspeedProgramにセットしています。
speedはpublicなので他のノードからも参照できます。

そして, これがそのときのUAssemblyです。

```
.data_start

    .export speed

    instance_0: %VRCUdonUdonBehaviour, this
    name_0: %SystemString, null
    value_0: %SystemObject, null
    speed: %SystemSingle, null

.data_end

.code_start

    .export _start

    _start:

        PUSH, speed
        PUSH, value_0
        COPY
        PUSH, instance_0
        PUSH, name_0
        PUSH, value_0
        EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__SetProgramVariable__SystemString_SystemObject__SystemVoid"
        JUMP, 0xFFFFFF
    

.code_end
```

データブロックとコードブロックに分かれています。

### データブロック
.data_startから.data_endまでがデータブロックで変数などを定義しています。

```
.data_start

    .export speed

    instance_0: %VRCUdonUdonBehaviour, this
    name_0: %SystemString, null
    value_0: %SystemObject, null
    speed: %SystemSingle, null

.data_end
```

それぞれの変数は
**型名: 型の種類, 初期値**
という書き方がされているようです。

例えば, instance_0はUdonBehaivorをいれる型で自分自身をいれています。
また, speedはSingle型で初期値はなしのようです。

また, nameをspeedとしてpublicにチェックを入れたFloatノードを置いたので
`.export speed`
が書かれています。
これによってpublic扱いになり, 他のコードからもこの値を参照できるようになります。

### コードブロック
.code_startから.code_endまでがコードブロックで実際の動く部分を書いています。

```
.code_start

    .export _start

    _start:

        PUSH, speed
        PUSH, value_0
        COPY
        PUSH, instance_0
        PUSH, name_0
        PUSH, value_0
        EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__SetProgramVariable__SystemString_SystemObject__SystemVoid"
        JUMP, 0xFFFFFF
    

.code_end
```
主な処理は事前に用意した関数に実行されています。

Unityでは実行タイミングを決めることができ, 
例えばStartは開始時に1度だけ, Updateは毎フレームごとになどいろいろあります。

Startノードを置いたので`_start:`が追加されています。
UAssemblyはインデント（字下げ）でブロックを決めているようで
_start:からインデントが下がっている範囲が開始時に1度だけ実行されます。

アセンブリでは他の関数で実行する際に必要な値をpushして関数を読んでいます。
UAssemblyでも同じようにやっています。

`COPY`では特定の変数の値を特定の変数にコピーしています。
ここでは事前に
`PUSH, speed`
`PUSH, value_0`
をすることで
speedの参照値をvalue_0にコピーしているようです。

関数を呼び出す部分が
`EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__SetProgramVariable__SystemString_SystemObject__SystemVoid"`
です。

この関数は**VRCUdonCommonInterfacesIUdonEventReceiver.SetProgramVariable**という名前で
SystemStringとSystemObjectのデータを受け取ってSystemVoidを返す（つまり何も返さない）ようです。

よって実行前に必要なデータを
`PUSH, name_0`
`PUSH, value_0`
で渡しているようです。

そして, すべてが終了したので`JUMP, 0xFFFFFF`で終わります。
これはメモリの最後の部分まで飛んで終了したことを示しています。

## Rotating_Cube
実際に回転するほうのノードとUAssemblyです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/203750/175f9370-5af6-f222-2797-c3853e74d1cf.png)

```
.data_start

    .export signal
    
    instance_0: %UnityEngineTransform, this
    axis_0: %UnityEngineVector3, null
    angle_0: %SystemSingle, null
    Single_0: %SystemSingle, null
    Single_1: %SystemSingle, null
    instance_1: %VRCUdonUdonBehaviour, this
    name_0: %SystemString, null
    signal: %VRCUdonUdonBehaviour, this

.data_end

.code_start

    .export _update
    
    _update:
    
        PUSH, Single_0
        EXTERN, "UnityEngineTime.__get_deltaTime__SystemSingle"
        PUSH, signal
        PUSH, instance_1
        COPY
        PUSH, instance_1
        PUSH, name_0
        PUSH, Single_1
        EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__GetProgramVariable__SystemString__SystemObject"
        PUSH, Single_0
        PUSH, Single_1
        PUSH, angle_0
        EXTERN, "SystemSingle.__op_Multiplication__SystemSingle_SystemSingle__SystemSingle"
        PUSH, instance_0
        PUSH, axis_0
        PUSH, angle_0
        EXTERN, "UnityEngineTransform.__Rotate__UnityEngineVector3_SystemSingle__SystemVoid"
        JUMP, 0xFFFFFF
    

.code_end
```

書き方は同じなのでSignal_Cubeの解説部分を見ればだいたい何をしているか分かるかと思います。
今回はUpdateノードを置いたので`_update:`が書かれています。
また、先ほどの`VRCUdonCommonInterfacesIUdonEventReceiver.SetProgramVariable`では返り値が**Void**(なし)でしたが

`EXTERN, "VRCUdonCommonInterfacesIUdonEventReceiver.__GetProgramVariable__SystemString__SystemObject"`
では返り値が**SystemObject**なので
それを受け取るために事前に引数用の`PUSH, name_0`に加えて
返り値を受け取るための`PUSH, Single_1`もしています。
これで返り値はSingle_1に代入されるので次の
`EXTERN, "UnityEngineTransform.__Rotate__UnityEngineVector3_SystemSingle__SystemVoid"`
に渡しています。

# 最後に
拡張した言語もつくれるようになっているようです。
興味がある方は同封されている
UdonAssemblyProgramAsset.csをUdonGraphProgramAsset.csを
見てみると良さそうです。