発明のための再発明

Webプログラマーが、プログラムの内部動作を通してプログラムを作る時の参考になるような情報を書くブログ(サーバーサイドやDevOpsメイン)

WebエンジニアがVRに手を出した軌跡

Web業界で働いていて、普段はRubyを書いているエンジニアがVRに手を出して、なんとか自分がしたかったことが形になりました。
その経験から、「VR開発ってこんな感じ」というのを伝えられればと思います。

結論を言えば、「Webエンジニアにとってはブラウザ、アプリに加えて第三のフロントができた」という感じなので、今までの経験を活かしてVR開発ができます。
自分が今作っているものはまだ作りたいものの全てはできていませんが、それは作業時間の問題であって実装自体に大きな壁があるわけではないという印象です。

作ったものは、こんな感じです。
VR上で、保有している株の推移を一覧できます。

マルチディスプレイで株価が見たい!

趣味で株をやっているので、映画やドラマの中でトレーダーが何個もディスプレイを使っている姿を見て「いっぱい見れて、いいなぁ」と思っていたのですが、
VRでならできるじゃないか!」と思い、作りました。
今は、自分が持っている株の推移が見れるようになったところです。
ここからさらに、持っていない株のチャートも観れるようになれば、優良株を見つけやすくなるのではと期待しています。

VR開発の軌跡

OculusとGPUを用意する

とにもかくにも、VR環境が無くてはどうにもならないので、用意します。それと、Unity
最初はAndroidVRで動かそうと思っていましたが、スペックのせいか画面がカクついて気持ち悪くなったので、Oculusを買いました。

VRを勉強する

入門として、「UnityによるVRアプリケーション開発 ―作りながら学ぶバーチャルリアリティ入門」を読みました。

UnityによるVRアプリケーション開発 ―作りながら学ぶバーチャルリアリティ入門

UnityによるVRアプリケーション開発 ―作りながら学ぶバーチャルリアリティ入門

これを読んで、「モデリング要素は畑違いだが、プログラミング要素は今の知識で十分」と感じました。
モデリング部分は全く初めてで、Webの仕事でもUI/UX部分は避けているので、「あ、これ、きつい」という感じがしました。
逆に、プログラミングはC#メインで、Webとは違った使い方をするわけでも無かったので、簡単です。 Webにはない概念だなと思ったのは、「角度や距離」といった、普段はブラウザが計算してくれているものを自分で計算する必要が出てくる時です。そのあたりは、数学を思い出して、慣れる必要があります。

ただ、この本を読んでいて、途中からVR入門ではなく、プログラミング入門のような話に移っていったので、途中で読むのをやめて実際に作り始めました。
Webエンジニアにとっては、Unityやmayaの操作方法とVR独自のUIがわかれば、入門書は十分だと思います。

作る

本を読んでいて、「後は実際に作って、問題に当たったほうが早い」と感じたので、本を読み終えずに作り始めました。
本で出会わなかった問題は、

  1. Oculus touchのボタン操作で移動したい
  2. 外部通信したい
  3. グラフ書くのが面倒

でした。

Oculus touchのボタン操作で移動したい

Oculusが提供しているライブラリを使えば、ボタン入力やスティックの角度が取得できます。
移動にスティックを使ったので、スティックの角度とカメラの角度を合わせることで、移動方向が決められます。
sin,cos使った回転が出てきたりして、「あー、ゲームで三角関数を使うっていう話は、こういうときのことを言ってたんだなぁ」としみじみしました。

ちなみに、移動速度を間違えると本当に気分が悪くなるので、速度にはかなり注意が必要です。

外部通信したい

UnityにはJSONパーサーがあるので、パーサーの癖さえつかめば、WebのAPI通信の知識で外部通信は問題なくできます。

グラフ書くのが面倒

Graph And Chart」というAssetを買いました。
時間を金で買った感じです。
他にも、今は使っていないものの無料のAssetで室内を作ったりもしました。

VRの感想

初心者レベルのものであれば、Webエンジニアでも十分作れると感じました。
デザインは全然違うので、VRのデザインを学ぶ必要はありますが、プログラミングの考え方は同じです。
さらに、サーバーへの通信より先は、Webの領域と全く同じです。
つまり、「フロントの技術にブラウザ・アプリに加えて『VR』という項目が増えた」という考え方でよいと思います。
なので、そこまで難しくないです。
VR開発には、億単位の金をかけられるという話もあるみたいなので、凝れば凝るだけすごいものを作れるのでしょうが、趣味で適当なものを作る程度のものは、十分できるので、Webエンジニアでもやればできるなぁという感想です。

gVisorのechoを読む

