KLabGames Tech Blog

KLabは、多くのスマートフォン向けゲームを開発・提供しています。 スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。 このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。

皆さん Docker 使ってますか?

なんだかよくわかんないからいいや・・・ そんなふうに考えていた時期が俺にもありました。

ということで今回は身近な例として、ローカルマシン上で Docker を使って開発環境を構築した事例を紹介します。 どちらかというと、ぶっちぎってサーバサイド寄りの話でしかも管理寄りの話だったりもしますが、その他の方々にも何かお役に立てれば幸いです。

問題

KLab にはたくさんの案件が有りますが、その中には WEBサーバやDBサーバなどが全部詰まった VM イメージを配布する、という方法でローカルの開発環境を提供している案件も有ります。 それらの案件も息が長くなり、下記のような状況が散見されるようになってきました。

  • 配布した VM を個人好みに育ててしまって、アップデートの心理的障壁となっている
  • 追加のミドルウェアを導入したが、環境のアップデートの伝播が行いにくい等
  • 個人が検証目的などで言語のバージョンを変えたり追加のエクステンションなどを入れたが、それを元に戻していない

その結果、チーム内メンバー間の環境の違いによるトラブル(テストが通らない等)が発生するというけしからん事態になってきました。

方向性

そこで、次の様な戦略でローカル開発環境をもっと簡単に管理していきましょう、というのが本論となります。

コードで管理する。環境の再現をコードで表しておくことで、管理が非常に楽になります。また、配布も git などのリポジトリ経由で気軽に行えるので、配布行為自体もラクになります。

個人の好みを配布物に含めない。コードを書く際のエディタやシェルその他(個人にとっての)便利ツールなどは完全に個人の好みの問題であるので、実行環境とは完全に切り離すべきです。 実行環境を不可侵にしておけば、心理的障壁はなくなるはずです。

構築物イメージ

上記のようにしておけば、気軽に作り直せるし、配布も簡単だし、良いことずくめです。

構成の再現

さて、KLab の場合 http を介したモバイルゲームサービスが主ですから、サーバサイドといってもユニットテストだけでなく、なるべく end-to-end なテストも存在します。 従って、開発環境としても WEB サーバや DB サーバなど、もろもろのサービスが必要となってきます。

これらのサービスをそれぞれ、別のコンテナとして立ち上げていくのが Docker 流のやり方です。 とはいえ、各サービスに対してちまちま docker コマンドを叩いていたのでは埒が明きませんから、docker-composeを利用して簡潔に管理できるようにしています。 例えば下記のような感じです。

docker-compose.yml.dist

web:
  build: ./web
  links: [db, memcached, redis]
  volumes:
    - %%SERVER_REPO_DIR%%:/app/src
    - %%LOG_DIR%%:/var/log/www
  ports: ["8080:80"]
ci:
  build: ./ci
  links: [web, db, memcached, redis]
  volumes:
    - %%SERVER_REPO_DIR%%:/app/src
  command: sleep infinity
db:
  build: ./db
memcached: { image: "memcached:1" }
redis: { image: "redis:2.6" }

このようにして、細かくイメージを作りこみたい場合は Dockerfile を作成、それ以外はオフィシャルイメージを流用するなどして、サービス群の関連などと共に記述していきます。

docker ファミリーのインストールが済んでいれば、環境の構築やアップデートは、

  1. これらのファイルが格納されたリポジトリを clone/pull して最新の構成情報を取得
  2. ビルドする (docker-compose build をたたくだけ)
  3. サービスを開始する (docker-compse up をたたくだけ)

を実行してもらうだけなので、実行環境の再現は非常に簡単です。

docker-compose.yml「.dist」 となっているのは、テンプレートとして使用しているためです。 マウントするパス(%%で囲まれている)は人によって異なるのが理由で、 本環境の導入時に、インストーラースクリプトを実行してもらう事によって実体を作成しています。

腐敗防止層

先述の「腐敗防止層」と名付けられた部分は、利用者が docker そのものを意識しなくてすむような、アダプタ的コマンド集の形をしています。 例えば、下記のようなインターフェイスを提供しています。

