Contents — 11 sections
VRChatのパラメータが true になったら、現実側のデバイスをONにする。
技術的にはそんなに難しくない。
OSCを受ける。
Boolを見る。
GPIOや別APIへ出す。
でも、現実側の機器を動かすなら、
「イベントを受けたら何をするか」より「壊れたときにどう止まるか」
の方を先に決めた方がいい。
RealityLinkのP0では、実際の物理出力をまだ無効にして、MockAdapterだけでSafetyCoreを先に作った。
P0では物理出力をあえて動かしていない
最初に実装したのは、
- 型検証
- edge detection
- debounce
- pulse
- re-fire条件
- Heartbeat監視
- state machine
- STOP latch
- epoch
- output queue
だった。
物理デバイスを動かすAdapterはMockだけ。
つまりP0の目的は、
「VRChatから現実を動かせた」
ではなく、
「現実を動かす前に、危ない状態を止められるCoreを作る」
ことだった。
状態は5つに分けた
Coreの状態は、
OFF
READY
ARMED
RUNNING
STOPPED
に分けた。
単純なEnabled Boolだけにしない。
たとえば「Enable=true」でも、Heartbeatが来ていなければARMEDへ進ませない。
STOPへ入ったら、その場でOFFへ戻してすぐ再開、ではなくラッチする。
人間側が安全状態を確認したうえで、再度明示的に有効化する。
「何か異常があったらfalseにする」だけだと、次の受信でまたtrueになり得る。
STOPは状態として残す。
80ms debounce
入力は80ms debounce。
一瞬だけ値が揺れたからといって、現実側へそのまま出さない。
VRChat側のパラメータや通信が、理想的なデジタル接点として振る舞うとは限らない。
「変わった」という事実と、「状態が確定した」を分ける。
300ms pulse
P0の出力は300ms pulse。
ONを受けたら、状態が変わるまで出しっぱなしにはしない。
時間上限を持たせる。
そのうえで、再発火にも条件を置いた。
- falseを200ms観測
- 前回終了から500ms経過
など。
入力がtrueに張り付いた場合に、連打し続けないためだ。
Heartbeatは「同じ値が来た」だけでは更新しない
Heartbeat timeoutは1,500ms。
ただし、同じHeartbeat値を何度受けても延命しない。
値が反転したときだけ「生きている」とみなす。
理由は、古い値や同じpacketが繰り返されるだけで接続健全と誤認したくないから。
通信が止まった。
送信側が固まった。
状態更新だけ止まった。
そういうときは安全側へ倒す。
epochで古いpulseを無効化する
非同期処理で怖いのが、過去に予約した処理。
RUNNING中にSTOPした。
でも300ms後に「pulse終了処理」が動く。
その間に別のstateへ遷移しているかもしれない。
そこでepochを持たせた。
状態遷移や停止でepochを進め、古いepochに属する遅延処理は無効扱いにする。
「昔予約した処理」が現在の状態を書き換えないための世代番号。
出力先ごとに一本のqueueへ通す
複数の非同期処理が、同じDeviceへ同時に書かないようにした。
出力先ごとに単一の送信queueを持つ。
順序を決める。
途中でSTOPされたら、その後に残っている古い仕事も扱える。
I/Oそのものより、この順序制御の方が重要だった。
54 testsと5つのmutation
RealityLink Coreには偽時計を使った54 testsを用意した。
時間依存の条件を、本当に500ms待って試すのではなく、時計を進めて検査する。
さらに5種類、意図的に安全機構を壊した。
例:
- Heartbeatの同値でも期限を延ばす
- epochによる古い処理破棄を外す
- 起動時のtrueだけで発火する
- re-fire間隔をなくす
- Enable基準値だけでARMEDへ入れる
この5つを入れると、テストは全部検出した。
これはかなり大事だった。
Testが54個PASSしているだけでは、
その安全機構をTestが本当に見ているか
までは分からない。
安全機構を故意に壊してTestが落ちるところまで見る。
VRChat側の受信も「何でも受ける」にしない
VRCOSC module側では、登録済みのParameter受信経路だけを使う設計にした。
PC側の許可toggleは起動時にOFFへ戻す。
必要なParameterが見つからなければARMEDを拒否する。
前回実行時にONだった設定が、次回起動時にも勝手に有効、という状態を避ける。
まだ現実の機器は動かしていない
ここは重要。
P0時点ではMock outputのみ。
VRCOSCへの実ロードや、VRChatを通した実経路にも未確認項目が残っていた。
Intifaceの機能照会も未着手。
なので、
「VRChatから安全に現実機器を操作できた」
という記事ではない。
正確には、
そういうものを作るなら、物理出力より前にどんなSafetyCoreが必要かを実装・検証した
という段階。
いまの結論
VR→現実I/Oは、デモならすぐ作れる。
でも現実側を動かす瞬間から、
- 通信が止まったら
- trueが張り付いたら
- packetが重複したら
- 古い非同期処理が戻ってきたら
- STOP後に再度入力が来たら
- 起動時に前回状態が残っていたら
を考える必要がある。
だからRealityLinkでは、
「trueならON」
より先に、
どうやったらOFFへ倒れるか
を作った。
PicoTowaのReality Bridgeは、派手なI/Oデモより、この部分から積み上げていく。