gVisorが面白そうだったので、どんな風に動いているか書きます。

使用したコミットは、

GitHub - google/gvisor at c400a0356b856e71fd30e3fe10372d7bb94356cb

です。

gVisor概要

gVisorは、先日googleが公開した、アプリケーションとホストの分離を目指したコンテナランタイムです。
システムコールをコンテナから直接実行するのではなく、システムコールをgVisorが受け取って実行を制御することで、コンテナをより安全に動かせるようにしています。

GitHubは、ここ

github.com

gVisorのコードは短くはないのですが、色々な機能を実装するために多くなっているだけで、各機能に必要なコードはそれほど多くなく、つまみ食いにはちょうど良い大きさという印象です。

動かし方

bazelでビルドして、Dockerでランタイムを指定すれば動きます。
READMEに詳しく書かれているので、簡単に動かせると思います(https://github.com/google/gVisor#building)

gVisorのechoの動き

本題の、コンテナ内でechoを実行したときに何が起きているか見ていきます。
今回は、ubuntuを起動し、その中で echo "Hello"を実行しました。
簡単に図にすると、下のように動いています。

f:id:mrasu:20180504185959p:plain

順番に見ていきます。

1. ubuntuシステムコール

echo "Hello" のコマンドを実行したところから始まります。

2. gVisorが気づく

echoが動かすWriteシステムコールにgVisorが反応するところから、gVisor仕事が始まります。
Task#run(pkg/sentry/kernel/task_run.go)という関数がループの中でシステムコールを待っています。

3. システムコールの内容をptraceで取得する

まず、gVisorがどんなシステムコールが実行されたかを知る必要が有ります。
そのために、Task#setRegsからptraceを呼び出します。
PTRACE_GETREGSを使用することで、対象プロセスのレジスタをコピーします。
これによって、gVisorはシステムコールの内容を把握できます。
例えば、Orig_raxにあるシステムコールの番号や、rdiなどにある引数などが取得できる情報です。

4. 実行するべきでないものを除外

システムコールの番号がわかったので、それを実行するべきかどうかを判定します。
Task#checkSeccompSyscallという関数が実行処理の判定をし、許可していないシステムコールを弾いています。

5. 前処理

システムコール(ここでは、Write)を実行するとなったら、まずは前処理として、.bootにログを出したりします。

6. システムコールに対応した処理を実行

gVisorはホストにシステムコールを直接投げるのではなく、gVisor自身が別のシステムコールを実行します。
どのようにシステムコールを取り扱うかは、pkg/sentry/syscalls/linux/linux64.go に色々と処理が書かれています。
ただ、まだ全部の処理が実装されているわけではなく、TODOがあったり、318までしかシステムコール候補がなかったりします。

7. Writeを実行

echoの出すWriteは当然実装されていて、pkg/sentry/syscalls/linux/sys_write.goに処理内容が書かれています。

8. ホストへWriteする処理を実行

sys_writeのwriteには共通処理が書かれているのですがechoはホストに書く処理なので、続いて個別処理としてpkg/sentry/fs/host/file.goのWriteが実行されます。

9. コマンドプロンプトに表示される。

ホスト用のWriteが動いて、ようやくコマンドプロンプトに "Hello"が表示されます。

まとめ

以上が、 echo "Hello" の流れでした。

このように、gVisorはコンテナがシステムコールを直接実行するのではなく、gVisorがホストの間に入っています。
この機能がsentryで、怪しいコンテナが変なシステムコールを実行するのをブロックしてくれます。
そのおかげで、コンテナがより安全に動かせるようにできるようになっているというわけです。

javascriptで記号プログラミング

JS記号プログラミング入門 - Qiita

で紹介されていた記号プログラミングがとてもおもしろかったので、chromeで作ってみました

コンソールに大きめの文字で「hello crazy hacker」と出力するコードです。

綺麗な書き方は、

console.log('%chello crazy hacker', 'font-size:30px')

記号だけで書いたのが、

[]
[(({})+[])[-~-~-~-~-~[]]+(({})+[])[-~[]]+([][[]]+[])[-~[]]+(![]+[])[-~-~-~[]]+(!![]+[])[+[]]+(!![]+[])[-~[]]+([][[]]+[])[+[]]+(({})+[])[-~-~-~-~-~[]]+(!![]+[])[+[]]+(({})+[])[-~[]]+(!![]+[])[-~[]]] // ["constructor"]
[(({})+[])[-~-~-~-~-~[]]+(({})+[])[-~[]]+([][[]]+[])[-~[]]+(![]+[])[-~-~-~[]]+(!![]+[])[+[]]+(!![]+[])[-~[]]+([][[]]+[])[+[]]+(({})+[])[-~-~-~-~-~[]]+(!![]+[])[+[]]+(({})+[])[-~[]]+(!![]+[])[-~[]]]( // ["constructor"](
(({})+[])[-~-~-~-~-~[]]+(({})+[])[-~[]]+([][[]]+[])[-~[]]+(![]+[])[-~-~-~[]]+(({})+[])[-~[]]+(![]+[])[-~-~[]]+(![]+[])[-~-~-~-~[]]+ // console
(({})+[])[+[]]+(/'/+[])[-~[]]+ // ['
(![]+[])[-~-~[]]+(({})+[])[-~[]]+((/ /[(({})+[])[-~-~-~-~-~[]]+(({})+[])[-~[]]+([][[]]+[])[-~[]]+(![]+[])[-~-~-~[]]+(!![]+[])[+[]]+(!![]+[])[-~[]]+([][[]]+[])[+[]]+(({})+[])[-~-~-~-~-~[]]+(!![]+[])[+[]]+(({})+[])[-~[]]+(!![]+[])[-~[]]])+[])[(-~-~-~[]<<-~-~[])+~[]]+ // log
(/'/+[])[-~[]]+(({})+[])[-~-~-~-~-~-~-~[]*-~-~[]]+ // ']
(/\(/+[])[-~-~[]]+(/\(/+[])[-~-~[]]+(/\(/+[])[-~-~[]]+(/\)/+[])[-~-~[]]+(/=/+[])[-~[]]+(/>/+[])[-~[]]+(/'/+[])[-~[]]+  // "((()=>'"
(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~[]]+[-~-~-~-~-~[]]+(({})+[])[-~-~-~-~-~[]]+  // "%c"
(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~-~-~-~-~[]]+[-~-~-~-~-~-~-~-~[]]+([][[]]+[])[-~-~-~[]]+(![]+[])[-~-~[]]+(![]+[])[-~-~[]]+(({})+[])[-~[]]+(({})+[])[-~-~-~-~-~-~-~[]]+ // "hello "
(({})+[])[-~-~-~-~-~[]]+(!![]+[])[-~[]]+(![]+[])[-~[]]+(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~-~-~-~-~-~[]]+(![]+[])[-~[]]+(-~[]/+[]+[])[~(~[]+~[]<<-~-~[])]+(({})+[])[-~-~-~-~-~-~-~[]]+ // "crazy "
(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~-~-~-~-~[]]+[-~-~-~-~-~-~-~-~[]]+(![]+[])[-~[]]+(({})+[])[-~-~-~-~-~[]]+(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~-~-~-~-~[]]+({}+[])[-~-~[]]+([][[]]+[])[-~-~-~[]]+(!![]+[])[-~[]]+(({})+[])[-~-~-~-~-~-~-~[]]+ // "hacker "
(/'/+[])[-~[]]+(/\)/+[])[-~-~[]]+(/\(/+[])[-~-~[]]+(/\)/+[])[-~-~[]]+(/,/+[])[-~[]]+(/\(/+[])[-~-~[]]+(/\(/+[])[-~-~[]]+(/\)/+[])[-~-~[]]+(/=/+[])[-~[]]+(/>/+[])[-~[]]+(/'/+[])[-~[]]+  // "')(), (()=>'"
(![]+[])[+[]]+(({})+[])[-~[]]+([][[]]+[])[-~[]]+(!![]+[])[+[]]+(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~[]]+([][[]]+[])[-~-~[]]+ // font-
(![]+[])[-~-~-~[]]+([][[]]+[])[-~-~-~-~-~[]]+(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~-~-~-~-~-~[]]+(![]+[])[-~[]]+([][[]]+[])[-~-~-~[]]+(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~-~[]]+(![]+[])[-~[]]+ //size:
[-~-~-~[]]+[+[]]+(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~-~-~-~-~-~[]]+[+[]]+(/\\/+[])[-~[]]+([][[]]+[])[+[]]+[+[]]+[+[]]+[-~-~-~-~-~-~-~[]]+[-~-~-~-~-~-~-~-~[]]+ // 30px
(/'/+[])[-~[]]+(/\)/+[])[-~-~[]]+(/\(/+[])[-~-~[]]+(/\)/+[])[-~-~[]]+(/\)/+[])[-~-~[]] // "')())"
)()

これで、生成されるのが、

[]["constructor"]["constructor"](console['log']((()=>'\u0025c\u0068ello cra\u007ay \u0068ac\u006ber ')(), (()=>'font\u002dsi\u007ae\u003a30\u0070\u0078')()))()

実行すると、

f:id:mrasu:20180503151938j:plain

となり、期待通り表示されました