[Resonite] LogiXからProtoFluxへ

# LogiXからProtoFluxへ
ご存知の通り、NeosVRの旧開発チームはYellow Dog Man Studiosとして独立し、新たなメタバースプラットフォーム『Resonite』を立ち上げました。ResoniteはNeosVRからの移行をサポートし、これによって殆どの資産がResoniteでも再利用できるようになっています。

一方でNeosVRのビジュアルプログラミングシステム **LogiX** は **ProtoFlu(x)** として刷新され、LogiXにはなかった新たな概念の導入も行われました。

本記事では、初めてProtoFluxを触る方に向けて、またLogiXについての予備知識がある人に向けてもその対比をお伝えしようと思います。(記事後半はある程度ProtoFluxまたはLogiXまたはプログラミングがわかる方向けの内容になっています。)

# 基本操作
まずはこちらを参照されたし。

https://qiita.com/e1ght3/items/6b3e1ba6a764607d9ae7#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB

## 値をコピーする
NeosVRには公式的に存在しなかった機能として、Displayノードから値がコピーできるようになっています。
![2023-12-09 05.10.17.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/aea22d00-e9fa-bec2-09e8-97105ab38290.jpeg)
画像のようにDisplayノードの値が記載されている部分をグラブすることで、ノードではなく値をグラブすることが出来るようになりました。(最近のアップデートです)

## ノードを複数選択する
これはNeosVRには存在しなかった新しい概念です。Resoniteでは接続されたノードをまとめて動かしたりすることができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/cceecd1d-7800-ab84-1f8b-8429ced9990f.png)
接続されている何れかのノードに対してレーザーをあて、そのまま1秒間セカンダリの動作を保ちます。これによってノードが青くなり、選択状態になります。この状態ではノードを纏めて動かす・回転させる・縮小させる・複製するといった操作が可能になります。

この状態を解除するには、再度選択時の操作を行います。

## オーバービュー
オーバービューを有効にすると、ノードの見た目がLogiXぽくなります。ノード名のみが簡潔に表示されるようになり、各パラメータの文字が隠されます。

オーバービュー無効
![2023-12-09 06.28.59.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/7d90d039-3d83-57d0-f493-76a0c5ac1f76.jpeg)

オーバービュー有効
![2023-12-09 06.28.54.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/e1f73108-fb2e-99fb-ebfd-aea147b6558a.jpeg)

# データの取り扱い
## 値を書き込む
特定のフィールドに値を書き込む方法はいくつかあります。
![2023-12-09 05.28.48.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/d62ddf86-67a3-3917-a794-87f1afff51e3.jpeg)
まず、FluxToolを装備した状態で、書き込みたいフィールドを掴んだ状態でコンテキストメニューを開きます。
Drive、Source、Referenceの3つの項目が追加されています。NeosVRと違い、ResoniteではRefCardではなく特定のフィールドに対してのみの値や参照を取り出すことになり、またその目的に応じて取り出すものも変わってきます。

|名称|説明|
|--|--|
|Drive|値を常に書き込み続けるノード「Driver」を取り出す。Driverが存在していると、Driver以外から値を書き込むことはできなくなる。|
|Source|値を読み出すノードを取り出す。|
|Reference|値の参照を読み出すノードを取り出す。|

Driverは少し意味が違ってくるのでここでは取り扱わず、SourceとReferenceを使用した際の値の書き込みについて触れます。
![2023-12-09 05.27.48.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/5195639e-6825-fe83-275f-6e8e2c092254.jpeg)

上はWriteノードを使用しているのに対して、下はIndirect Writeというノードを使用しています。

### Indirect Write
NeosVRから移行したコンテンツに含まれるLogiXにWriteノードが含まれていた場合、そのWriteノードはIndirect Writeノードに置き換わっています。フィールドの参照を「変数」として取り扱う必要があるようで、Field As Variableというノードが要求されます。ノード数が増える他、LogiXの移行で目にする機会が多いため、筆者はこれをレガシーな方法であると解釈しており、基本的に利用していません。

### Write
Indirect Writeに変わって、このWriteノードはフィールドや変数に対するあらゆる書き込みを行えます。変数のワイヤーをVariableの文字の上で離すことで書き込み先を指定します。(`Value`ではありません。Valueは書き込む値です。)

#### 変数の作成方法
ここでResoniteにおける変数の作成について触れておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/190ec768-76d2-9c14-1452-e3c5e58ce6f9.png)
WriteノードのVariableの文字を掴んだ状態でコンテキストメニューを開きます。Create Local、Create Store、Create Data Model Storeの3つが追加されています。Resoniteには他にデータを格納する手段としてGlobal、Dynamic Value Variable、Cloud Variableというものも存在します。