myenv build                     # docker-compose build などを集約
myenv up                        # docker-compose up などを集約
myenv server test --group Hoge  # テストを実行する
myenv server init-db            # DB の初期化 & test ready な状態にする
myenv server db-cli             # DBのインタラクティブシェル
myenv server shell              # バッチ実行用サーバを模したインタラクティブシェル

dockerdocker-compose などのコマンドは便利ですが、細かいオプションや docker の概念などを短時間でマスターして下さい、というのも難しい話ですし、何より myenv を実行環境への唯一のエントリーポイントとしておくことで、環境が育ってしまうことが避けられます。

依存を少なく

コマンドの dockerize

開発に必須なツール達が色々なソフトウェアに依存していることもあると思います。 例えば、PHP がないと動かない composer など。 これらも全て docker 上で実行するようにスクリプト化しておけば、面倒な依存のインストールの負担をかけなくてすみます。

# 最小構成の例です。実運用では他にも git などが必要だったり、
# composer が作るキャッシュを volume して永続化するなどの工夫が必要になると思います。
echo 'FROM php:5.6-cli
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin
ENTRYPOINT ["composer.phar"]
' | docker build -t php-composer - > /dev/null
docker run -it --rm -v `pwd`:`pwd` -w `pwd` php-composer "$@"

このようにコマンド単位であっても dockerize して依存を少なくし、環境構築がすぱっといくようにしています。

docker-compose を Windows で動かす

Docker client は Windows でも動くようになりましたが、docker-compose は実は Windows をサポートしていません。 色々なやり方があるかもしれませんが、例えば boot2docker を使用している場合は、docker-compose というイメージ名で docker/compose:Dockerfile をビルドしておけば、下記のように利用できます。

boot2docker ssh -t "docker run -it --rm -v `pwd`:`pwd` -v /var/run/docker.sock:/var/run/docker.sock -w `pwd` docker-compose $@"

イメージのビルドも含め、先ほどの myenv build に組み込んでおけば、Windows でも大丈夫ですね!

まとめ

Docker をどっかどっか使って、管理のしやすさを念頭に置いたローカル開発環境の事例を紹介しました。

今回紹介した事例では web サーバが動いていますので、サーバサイドの開発・テスト用途だけではなく、クライアント開発時の http API サーバとしても利用できます。 ここからさらに発展させて、クライアントのテスト実行環境、その他運用上必要なツールの開発に至る、案件の包括的な開発基盤として発展しているケースもありますが、これについてはまた別途紹介したいと思います。


@gilbite a.k.a. イケ☆メン

※この記事ではgitのタグ「v2.74」から生成したブランチ上でコードリーディングしています。

前回は流体シミュレーションの実験のためのプロジェクトを作成しました。
( プロジェクトファイルのダウンロードはこちら
今回はこれをもとに流体シミュレーションのコードリーディングをしていきます。

流体シミュレーションの計算が行われるのはDomainタイプのFluidオブジェクトのBakeボタンを押したときです。このときに何が起きるのか探っていきましょう。

流体計算の関数を探す

まずはBakeボタンを押したときに呼ばれる関数を探したいと思います。

どうやって探すか?UIパーツにマウスカーソルを合わせると、そのUIに関するPythonの命令を表示してくれます。Bakeボタンにマウスカーソルを合わせてみると、
bake_python
bpy.ops.fluid.bake() というPythonの命令が出てきます。

ここで余談になるのですが、Blenderはコマンドラインからバックグラウンドで立ち上げる事も出来ます。Mac Proなどハイスペックなマシンをサーバーとして用意しておき、流体シミュレーションのBakeなど長く時間のかかるものはサーバーで起動したBlender上でPythonコマンドを使って自動で処理させておくような事も可能です。

さて、話を元に戻してbpy.ops.fluid.bake()を起点にC<ー>Pythonのつなぎの部分のコードを追いかけていくと確実にたどり着けそうですが・・・まあ、もっとシンプルに「fluid」や「bake」で検索すればおそらくたどり着けるでしょう。

Xcodeのパターン検索で、「fluid(Any)bake」で検索すると、
30 results in 4 files
案の定、physics_fluid.cといういかにも関係ありそうな名前のファイルが引っかかります。 このphysics_fluid.cのなかで「fluid(Any)bake」は27カ所。その中でも関数の定義だけに絞り込んで関係してそうな名前のものだけピックアップしてみると

static int fluidsimBake(bContext *C, ReportList *reports, Object *fsDomain, short do_job)

static int fluid_bake_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))

