openFrameworksでGUI化したパラメータをDBに保存してみた
前置き
openFrameworksはC++でリアルタイムに映像・音声等のメディアやユーザからの何らかの入力を処理し視覚・聴覚的な表現に置き換えたり他のアプリへ処理結果を通信したりするのに大変便利なフレームワークです。
例えば、webカメラからの入力を画像処理して画像中の手の位置にキラキラと軌跡を出すとか、そんな具合です。
上の例だと手の検出部分が最も注力したいタスクだと思います。しかし、実際のアプリを完成させるにはループ処理やカメラの入力部分、キラキラを画面に描画するための手続き等を行わなくてはなりません。openFrameworksはそれらを全て用意してくれており、すぐに最も注力したいタスクに取り掛かれます。ただし、GUIを除いて。
GUIがめんどくさい
私はGUIで設定した値を保存する部分を実装することがopenFrameworksでの開発で一番めんどくさいです。
実際に画面に描画されるGUIはimguiというC++ライブラリをopenFrameworksで使いやすくしたアドオンが作成されています。これを使えばGUIが簡単に描画できます。
しかし、これにはそのGUIパラメータを保存する機能がありません。
私の知っているスタンダード(?)な方法はGUIパラメータにした変数を何かしらシリアライザを通してjsonなりxmlで保存する方法です。ただし、これには以下のめんどくさいポイントがあります。
GUIパラメータにした変数をシリアライザに渡す処理を書かないといけない
当たり前っちゃ、当たり前なのですが、これがめんどくさい。GUIを最初に作るときには何の苦労もないのですが、アプリの開発が進むにつれて削除されたパラメータや追加されたパラメータが出てきます。その度にシリアライザに渡す処理を消したり足したりと忘れず完璧にこなせる自身が私にはないですし何度も忘れてきた確かな実績があります。私には無理です。
保存したパラメータとアプリのGUIの項目に差分がある場合の処理を考える必要がある
これも開発が進むにつれてGUIパラメータが削除されたときや、本番稼働しているアプリの設定ファイルが何らかの原因で変更されてしまう(そんなことがそもそもあってはならないのですが。。。)場合に、設定ファイルから変数に戻すデシリアライズで「そんな変数ないよ!」とエラーになってしまうケースです。このエラー処理を書かないといけないのもめんどくさいです。
めんどくさいポイントを2つ挙げましたが、つまりどうなって欲しいかまとめました。
GUI用に宣言した変数は勝手に保存されて欲しいし、保存したファイルから勝手に復元してほしい!!
ということで、変数の値をDBに常に保存しつづけてみます。
DBの選定
C++アプリから常に変数を保存され続けるのですから、高速でなければなりません。ただし、GUIパラメータを保存する程度なので容量は全然なくていいです。これらのことからインメモリで動くDBが良さそうです。
あと、どんな値(型)が保存されるかまで気にして設計していては余計にめんどくさいことになりそうなのでNoSQLがいいです。ということで、インメモリで動くNoSQLなデータベースで世の中的によく使われているメジャーなものを探してきました。
こちら、Redisです。
Redisは要件通り、インメモリで動作するNoSQLなデータベースで、どのくらいメジャーなのかというと、Amazon ElastiCacheに採用されるくらいです。
一般的な使い所としてはwebアプリケーションのセッション管理やRDBのキャッシュの保存等、リアルタイム性を求める場面でよく採用されているようです。今回はweb開発ではなくC++のアプリですが、たしかにリアルタイム性を求めているので、ピッタリということにしました。
シリアライザの選定
Redisは基本的にStringで値を保存します。ListになったりHashになったりと1つのKeyに対してValueが構造を持つことはありますが、値そのものはStringです。このままではC++からintでもfloatでもboolでもstringで保存されstringで読み込まなくてはなりません。自分で型に合わせた変換を実装するなんてことはしたくないので、シリアライザを探しました。
こちら、MessagePackです。
JSONよりも速く小さくシリアライズができるとのことですが、どうやらRedisと合わせて使われている事例が多そうだったのでとりあえず選定しました。小ささは今回求めていませんが、速いのはいいことです。
さっそく作って試してみた
以下とりあえず作ってみたリポジトリはこちらです。
(ofxと接頭辞がついているあたり、アドオンを作るぞという当時の私の気合がみて取れますが、もう実験だけして終わりにしたい気持ちです)
実験環境を整える方法はREADMEに書きます。
アプリはopenFrameworksのofAppというメインのクラスとguiParameterという今回作成したクラスだけの構成です。画面に円を描画するだけなのですが、GUIで円の半径の大きさを変えることができます。
guiParameterクラスはテンプレートになっており、型とDBへのコネクションとDBでのKeyで初期化されます。このとき、DBへアクセスしてKeyに値があればその値をmessagepackでデシリアライズして保持します。そのあとはget/setValue()で値をゲット/セットしたり、imgui用に値へのポインタも返すようにしています。
値を変更した後や任意のタイミングでdbsync()を呼び出せばmessagepackでシリアライズ、初期化で指定したKeyでDBに保存までをおこないます。(本当はdbsyncも自動でやりたい)
サンプルのアプリではimguiで値が変更されるたびにdbsync()を呼んでいるので、値は常にdbに保存され続けることになります。
高速なシリアライズで高速なDBとはいえ、速度は本当に大丈夫?
サンプルのアプリには普通のintとguiParameterのintとの値の書き込みの時間差を図る実験も組み込まれています。それぞれ10万回のループの中で今のループ回数をそのまま値に書き込んでいます。もちろん、guiParameterはDBへの書き込みまで行います。結果はこちらです。(めんどくさいので1回しか試行してません)
普通のint : 4.70877e-06 sec
guiParameterのint : 5.61362 sec
だいぶ差がついてしまいましたね。ちなみに、guiParameterでシリアライズだけしてDBへの書き込みをなしにした場合の結果がこちらです。
普通のint: 5.60284e-06
guiParameterのint : 0.0273081
ほとんどの時間をDBへの書き込みにつかっていることがわかりました。
まとめ
redisとmessagepackを使ってOFのGUIパラメータを変更するたびにDBへ値を保存する実験をしてみました。結果は普通のintと変わらないような速度は出ないですが、10万回を5秒で行っているのでGUIでパラメータを変更するのにかかる時間はわずかです。人によっては使えると思ってもらえるでしょうか。もし使えればアプリが突然クラッシュしてもクラッシュする直前の値はDBさえ生きていれば保存されるし、DBにさえアクセスできればGUIをブラウザベースのよりリッチなものへ完全分離することもできると思います。
GUIでパラメータ操作しているときにこそリアルタイム性を求める場面もあると思うので、そういった場合には不向きといえそうです。
なんだか久しぶりにやってみた的なことができて楽しかったです。時間をみて追加の実験や開発もやれればいいなと思っています。年末年始なんてちょうどよさそうですね。