表中の「同期」とは、ネットワークを経由して他のユーザにも値が伝播されるか、ということを指しています。
|名称|同期|説明|
|--|--|--|
|Local|×|実行されている瞬間のみ値が保持される変数。|
|Store|×|値を保持し続けられる変数。|
|Data Model Store|◯|値を保持し続けられる変数。筆者の見解だが、あまり使う意味はない。この変数が求められる状況では、DynamicValueVariableのほうが適している。複数人でデバッグしたい時用か。|
|Global|多分◯|筆者もよく分かっていない。使われているのをいまだかつて見たことがない。変数名が恐らく付けられない。|
|Dynamic Value Variable[^*]|◯|コンポーネントで定義する。ノードを直接繋がなくとも名前によって値を読み出すことができ、コンポーネントからも値を読み出してドライブすることが出来る(->`DynamicValueVariableDriver`)。|
|Cloud Variable[^*]|△|ResoniteのBotにDMでコマンドを送信して定義する。ユーザごとに値を保持することができ、その値の読み出し/書き込み権限を個別に設定できる。コンポーネントからも値を読み出してドライブすることが出来る(->`CloudVariableDriver`)。|

[^*]:データの保持手段として表に挙げた。実際にこれらに書き込む際には、Writeノードではなく専用のノードを使用する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/e4c201fe-a700-3e01-3dff-a8efe9a8b212.png)
元々NeosVRでは名前を付けることができる変数はDynamicValueVariableだけでしたが、ResoniteではGlobal以外の変数に個別に名前を付けることができます。また、変数にカーソルを合わせている間、その変数に関連するノード間に矢印が表示されます。(LogiXのように、ピンクの矢印を近くにおいて関連性をユーザ側が頑張って表現する、といったことをしなくて済むようになりました。)

## データの型
### 新しい型
色を表現するデータ型として新たにColorXという型が存在します。どうやらこのColorXは単純なRGBA以外に何かしらのパラメータを持っているようで(筆者もよくは知らないんですが)、実際のオブジェクトの描画色を指定する場合は殆どの場面でColorXが使われています。旧来のColor型は単純な文字の色などの限定的な場所で引き続き採用されているようです。

### 分類方法
Resoniteでは現在、データの型の分類方法が2種類存在するようです。この中でも特にstringが状況に応じて属するカテゴリーが違います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/c1b54fed-1928-77ab-22b2-bf66ae309ba2.png "同じDynamicValueVariableに関する事柄なのにComponentとProtoFluxで採用されている分類方法が異なる")

#### ValueかReferenceか
この分類方法では、値か、その参照か、という分類をします。このカテゴライズでは、stringは値(Value)側に属します。この分類方法は主にコンポーネントを選択する場面で採用されています。

#### ValueかObjectか
この分類方法はC#に基づく分類方法のようです。詳しいことは省略しますが、stringはObject側に属します。この分類方法は主にProtoFluxのノードを選択する場面で採用されています。

## Async
ProtoFluxになって変化したことについて語る際に、Asyncは避けて通れないでしょう。Asyncを使うことで、非同期処理をうまく扱うことが出来るようになり、ざっくり言えば処理の完了待ちをすることが出来るようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/84370bd5-9de5-402b-cd4d-f2c31a814cec.png)

Updates Delayは、指定されたフレーム数待機をするノードで、引数が未指定の場合は1フレームの待機を実施します。このように、実行完了までに一定の時間を要するノードについては、AsyncのPulseのみを受け付けるようになっており、不正な繋ぎ方をすると画像下のようにノードが赤くなります。ProtoFluxではこのAsyncに関連してPulseを伝達するワイヤーについて少なくとも3種類が存在しており(AsyncResumption, Continuation, AsyncCall)、相互に繋がるもの、繋がるがエラーとなるもの、繋がらないものの組み合わせがあるようです。

Asyncの実装によって、HTTP GETやPlay One Shot And Waitをより柔軟に活用したり、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/17e4b8ee-995c-18d2-62dc-4c1fcef73b06.png)

Local変数がStart Async Task毎に生成されることを利用した再帰処理などが書けます。再帰処理に限らず、特定のFluxのブロックを関数のように独立したロジックとして扱いやすくなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/c514708f-42be-56ea-aedb-1e10d4680414.png)