static int fluid_bake_exec(bContext *C, wmOperator *op)

となります。

fluid_bake_invoke()もfluid_bake_exec()も両方中でfluidsimBake()を呼んでいるので、fluidsimBake()の先頭部分にブレイクポイントを張ってBakeボタンを押してみる事にします。すると狙い通りブレイクポイントに引っかかりました!

fluidsimBake()の中身を追う

fluidsimBake()の中身は232行に渡っています。その中の多くはパラメータをセットする命令になっているようです。最後の方にジョブをスタートさせたりコールバックの設定をしたりするような部分があります。おそらく別スレッドで非同期に動かすためのジョブのパラメータをセットし、最後にジョブをスタートさせる事で流体シミュレーションを実行させているのではないでしょうか?Bakeが終わるのを待っている間他の操作が同時に出来るので、そう考えると自然な気がします。

ジョブを追う

このような予測のもと、ジョブを中心に追いかけていこうと思います。実際の流体計算にたどり着き、それが別スレッドで実行されていれば予測が的中しているものと見てよさそうです。

fluidsimBake()の最後の方で呼ばれているfluidbake_startjob()がジョブをスタートさせる関数で、これに渡されているジョブがfbと言う名前の付いたFluidBakeJob構造体のポインタです。

typedef struct FluidBakeJob {
    /* from wmJob */
    void *owner;
    short *stop, *do_update;
    float *progress;
    int current_frame;
    elbeemSimulationSettings *settings;
} FluidBakeJob;

最後のelbeemSimulationSettings *settings;にいろいろ詰まってそうです。

fluidbake_startjob()を呼ぶ直前で、

fb->settings = fsset;

というように、fssetというポインタが渡されています。

このelbeemSimulationSettings構造体の中身に

elbeemRunSimulationCallback runsimCallback;

という関数ポインタ(別の場所でelbeemRunSimulationCallbackはtypedefされている)が定義されています。コールバックなのでもし流体計算が別スレッドで処理されていればそのスレッドから呼ばれるので、ブレイクポイントで止めれば計算箇所を突き止められるかもしれません。

fsset->runsimCallback = &runSimulationCallback;

のように、physics_fluid.cの中の

static int runSimulationCallback(void *data, int status, int frame)

がセットされています。この関数の先頭にブレイクポイントを仕掛けてみましょう。すると予想通り別スレッドから呼ばれている事がわかりました。ブレイクポイントに引っかかっているので、どの関数からコールバックが呼ばれているのかもわかります。

「elbeem」ってなんだ??

どうもさっきからelbeemSimulationSettingsやelbeemRunSimulationCallbackの「elbeem」という名前が目につきます。そしてブレイクポイントを張って調べたrunSimulationCallback()もbf_intern_elbeemフォルダの中のソースから呼ばれています。

この「elbeem」って何なんでしょう?Googleで調べてみると
http://elbeem.sourceforge.net
にたどり着きます。

「El'Beem Is an Open-Source free surface fluid simulation library.」Blenderは流体シミュレーションのためにこのライブラリを採用していたのですね!格子ボルツマン法に基づいたライブラリだそうです。これだけで参考書や論文が何本もありそうですね(^^; こ、ここを追いかけるのは ま、またの機会に・・・|)彡サッ


@fmystB

ターミネーター

2015年7月10日に映画ターミネーターの最新作「ターミネーター: 新起動/ジェニシス」が公開になりましたね!

この最新作でも「ターミネーター2」で初登場してシュワちゃん達を苦しめたアイツが登場するようです。

そう、液体金属ロボット「T-1000」です!

液体金属なのでどんな形にも自在に変形可能。手を武器に変形させて襲いかかります。小さな隙間から侵入したり、別の人物に成り代わって待ち伏せする事も可能です。銃で撃たれても自己再生するのでとても厄介!

Youtubeで検索すれば元祖の方も含めてT-1000の映像はたくさん出てきますよ。

ターミネーター2が公開されたのはずいぶん前の話ですが、あの時代にもこんなにすごい映像を作る技術が存在したのですね。

私はこのT-1000が大好きで、技術的目標の一つなのです。それもあって今では3D関連のプログラミングを仕事にしています。見る人を「あっ!!」と言わせるようなものを作りたい!

私以外にも多くのファンを惹き付けたT-1000ですが、この映像を作るためにシェーディングや流体シミュレーションなど色んな技術が使われている事でしょう。今は映画スタッフのような専門の人でなくても普通の人が無料でこのような技術に触れる事が出来るんですよ!そう、Blenderの出番です。

特に今回はT-1000の動きに関係の深そうな流体シミュレーションの技術に触れてみましょう。と、いうわけでBlenderの流体シミュレーション周りのコードを探っていきたいと思います。part1では下準備として、簡単な流体シミュレーションのプロジェクトをBlenderで作っていきます。

とってもお手軽な流体シミュレーションのやり方

( 作り方は気にせず完成したプロジェクトだけ欲しい場合は下にファイルへのリンクがあります。 )

1、オブジェクトの中にオブジェクトを作る都合上、ひとまずWireflame表示に切り替えます。
wireflame

2、デフォルトで配置されているキューブを適当に大きくします。
default
big

3、上記のキューブの内部に新しくキューブを追加して、大きいキューブ内部の上の方に移動します
small

4、さらに障害物となるキューブを追加し、大きさを調整して移動します。
obstacle_perspective
obstacle_frustum

5、ここから物理オブジェクトとしての設定をしていきます。まず小さいキューブを選択してプロパティエディタのPhysicsタブからFluidボタンを押します。
fluid
Typeは「Fluid」を指定します。
type_fluid

6、次に障害物用のオブジェクトを選択して同じようにFluidボタンを押します。Typeは「Obstacle」を指定します。
type_obstacle

7、最後に大きいキューブを選択してFluidボタンを押し、Typeは「Domain」を指定します。
type_domain

8、DomainタイプにはBakeボタンがあるので押します。
bake_button
これで物理計算が走り、進捗は画面上に表示されるプログレスバーで確認できます。時間のかかる処理なので途中でキャンセルしたい場合はxボタンを押しましょう。
baking

9、物理計算が終了した後、アニメーションを再生するとドメイン内で障害物に邪魔されながら水の落ちる様子が確認できます。
playing

今回のプロジェクトファイルのダウンロードはこちら(右クリックでリンク先を名前を付けて保存してください)
ちなみに、上記bakeボタンを押して物理計算が終了すると、このfluidsym.blendと同じフォルダにcache_fluidというフォルダが出来て中に計算結果のキャッシュデータがたまるのですが、このデータはファイルサイズが大きいためここには置かないので上記ファイルダウンロード後にbakeボタンを押してご自身で作成してください。これが無いとアニメーションを再生させても水が落ちません。

プリレンダリングという技術

Bakeには少々時間がかかると思います。MacBook Pro Mid 2014, 2.6GHz intel Core i5で実験しましたが数分待たされました。このようにレンダリングに必要なデータを事前計算しておく事はプリレンダリングという技術に分類されます。ゲームのムービーシーンや映画で使われる技術で、ターミネーターシリーズでも様々なシーンで使われている事でしょう。ゲームのプレイ中にユーザーの操作を動的に受け付けながらレンダリングするリアルタイムレンダリングとはまた違った技術です。Blenderには多くのプリレンダリングの技術が含まれているのでプリレンダリングを研究したい場合は他に無いくらい最適な材料になるかと思います。

今回作ったプロジェクトを使って、part2ではブレイクポイントを張りながらコードを追いかけていきます。それではまた次回。
I'll be back.


@fmystB

↑このページのトップヘ