## その他
### 名前が変わったノード
#### ほぼ全ての演算子
ほぼ全ての演算子が英語表記となりました。
|名称|記号|意味|説明|
|:-:|:-:|--|--|
|Add| + |加|2つの値の和。|
|Approximately|≒|約|2つの値が指定した誤差の範囲内にあるか。|
|Conditional|?:|条件|特定の条件を与え、それが真であればAを、さもなくばBを返す。三項演算子。|
|Distance||距離|2つの値の差を正の値で返す。|
|Div|÷|除|2つの値の商。|
|Equals|=|等価|2つの値が等しいか。|
|GreaterOrEqual|≧|以上|AがB以上か。|
|GreaterThan|>|大なり|AがBより大か。|
|IsInfinity||無限大|値が無限大か。|
|IsNan||非数|値が数値でないか。|
|IsNull||Null|値がNullか。|
|LessOrEqual|≦|以下|AがB以下か。|
|LessThan|<|小なり|AがB未満か。|
|Mask|||Conditionalに近い動作をする。複数のパラメータから構成されるデータ(Color, Float3など)に対して、入力された条件によってAとBどちらの要素を選択するか。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/c514c1f8-9b3d-41f3-6b81-c03637993efd.png)|
|Mul|×|乗|2つの値の積。|
|NotEquals|≠|不等価|2つの値が等しくないか。|
|NotNull||非Null|値がNullか。|
|NullCoalesce|??|Null合体|AがNullの場合、Bを返す。Null合体演算子。|
|Sub|-|減|2つの値の差。|
|ValueCube|n³|立方|値を3乗した値。|
|ValueDec||-1|値から1を引いた値。|
|ValueFilterInvalid||不正フィルタ[^2]|値が不正だった場合に別の値を返す。(ProtoFluxは1/0も0が返るのでこのノードの使い所は不明)|
|ValueInc||+1|値に1を足した値。|
|ValueMod||余|2つの値を除算した余。|
|ValueNegative|-n|陰[^2]|符号を反転させる。|
|ValueReciprocal|1/n|逆数|値の逆数を得る。|
|ValueSquare|n²|平方|値を2乗した値。|
|ZeroOne||0/1|booleanを0か1にキャストする。|

[^2]: 何かいい訳が思いつきませんでした。


#### DataModelBooleanToggle
Boolean Latchのことです。ノードにもそう書いてありますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/fc098e40-3402-d887-f0f3-669282045bbd.png)

#### WorldTimeFloat
Tのことです。ノードにもそう書いてありますね。そもそもTという名前が良くなかったと思うので、これは妥当でしょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/1dee2987-3beb-6e0d-6991-7d775cff47de.png)


### 新しいノード
#### VerifyJoinRequest
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/d5aa6f10-82fd-a317-4c86-d22c6016845f.png)
ユーザがセッションへの参加を試みた際にPulseが発生し、同カテゴリーのAllow Join/Deny Joinノードと組み合わせることで独自の入室条件を設定できます。
ノードのインスペクターを開いて『カスタム参加検証として設定』を押すことで使用できるようになり、ダッシュメニューのセッションタブから有効にすると利用できます。このボタンはホストしか押せません。ResoniteからHeadlessのアカウントが必要なく、同一アカウントで同一セッションに参加できるようになったのは、ヘッドレスと同じアカウントからカスタム参加検証としてアクティベート出来るようにするためだったのではないかと推測しています。

またこのノードではユーザのID情報がStringでしか取得できません。ユーザの更に詳しい情報にアクセスするには、ResoniteのAPIを叩いて各自で取得するような仕組みを作る必要がありそうです。


筆者が作ったより簡単に参加条件を設定できるツールもあります。

![2023-12-09 07.17.22.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/0baa0d74-545e-034a-cb1e-0b098237357f.jpeg)
```
resrec:///U-Sweshelo/R-1047F3D1A4652F15F9D00918D67D6DB678675D64BE1C26F79B5F0BEB36292676
```

#### ValueFieldHook
恐らくLogiXにはなかったノードです。
このノードを使用すると、指定したフィールドに対してDriveを開始し、Drive中に外部からWriteを試みたことを検知できます。
![2023-12-09 07.23.35.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/9a287aa6-6672-2501-f0e0-ea884c85aefa.jpeg)
上記の画像は32という値がDriveされているFieldに対して48のWriteを試みたところを(Driveされているので当然これは反映されません)、FieldHookノードのOnHookによって検知している様子です。書き込もうとした値も取得できています。

#### Coreカテゴリ
全てそうというわけではありませんが、このカテゴリーはComponentのUncategorizedに匹敵する怪しいニオイがします。画像はCore/CastsのObjectCastです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/911135/c508cf95-e5c6-5434-5e74-8b8a5a6ba6f4.png)


## 終わりに
また何か得られたら適宜追記していこうと思います。ここではProtoFluxでのものづくりについて取り上げていますが、ResoniteではUniPocketにも採用されている、Reactベースでモノづくりが出来るMirageX(ミラージュ・クロス)という有志のフレームワークが開発中で、こちらも機会があれば取り上げたいと思います。Resoniteはノードベースだけだと思われがちですが、この自由な空間ではそれも段々と過去のモノとなりつつあるようです。楽しみですね。