Dec 07, 2013

Debian SqueezeにTomcat+MySQL環境構築

玄箱にDebianをクリーン再インストールしたので、Tomcatの動作環境も復旧することにした。結局、このサーバーでもPerlのCGIがメインで、JavaのservletやJSPはあまり作っていないのだが、筆者にはJavaへのこだわりがあり、まだ諦め切れないのである。

サーブレットの動作環境としては、引き続き、Tomcatを選択する。また、MySQLを使うservletがあるので、その為のJDBCが必要である。
以下、MySQLはセットアップ済だとする。

■手順
1. aptitudeでtomcat6とlibmysql-javaをインストール
2. HTTPの待ち受けポートを変えるなら、/etc/tomcat6/server.xmlを適当に編集する。
3. /etc/init.d/tomcat6 restart としてTomcatを再起動する
4. Webブラウザーからアクセスし、"It works !"の画面が出ることと、CATALINA_HOME とCATALINA_BASEを確認する
5. $(CATALINA_HOME)/sharedまたは $(CATALINA_BASE)/sharedディレクトリーに、
 /usr/share/java/mysql-connector-java.jarへのシンボリックリンクを作成する
 例:

ln -s /usr/share/java/mysql-connector-java.jar /usr/share/tomcat6/shared/

6. $(CATALINA_BASE)/webappsにservletを置いて、Tomcatを再起動する

See more ...

WriteBacks

Dec 01, 2013

玄箱HGにwheezyを諦めてsqueezeをインストール

夏くらいから、このブログに大量のスパムコメントが届くようになった。その数、1日に800件以上。MovableTypeのSpamLookupプラグインによるフィルターをすり抜けるコメントが200件を越え、日々大量のコメントを手作業で削除するのが阿呆らしくなってきた。

まず、MovableType 3.xのサポートが終了したので、SpamLookupのブラックリストもメンテナンスされなくなったのではないかと思った。(その真偽の程は未確認である。)
そこで、先日、MovableTypeをバージョンアップしようと思った。
しかし、最新のMovableType 5.xや6.0は多機能で、システム要件が3.xと変わっており、インストールに時間を割く羽目になる予感がした。

このサーバーは玄箱HG + Debianである。ふと、aptitudeでmovabletypeを検索してみると、あった。MovableTypeのバージョンは4.xとなっていた。
暫くaptのデータベースを更新していなかったので、更新すればバージョン5.xもaptitudeでインストールできるのではないかと思ったが、何故か、更新の操作をしても全く更新されなかった。しばらくして今頃気付いたのだが、Lennyの更新は昨年3月頃に終了していたようだ。

最新のDebianのバージョンは7(wheezy)だそうだ。/etc/apt/sources.listを書き換えてwheezyのパッケージリストを取得してみると、movabletypeのバージョンは5.xだった。何故か、まだ更新されているDebian 6(squeeze)だと、最新のmovabletypeは4.xである。これはwheezyにアップグレードするしか無い、と思った。

Lennyをインストールした(というかされてしまった)時は起動に苦労する羽目になったが、それで玄箱HGが起動する2.4系のカーネルのLinuxから2.6系のカーネルを起動する2段ブート環境になったし、Linuxのカーネルは2.6系になったし、もうDebianをアップグレードしても大きなトラブルになることはなかろう、と思って、/etc/apt/sources.listのlennyをsqueezeに書き換えて、aptitudeでデータベース更新→自動的にマークされたパッケージをインストール、とすると、途中でいくつか理解できなかった警告があったものの、何とか更新が完了し、再起動もできた。

その勢いで、/etc/apt/sources.listのsqueezeをwheezyに書き換えて、同じようにaptitudeで更新インストールすると、途中で"FATAL: kernel too old"というエラーが出て、回復困難な状態になってしまった。

アンインストールしようがないlibc6のインストールが途中で失敗しており、その後はaptitudeやapt-getで何をやろうとしても、

Setting up libgcc1 (1:4.6.1-1) ...
FATAL: kernel too old
Segmentation fault
The following packages have unmet dependencies:
libc-dev-bin : Depends: libc6 (> 2.13) but 2.11.x is installed
libc6 : Depends: libc-bin (= 2.11.x) but 2.13.x is installed
libc6-dev : Depends: libc6 (= 2.13-x) but 2.11.x is installed
となった。中断したインストールを強制実行する
apt-get -f install
でも結果は同じで、libc-dev-binやlibc6-devを個別にインストールしようとしても先にこのエラーに阻まれ、apt-getにlibgcc1やlibc6のインストールを保留させる方法も分からなかった。

既に再起動もできない状態だと思った(バックアップの準備をして、すぐ玄箱をEMモードに切り替えて電源を落とした)ので、仕方なく、Debianを再インストールすることにした。

試しに、不要なHDDを玄箱に繋いで、同じようにlenny->squeezeとアップグレードして、
/etc/apt/sources.listにてwheezyのディレクトリーを指定して、

apt-get update
apt-get upgrade
(最小アップグレード手順)としても、同じ状態になった。玄箱へのDebianのインストールに使用しているLinux kernelのバージョンが2.6.25なのに対し、wheezyのインストールには2.6.26以降のkernelが必要であることが原因のようだ。
そう思って、squeezeの状態でaptitudeでlinux-image-2.6.32-5-powerpc等をインストールしてみても、2段ブートの1段目から起動されるカーネルが置き換わる訳ではないので、無効であった。
玄箱や玄箱HGにwheezyをインストールした人も居るので、1段目から起動するカーネルかU-Bootのカーネルを更新さえすればできそうではあるが、そこまでするよりは手作業でMovableTypeをインストールする方が楽だと思ったのと、squeezeのサポートが終了する頃にはそろそろ玄箱HGよりも省電力な小型Webサーバーに買い換えたくなるような気がするので、wheezyのインストールは諦めた。

そこで、とりあえずsqueezeをきちんとインストールしておくことにした。

■手順
1. 玄箱HGにLennyをインストールする
2. /etc/init.d/kuroevtd の2行目辺りに以下のコメント行を追加
(squeezeのdependency based boot sequencingに対応する為、/etc/init.d/*や/etc/rc?.d/*はLSBInitScripts形式にする必要がある)

### BEGIN INIT INFO
# Provides: kuroevtd
# Required-Start: $network $remote_fs $syslog
# Required-Stop: $network $remote_fs $syslog
# Default-Start: 2 4 5
# Default-Stop: 0 1 3 6
# Short-Description: Start/stop the KURO-BOX Event daemon
### END INIT INFO

3. /etc/init.d/kuro_boot の2行目に以下のコメント行を追加

### BEGIN INIT INFO
# Provides: kuro_boot
# Required-Start: $remote_fs $all
# Required-Stop:
# Default-Start: 2 4 5
# Default-Stop: 0 1 3 6
# Short-Description: Initialize the KUROBOX AVAR
### END INIT INFO

4. /etc/apt/sources.list の"lenny"を"squeeze"に変更
 なお、日本国内なら http://ftp.jp.debian.org/debian の行だけにしてもOK
5.
apt-get update
を実行、しばらくするとGPGのエラーになるので、続いて
apt-get install debian-keyring debian-archive-keyring
を実行(エラーになればもう1度実行)
6.
apt-get dist-upgrade
を実行

See more ...

WriteBacks

Sep 22, 2013

AndroidでOpenGLしてみた(2)

またしても運良くAndroidタブレットを借りることができたので、この間のOpenGLのテストアプリをAndroidに移植してみた。

・実行画面(シミュレーター)

・Android用実行ファイル
AndroidTestP1.apk

・ソースコード
AndroidTestP1src.tar.gz

筆者はAndroidに疎く、見よう見まねで何とか動かしたというだけのものである。
以下、今回苦労した点を記録する。

・ADT付属のemulatorではOpenGLの動作が完全には再現されない
最も時間を取られたのは、ポリゴンを重ねて描画した時のシミュレーターの動作だった。前回のエントリーに書いた影行列を設定してオブジェクトを描画すると、上の画像でもそうなのだが、影が所々にしか塗られなかったり、汚いまだら模様になったりする。
色々試した結果、DEPTH_TESTが有効で、2つのポリゴンの面が重なるように近くにあると正しく動かないようであることがわかった。基本的にはdepth値の精度に起因する誤差による不具合だと思うが、表面と裏面を重ねてCULL_FACEを有効にしても表面が裏面に隠されて何も描画されない部分が出てくるし、色もおかしくなるなることがある((R,G,B)=(1,0.5,0)のオレンジのポリゴン表裏2枚をゆっくり重ねると、だんだん黄色になることを確認した)。
ふと、実機ではどうなるのだろう、と思って実機で動かしてみると、何の問題もなく影が表示されて、力が抜けた。
Android SDKで動作がおかしかったら、まずは実機の動作を確認することだと思い知った。

・glMaterialfv()の引数はGL_FRONT_AND_BACKでないと反映されない
GL10#glMaterialfv()の引数のfaceにGL10.GL_FRONTを指定すると、ambient colorやdiffuse colorの設定が反映されなかった(シミュレーター、実機共)。まさかそれが誤りだとは思わなかったので、そのことが原因だと特定するのに時間がかかった。何なんだろうこれは。
って、OpenGL ESの仕様なんだな。glGetError()すればGL_INVALID_ENUMが返されるのですぐにわかるのだが、glGetError()の値をこまめに見る習慣が無かったのが致命的な敗因だった。

・GLSurfaceViewのデフォルト設定ではステンシルバッファが存在しない
そのこと自体は簡単に試してみればすぐわかることであり、android.opengl.GLSurfaceViewのreferenceにも

By default GLSurfaceView chooses a EGLConfig that has an RGB_888 pixel format, with at least a 16-bit depth buffer and no stencil.
とあるので、setEGLConfigChooser()すればいいこともすぐにわかる。
そこで、
setEGLConfigChooser(8, 8, 8, 8, 16, 8);
とすると、実機ではステンシルテストが動くようになるが、シミュレーターではステンシルバッファがサポートされていないので、
Unfortunately, AndroidTestP1 has stopped.
と出てアプリがクラッシュする。シミュレーターでは、ステンシルバッファ付きのEGLConfigが得られないことが原因である。その場合はステンシルバッファ無しのEGLConfigで継続実行するようにしようと思ったが、setEGLConfigChooserの呼び出しをtry/catchで括ってみても、ここですぐにeglChooseConfig()によるEGLConfigの検索が行われる訳ではないので、ここでは例外が発生しない。setEGLConfigChooser()はsetRenderer()より前に行わないといけないとなっているので、実際に例外が発生してから引数を変えてsetEGLConfigChooser()をやり直す訳にもいかない。
実機で動作すればそれで良い、という人も居るかも知れないが、シミュレーターで全く動作確認できなくなるのは不便である。何より、筆者は普段Androidの実機を所有しておらず、基本的にシミュレーターしか使わないのである。
すると、EGL10に依存するGLSurfaceView.EGLConfigChooserインターフェースを、ステンシルバッファを有効にするためだけに実装しないといけないことになる。
…という結論に達するまでに、結構時間が掛かった。
GLUTなら前回のエントリーに書いたようにglutInitDisplayMode()の引数にGLUT_STENCILを加えれば済む所、GLSurfaceViewだとEGLを呼び出すコードを自分で用意しないといけないとは、面倒である。自由度が高いことに、実用的なメリットはあると思うが、すっきりしない。

・GL10クラスにglGetMaterialfvメソッドが無かった
COLOR_MATERIALを有効にするとambientやdiffuseが変化してしまうので、後で戻す為に現在のambientやdiffuseの値を取得しようとしたら、glGetMaterialfvメソッドが無かった。GL11クラスにはあったが、GL10のインスタンスからGL11のインスタンスを取得する方法が見つからず、ネットで検索するまで、単に(GL11)でキャストすれば良いとはわからなかった。

Javaでは、AWTのGraphicsクラスのインスタンスを(Graphics2D)でキャストしてGraphics2Dのインスタンスを得るようなことがよくあるが、クラスの継承関係からするとGraphics2DがGraphicsのサブクラス、GL10とGL11についてはGL11 implements GL10であり、いずれもサブクラスへのダウンキャストなのだが、そうするのが正しいことは何を見ればわかるのだろうか?(一般にスーパークラスへのキャストは可能だがサブクラスへのキャストは可能とは限らない)

・例外発生時の解析手段がわからない
試行錯誤中に誤ってnull参照するコードを埋め込んでしまった時、デバッガで例外をキャッチして停止させても、どこでnullアクセスしたのかさっぱりわからず、原因コードを特定するのにすごく時間を取られた。EclipseのDebugモードでは、
(Suspended (exception NullPointerException))

(Suspended (exception RuntimeException))
と出るだけである。画面上で"Unfortunately, AndroidTestP1 has stopped."と出るだけより1つは情報が加わるが、それでピンと来ることは困難であり、結局、Log.dやSystem.out.printlnやtry/catchを使いまくって原因箇所を見つけるのでは、何の為にデバッガを使ってるのかわからない。
まさかAndroid SDKはそういうものなのだとは思わないが、printfデバッグでもすぐ見つかるだろうと思っていたので、Android SDKでのまともな解析手段を探す気力は起きなかった。しかし、結局数時間調べる羽目になって、後悔すると共に、二度とAndroidでOpenGLしたくないと、二度くらいは思ったと思う。

・視点を動かしたので、トラックボール風の回転動作を変更する必要があった
今回、ついカメラ位置を自動的にオブジェクト中心に回転するようにしてしまったが、そのせいで、前回Android用に作成した、タッチドラッグ(スライド?)による回転動作がそのままでは使えなくなり、それを視点の回転に対応させる方法にかなり悩んでしまった。

画面上のドラッグ操作をトラックボールの回転操作のように扱うのは、下の図の手前の矩形のように、画面が球面上の(x,y,z)=(0,0,1)の位置(手前)に、画面の上方向がY軸が上になるように接しており、画面上のドラッグはその球面上をなぞっているものとして、ドラッグ開始位置と終了位置からトラックボールの回転軸と回転角を計算することによって実現していた。
SVG image
これは視点がZ軸上にある前提によって成り立っているものであり、視点が変わると、上の図の左上の矩形のように、画面がそちらに張り付いているものとして計算しないといけない。

最初は、一方の画面の法線をもう一方の画面の法線に移す回転軸と回転角度(クォータニオン)を求めて、ドラッグの開始終了位置から求めた回転軸と回転角度にそれを掛け合わせるか何かして回転を合成すれば良いのではないかと思ったが、どうしても2つの画面の縦軸同士、横軸同士を対応付ける方法がわからなかった。
ドラッグの回転軸を移すだけでは、画面右方向へのドラッグが画面右方向へのドラッグとして保たれるとは限らないので、少なくとも、画面間の法線を移す回転軸と回転角度の他に、法線を移した後に、画面の上が画面の上に対応するように画面上で回転させる角度が必要なのである。
この文章を書いている途中に、またちょっと考えればできそうな気がしてきたが、ドラッグ回転軸を求めてからの変換はクォータニオンベースでできないと意味が無いこともあるので、またドツボにはまる気がする。もしもう一度頭の中がクォータニオンまみれになることあったりしたらついでに考えてみることにする。

今回は結局、ドラッグの開始位置と終了位置をもう一方の画面に移動してから、ドラッグの回転軸を求めるようにした。上を+Y方向とする画面上の点(x,y,1)を、上をup=(upx,upyupz)、手前をeye=(eyex,eyey,eyez)とするもう一方の画面上の(x',y',z')に移動させる変換行列Mは、(1,0,0)がupとeyeの外積、(0,1,0)がup、(0,0,1)がeyeに変換されることから、
[up×eye up eye]
だとわかる。行列の積やsin()/cos()も無いので計算量が少ないし、シンプルでわかり易いので、実用的にはこの方法が最適なのかも知れない。

・意外と遅くなった
前回パソコンで描画したのと同じポリゴン数では、極端に遅くなった。PCでは15fpsで動かしてもCPU負荷は3%程度なのだが、Androidタブレットでは3fpsくらいしか出なかった。今回使用したAndroidタブレットのCPUやGPUは不明だが、ベンチマークテストではNexus OneよりCPU性能も3D性能も遥かに上回る機種である。Androidタブレットは、画面の見た目がパソコンのようでも、やっぱりパソコンとは処理速度が2桁くらい違うのだろうか。

影を付けるのをやめると速くなるので、ライティングによって影に着色してるのが重いのかと思ったが、ライティングを無効にして影描画してもあまり変わらなかった。
カリングを有効にしたり、depth testを有効にしたり無効にしたりしても体感的には変わらなかった。つまり、GPUによるポリゴンの着色処理に時間が掛かっているのではなく、depth testや表裏判定に至る前の処理に時間が掛かっているのだと思う。

ポリゴン数を増減すると見た目にはっきりと速くなったり遅くなったりするので、ポリゴン数に依存する処理に時間が掛かっていることはわかった。従って、例えばシーングラフを作っている為に、1回の描画で何度もDrawElementsしていることは問題ではなさそうである。

OpenGL ES 1.1ではvertex arrayが使えないので、glVertexPointerやglNormalPointerを使って頻繁にvertex bufferを切り替えているが、このやり方がまずくて不必要に遅くなっている可能性が高い。現在のコードはGL10クラスを使ったサンプルコードを流用しているが、GL10クラスにはBindBufferもBufferDataも無い(GL11やGLES11にはある)ので、vertex bufferのデータがGPUに転送されておらず、描画する度に全頂点のデータがGPUに転送されている気がするのである。

See more ...

Posted at 22:38 in PC一般 | WriteBacks (0)
WriteBacks

Sep 02, 2013

[OpenGL] 平面への投影変換で影を付けてみる

OpenGLでオブジェクトに影を付けようとして、最初に思い付いた方法は、地面に投影する変換行列を作って、同じオブジェクトを黒で描画することだったが、それが意外と難しかった。

そこで調べてみたが、オブジェクトへの影の付け方には色々な方法があるようで、影オブジェクトを別に作成する方法を除くと、大体次の3通りが多いようだ。
・変換行列による平面への投影
・シャドウボリューム法
・シャドウマッピング法
正確に説明できる自信が無いので、説明は省く。とりあえずわかったのは、簡単に実装できる方法は無いということだった。
平面上にできる影であれば、やはり行列による投影変換で描画するのが最も簡単なので、引き続き、納得できるまで取り組むことにした。

・結果
object drawn with shadowobject drawn with shadowobject drawn with shadow
ソースコード

光源の位置をL:(Lx,Ly,Lz,1)とすると、任意の3次元座標を、法線(a,b,c)が光源側を向く平面ax+by+cz+d=0に投影する行列は、次のような行列であることは、色々な所に書かれている。
shadow matrix
一応、これを筆者なりに求めてみる。

L=(Lx Ly Lz Lw)T : 光源
P=(x y z 1)T : 物体の位置
P'=(x' y' z' 1)T : 平面 ax+by+cz+d=0 に投影されたPの位置
N=(a b c d)T : 平面のパラメーター、但しNT L > 0(法線(a,b,c)が光源側を向くことより)

とし、P' = M P を満たす行列 M を求めることを考える。
P'はLとPを含む直線上にあるので、スカラー媒介変数 t を用いて
P' = L + t(P-L) --- (1)
と表せる。P'は平面上にあるので、
NTP' = 0
成り立つ。これより、
NT P' = NT L + t NT (P-L) = 0
なので、t = - NTL / (NT(P-L)) とわかる。NTL > 0なので、
t = NT L / (NT(L-P))
としておく。このtを(1)に代入し、両辺をNT(L-P)倍すると、
NT(L-P) P' = NT(L-P)L + NTLP - NTLL
= NTL P - NTP L
= NTL P - L NTP ※(NTPはスカラーなのでこのように入替可能)
= (NTL - LNT) P
となる。ここで、P'の4つ目の要素をscale factorだとすると、P'とそれをスカラーk倍したk P'は同じ位置を表すので、P'とNT(L-P) P'も同じ位置を表している。従って、Mは NT(L-P) P' = M P を満たすMでも良いので、Iを単位行列として
M = (NTL I - LNT)、つまり
shadow matrix
と求まる。

この行列をmodel view matrixに掛け合わせて、描画した地面に重ねて影を描画すると、影にノイズが出たり、全く影が表示されなかったりすることがある。
object drawn with shadow
少しでも影を浮かせると正常に表示されるが、全く同じ位置に重ね書きしようとすると、depth値の丸め誤差の都合で、所々に地面より向こうだと判断されてしまうフラグメントが発生するのが原因である。(Zテストで落とされるので、ブレンディング描画にしても解決しない。)
地面と全てのオブジェクトの影を先に描くことが可能なら、depth testを無効化して影を描画することも考えられるが、こういう時はpolygon offsetを設定するのがOpenGLの定跡であるので、今回もそのようにした。

glPolygonOffset(-1, -1);
glEnable(GL_POLYGON_OFFSET_FILL);
PolygonOffset()の引数の意味は難解なのだが、大抵の場合、少し向こう側ということにするなら(+1,+1)、少し手前ということにするなら(-1,-1)とすれば良いとされている。
このソースコードでは、手前に見える(地面に隠されない)はずの影を描画するための設定なので、(-1,-1)としているが、逆に地面に対してPolygonOffset(+1,+1)としても良い。

影を真っ黒で描画してしまうと、環境光で多少は照らされるはずの地面が全く見えなくなって不自然なので、影は半透明の黒で、つまり多少は地面が透けて見えるように描画したい。
その為には、αブレンディング描画をすれば良い。

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
これだけでオーソドックスなαブレンディングが有効になる。

影をαブレンディング描画をすると、平面に投影したポリゴンが重なると、影が2重になってしまう。
object drawn with shadow
このオブジェクトはいくつかのパーツを重ねて描画しているので、パーツ毎の影が重なるとこのようになる。
物体が半透明でない限りは、これは不自然である。これを防ぐには、ステンシルバッファを使うのが最善だと思う。
OpenGLのステンシルはマスクのような役割をし、ステンシルテストをパスしたフラグメント(viewport上のピクセル)を描画しながら、描画した位置のステンシル値を更新することができる。
例えば、このようにする。

glEnable(GL_STENCIL_TEST);
glClear(GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_NOTEQUAL, 1, 1);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
描画をステンシル値が0のピクセルに限りながら、描画したピクセルのステンシル値を1に置き換えることにより、どのピクセルも2回描かれないようにするものである。

無限に広がる平面でなく、有限の地面を描画する場合、影が地面をはみ出すと不自然である。影が地面をはみ出さないようにするには、地面が四角形であればOpenGLのclip planeを無理矢理使う方法も考えられるが、今回はこれもステンシルテストで解決した。

glEnable(GL_STENCIL_TEST);
glClear(GL_STENCIL_BUFFER_BIT);
glStencilFunc(GL_ALWAYS, 1, 1);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
/* 地面の描画 */

glStencilFunc(GL_EQUAL, 1, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
/* 影の描画 */
glDisable(GL_STENCIL_TEST);

地面を描く時にステンシル値を1に置換し、影の描画をステンシル値が1のピクセルに限りながら、影を描画したピクセルのステンシル値を+1することにより、地面がある場所のみ描画するのと影が2重に描かれないのを実現している。

なお、影を描画する際にはdepth maskによってdepth bufferへの書き込みを停止するのが、OpenGLにおける影描画の定跡である。

glDepthMask(GL_FALSE);
これによって、depth buffer上で影の位置が地面より少し盛り上がるのを避けることができる。
そのためにも、上記のpolygon offsetの件は、地面をPolygonOffset(+1,+1)して描画するより、影をPolygonOffset(-1,-1)して描画する方が適切だと思う。

See more ...

Posted at 22:24 in PC一般 | WriteBacks (0)
WriteBacks

Aug 09, 2013

iモード棋譜再生アプリを更新し続ける

明後日は、一昨年から参加している将棋大会である。
準備不足だった昨年は散々な内容だったので、それ以来、ちょっと本気で将棋に取り組もうかと思い、筆者が所有するドコモのガラケーで棋譜を持ち歩けるようにiモードアプリを作ったりしたものである。全ては明後日のためであった。
…と言いながら、結局、この1年間も、筆者の脳には十分に準備ができなかったので、やっぱり来年に向けて準備することにする。

自分がどうすれば強くなるかはわかっている。まずは、自分の戦法を決めて、相手がどう指してきても対応できるように定跡を覚えることだ。集中して先を漏れなく読むことも苦手なので、ある程度時間の長い将棋を指して読む訓練をすることも必要だが、序盤がまともに指せず、すぐに崩れると、同じくらいの棋力の相手とじっくり中盤を指す機会も得られない。
しかし、筆者は幼少の頃から、勝つことよりも奇抜な手を考えて楽しむことを目的に将棋を指してきたので、勝つ為に勉強をする習慣が無いのである。定跡本を読んでいても、すぐに飽きて、持ち時間を極端に短くできるYahoo!将棋での早指し対局に走ってしまう。
そもそも、筆者は昔から、覚えるための勉強が苦手なのである。おそらく、記憶力が悪く、覚えようとしても覚えられないから、覚える作業が嫌いになったのだろう。

そういうこともあって、1年前に自分の携帯電話用の棋譜再生アプリを作ったのである。覚えるべき定跡や棋譜を集める作業を怠っていたので、予定通りの準備はできていないが、暇な時にいつでも携帯電話で定跡の盤面やプロの棋譜を見ることは、思ったよりも学習効果を実感できることだった。同じ定跡局面を3ヶ月くらい繰り返し見ていても、最初は気付かなかったその手の意味に気付くなど、時々新たな発見があるものである。

今やすっかり、このDoJaアプリが動くことのみによって、ガラケーP-01Cが手放せなくなった。このアプリが動く限り、スマホに買い換える気がしない。無い物は作れば良いのである。DoJa万歳。
従って、この自作のiモード対応棋譜再生アプリをメンテナンスし続けることにした。

■変更内容

  • 異なる手順からの同一局面の選択肢を結合できるようにした。
    ▲2六歩△3四歩▲7六歩 で始まる棋譜の次の手が、▲7六歩△3四歩▲2六歩と辿ると選択できない問題への対策。
    携帯電話でiアプリ起動時に同一局面を検索すると時間がかかり過ぎるので、棋譜中にラベルを付けて同一局面として扱うことを指定できるようにした。
    結合先の局面に至る手の後ろに「#label 番号」を付け、同一局面に至る手の後ろに「#merge 番号」を付けると、双方の次の手がどちらからでも選べるようになる。
    例:
    ▲2六歩
    △3四歩
    ▲7六歩 #label 1
    △4四歩

    ▲7六歩
    △3四歩
    ▲2六歩 #merge 1
    △8四歩

    こうすると、どちらから辿っても同じことになり、4手目の選択肢として△4四歩と△8四歩の両方が表示される。
  • 次の手が9種類以上ある場合に、親ノードを分ける(表示されている以外の選択肢を選ぶためには一手戻して同じ表記の別の選択肢を選ぶ必要がある)のでなく、"9"を押すと次のページに移動して9番目以降の選択肢が表示されるようにした。
  • 同じマスに移動できる駒が複数ある場合の移動駒特定の不具合修正
    • 成れる駒が1つしかないのに「成」で特定できなかったのを修正
    • 「左上」とかで左側に上がらない選択肢があると特定できなかったのを修正
  • "*"や"#"で複数の選択肢があるノードに戻った時に前回の選択を表示するよう変更

■スクリーンショット(エミュレーターの画面)
screen shot
i-mode用ダウンロードページへのリンク
■ソースコード
 iKifuPlayer.java
 Board.java
 KifTree.java
■棋譜データの例
 Kifs.txt

See more ...

Posted at 17:12 in Java | WriteBacks (0)
WriteBacks

May 06, 2013

AndroidでOpenGLしてみた

GW直前に運良くAndroid端末を借りることができたので、このGWは以前作ったOpenGLのテストアプリをAndroidに移植するのに費やした。

・実行画面(シミュレーター)

ドラッグして離すと、反対方向に回る。

・Android用実行ファイル
AndroidPolygonSphereTest.apk

・ソースコード
AndroidPolygonSphereTest.tgz
Android SDKの"ApiDemos"(Eclipseで"Android Sample Project"を作成すると選択できる)の中のGLSurfaceViewを使用したサンプルコードに毛を生やした程度のもの。
Activityがどういう状態になったらどうすべきか等のAndroid特有の制御は全く理解していない。
見よう見まねで書いてみたら動いた、というだけのものである。

・コンパイル方法
Eclipse+Android SDKで"Android Application Project"を"Blank activity"付きで作成して*.javaを上書きして"Run As"->"Android Application"でbin/に.apkファイルができる。

See more ...

Posted at 19:14 in PC一般 | WriteBacks (0)
WriteBacks

Apr 03, 2013

Mesa+GLUTでもシェーダーを使ってみた

GLUTを使うことによって移植性を確保しながらOpenGLのプログラム作りの練習を進める中、VMWare+FreeBSD+Mesa 7.4.4の環境でもなんとかOpenGLのシェーダーが動くことがわかったので、続いて、シェーダーを使って鏡面反射っぽいことをしてみる。

・ソースコード
polygon_sphere_test4.c

・コンパイル方法
前のエントリー参照

・実行画面

ちょっとわかりづらいが、画面中央にある球に、周囲の景色が映り込んでいるつもりである。今回の環境マッピングはcube mappingで行っているので、景色の背景画像は巨大な立方体の面の内側に貼られている感じになっている。

背景画像を変えたりもしてみたが、やっぱりわかりづらい。

床が格子模様、天井が同心円模様、側壁が波模様のつもりである。
きっと、意味不明な絵だからであろう。

絵心が無い癖に、プログラムで背景を生成しようと思ったのが間違いだった。

・操作方法
画面上をドラッグすると、背景が回転し、球への映り込みも同時に変化する。
'c'を押すと、背景画像が切り替わる。

GLSLによるshaderのコードの作成には、「Win32APIによるOpenGL 3Dプログラミング 」(工学社)という本を多分に参考にした。
Vertex shaderのコード(initShader()内のvertexShaderSource[])にmatrixが増えたので、後で読む為にそれぞれの意味をメモしておく。

modelViewMatrix
オブジェクト座標系の各頂点の法線ベクトルを回転させて視点座標系にするためのModel-View変換行列
参考:OpenGL 2.1 Spec.のFigure2.6
viewTransposeMatrix
視点が原点から離れている分、平行移動させる行列
視点から物体の頂点までのベクトルを得るのにこれを使用し、そのベクトルを法線ベクトルで反射させて、環境マッピングのベクトルを計算している
modelViewProjectionMatrix
Z軸方向の必要な範囲を[-1,1]に収めるための、いわゆるProjection Matrix
glFrustrum()で得られる行列をそのままshaderに引き渡すのに使用している
environmentRotateMatrix
環境を回転させる行列
環境マッピングの参照ベクトルを回転させるのに使用している

Shaderの記述に用いるプログラミング言語であるGLSL(OpenGL Shading Language)のバージョンは、1.5以降と1.2とではかなり異なり、1.2は時代遅れのようである(参考書籍では4.0)が、筆者の手持ちのMacOSX 10.7+GLUTの環境では1.2しか使えないようであるため、今回はGLSL 1.2で記述している。それでも、今回のようにtextureCube()でcube mappingはできるし、反射ベクトルを求めるreflect()や屈折ベクトルを求めるrefract()も使えるので、十分に使い勝手があると思った。

前回のVAOに続き、今回も、glUseProgram()等のOpenGL 2.x標準のshader APIをOpenGL 1.3相当のMesaで動かす為に、glutGetProcAddress()による拡張機能のリンクを使いまくって、何とかOpenGL 2.1用のコードをそのまま動かすことに成功した。
しかし、それぞれの拡張APIとOpenGL 2.x以降の標準APIとの違いや、拡張APIの中でも、名前の後ろに"ARB"が付いているAPIと"EXT"が付いているAPIとの違いとかは全く調べていない。ただ、試行錯誤したら動いたというだけである。
1つ分かったのは、glUseProgramをglUseProgramObjectARBで代用しているが、

glUseProgram(0);
(shader program不使用への切り替え)のつもりで
glUseProgramObjectARB(0);
とすると異常動作するようである。幸い、今回はglUseProgram(0)しなくて済んだので、MesaではglUseProgram(0)を無処理に置き換えて回避した。

一応、Mesaのソフトウェアレンダリング環境でも動作したが、グラフィックハードウェアでユーザープログラムを処理する為の仕組みであるshaderをソフトウェアで処理するだけあって、極端に遅い上に、表示が汚い。上の画像はMac OS X上の実行結果であるが、同じMacでVirtualBox+FreeBSD上で実行すると、次のようになる。

ソフトウェアレンダリング環境でも動くようにシェーダーを作るというのはあまり意味が無いと思った。

See more ...

Posted at 18:50 in PC一般 | WriteBacks (0)
WriteBacks

Mar 06, 2013

VAOでテキスチャーも貼ってみた

続いて、vertex arrayにテキスチャーの頂点座標も含めてみる。

・ソースコード
polygon_sphere_test3.c

・コンパイル方法
前のエントリー参照

・実行画面

例によって、ドラッグで回転する。

前回は頂点座標の配列と法線ベクトルの配列を別にして、それぞれのVBOを作るような形にした(法線ベクトルは不要になったのでコメントアウトした)が、今回は頂点座標と法線ベクトルとテキスチャー座標を1つの配列にまとめて、VBOも1つにした。つまり、

struct {
 GLfloat vertex[3];
 GLfloat normal[3];
 GLfloat texCoord[2];
};
こういう型の配列である。こうすると、それぞれを別の配列にするのと比べ、少なくとも1つの頂点の処理中は頂点座標も法線ベクトルもテキスチャー座標も同じキャッシュラインに乗る確率が上がるので、CPUやGPUのメモリキャッシュの効率が上がると言われている。

See more ...

Posted at 22:30 in PC一般 | WriteBacks (0)
WriteBacks

Feb 25, 2013

OpenGLのVAOを使ってみた

前回のコードは、一連のコマンドのコンパイルもせず、vertex arrayやbuffer objectも使わずに、描画の度に毎回大量の命令を行っており、大変非効率なので、vertex arrayを使って少しマシにする。

・ソースコード
polygon_sphere_test2.c

・コンパイル方法
前のエントリー参照

・実行画面

※今回は処理負荷軽減が主旨であり、ポリゴンの各頂点を使い回すため、各頂点の法線ベクトルは頂点座標と同じ値にしているので、表面が前回よりツルツルになっている。

Vertex array初体験の筆者にとっては、vertex arrayとbuffer objectとvertex array object(VAO)の関係がややこしい。今週理解した限りでは、おそらく、次のような感じだと思う。


Vertex array
頂点座標データを含む配列。"Vertex"と言いながら、頂点の法線ベクトルや頂点の色情報やテクスチャーの座標データなど、いくつかの決められた種類のデータをvertex arrayにすることができる。データの種類と、種類によっては1要素当たりのバイト数を指定して使う。

Buffer object
H/Wが直接アクセス可能なメモリ領域に置くことができるオブジェクト。任意のデータを含めることができる。Vertex arrayを入れることもできる。
Vertex buffer object(VBO)はvertex arrayを入れたbuffer objectのこと。

Vertex array object(VAO)
各vertex arrayの実データの位置(buffer object内でも良い)と状態を保存するオブジェクト。これにより、複数のvertex arrayとbuffer objectを一発で切り替えることが可能になる。OpenGL 3.0以降で標準サポートされているが、それ以前のバージョンの環境やOpenGL ES 1.1の環境でも拡張I/Fとしてサポートされていることが多い。

今回、一旦VAOやVBOを使わずにglVertexPointer()等のvertex arrayのI/Fだけを使ってglVertex3d()やglNormal3d()の嵐を解消したが、いかにも中途半端な感じになった。負荷軽減の目的ならVBOを使わないと意味が無く、VAOが使えるならその方がすっきりするので、結局、頂点座標と法線ベクトルをVBOにしてVAOにまとめた。

glDrawElements()を使うには頂点のインデックスの配列(element array)(vertex arrayの文脈ではindex arrayは色番号の配列のことなので別物) が必要で、どうも頂点のインデックスの配列はVAOに登録できないようなので、glDrawElements()を使うにはglBindVertexArray()するのとは別に、 glBindBuffer()で頂点のインデックスの配列を有効にすることが必要になるようだ(上記ソースコードのdrawSphere()参照)。
なので、座標も法線も全く同じ頂点を使い回さないなら、glDrawElements()でなくglDrawArrays()を使う方がすっきりすると思ったが、連続平面ならポリゴンの各頂点を3回使うのが普通だろうと思い直して、やっぱりVAO+element arrayのbuffer objectという構成にした。

See more ...

Posted at 01:13 in PC一般 | WriteBacks (0)
WriteBacks

Feb 21, 2013

GLUTでOpenGLしてみた

Java3Dには何の不満も無いのだが、Mac OS Xには初めからGLUTが入っていることを知ったので、ちょっと、OpenGLも試しておくことにした。

・ソースコード
polygon_sphere_test1.c

・コンパイル方法の例
Mac OS X 10.7.5で成功したコマンド

gcc -framework OpenGL -framework GLUT polygon_sphere_test1.c

FreeBSD 7.3 + libGL7.4.4 + libglut-7.4.4 で成功したコマンド

gcc -g polygon_sphere_test1.c `pkg-config --cflags --libs glut`

・実行画面

球面の1/8を均等な三角形に分ける方法で球のポリゴンを作成したものである。
マウスでドラッグすると球がその方向に回転し、ボタンを離すと逆方向に少し回転して止まる。
Sキーで、ワイヤーフレーム表示と塗り潰し表示が切り替わる。

回転軸の合成と、ポリゴンの頂点座標の算出に用いる球面線形補間(Slerp: Spherical linear interpolation)には、クォータニオンを使っている。クォータニオンの演算やSlerp等については、参考になる多数のwebページがあるので、説明を省く。GLUTやGLUにはクォータニオンに関する関数が見つからなかったので、適当に作った。特殊なことはしていないと思う。

ドラッグによる回転は、クリックした位置に半径1のトラックボールが接しているような感じに、トラックボールの中心からクリック位置へのベクトル(x,y,-1)とドラッグ先のベクトル(x',y',-1)との外積を回転軸として行っている。カーソル位置に表示されている球面上の座標を求めるのと比べるとかなり手抜きな方法であるが、まあまあそれらしく動いているので良しとする。

描画の度に全ポリゴンの頂点座標を再計算してVertex3dコマンドで転送しており、とても効率が悪い。VAOやVBOを使って頂点データを保存しておくか、せめてGenLists, NewList(GL_COMPILE)を使ってコンパイルしておきたかったが、1つのシンプルな実装例として、一旦公開することにした。

See more ...

Posted at 19:04 in PC一般 | WriteBacks (0)
WriteBacks

Feb 03, 2013

Safariで複数のURLを一度に開く方法

MacのSafariで、テキスト表示されている50個くらいの写真のURLを、全て一発で開きたいと思ったが、適当な方法がすぐにわからなかった。

筆者はWindows XPではLunascapeを愛用しており、Lunascapeでは、メニューバーにある「クリップボードで開く」を実行すると、クリップボードに複数のURLが含まれていれば全て抽出して開いてくれるので、造作もないことである。

URLの先が全てJPEGファイル等の画像ファイルであれば、それら全てを<img>タグで表示するHTMLを作るという手があるが、今回はURLの先が全てHTMLファイルであった。

そういえば、Internet Explorerならどうするんだろう、と探してみたら、わからなかった。通常のWebブラウザーでは無理なのか…と思ったが、しつこく探してみたら、いい方法が見つかった。

1. メニューバーから「ブックマーク」→「全てのブックマークを表示」
2. 「ブックマークフォルダを追加」
3. URLを含むテキストをコピー
4. 追加したブックマークフォルダを開いて(フォルダーアイコンの横の三角が下向きの状態にして)ペースト
→ テキストに含まれる全てのURLがフォルダーに追加される
5. フォルダーのサブメニュー(右クリックやトラックパッドに指2本タッチ+クリック) から「すべてをタブで開く」

See more ...

Posted at 21:12 in PC一般 | WriteBacks (0)
WriteBacks

Jan 27, 2013

MVCパターンの適用限界を考える(5)

MVCの適用限界について考えることをテーマにして書き続けてきたが、MVCとその変遷が複雑すぎて、それらを考察し終えるのにはまだまだ時間がかかりそうである。
なので、一旦、現時点での筆者のMVCの適用限界に関する見解をまとめることにした。

ここでは、ドメインモデルとビジネスモデルをまとめて「ビジネスロジック」と表現する。以下の「ビジネスロジック」は「ドメインモデル」に置き換えても同様である。

●1. MVCパターンは、現代のGUIフレームワークには適さない
ウィンドウやウィジェットを入れ子構造で配置し、ボタンやリストなどの表示部品を置き、それらが押された時の動作などを各表示部品に直接関連付け、マウス操作などのイベントがアクティブな子ウィジェットから親ウィンドウへと伝搬される、AWTやSwingや*/TkやGTKやVBやVCなど、現在開発に使用されている代表的なGUIフレームワークでは、MVCで言う所のControllerが階層構造のViewに分散しており、ControllerはViewの一部という形になる。それにより、ControllerがViewに依存する形となり、例えば、CをそのままにしてVを追加/変更できるという前提が崩れる。

いわゆるGoF本に書かれているMVCでは、originalの(Smalltalkの)MVCとは異なり、ControllerはViewの一部だと割り切り、presentation logicとdomain modelを分けて多対1の関係とすることに絞っている為、現代的なGUIフレームワークにも適した考え方となっているが、それはMとVだけで成り立つ概念であり、MVCと呼ぶ意味が無いものである。
なお、MVCはGoF本の発刊時点で既にこの理由でobsoletedであった為、デザインパターンの元祖として紹介するだけで、同書の23のデザインパターンとは並列にされなかったという説もある。

これに関して、参考リンクに挙げたWebページから、いくつか興味を引かれた文章を引用する。

[3]のコメント欄より:

if you need to send an onMouseOver event to a controller instead of just changing state on the button in the view, what do you gain?
...
Only things that change application states (be they persistent, session-specific, or application-wide) should be sent to a controller

(Viewしかわからない)MouseOverイベントをControllerが受けてViewに配送することに何の意味があるのか?(Viewが単独で処理すれば良いのではないか?)という疑問である。Modelの変更に繋がり得るイベントだけがControllerを通れば良い、というのも同感で、そこまできちっとViewからControllerを切り離す必要性は無いと思う。

[6]のコメント欄より:

MVC, and StateMachines are orthogonal concerns (depending on the app each tier may have it's own StateMachine). The differentiation typically is in MVC the model state is persistent, and survives a many to one relationship (e.g. a bar and line graph both using the same data.) where as state machines are useful for runtime states.

MVCと状態遷移モデルは直交する概念で、状態遷移モデルの状態は全てMVCのModelの状態であるべきだということにはならず、永続的でV-Cとは多対一の対応の関係にあるものがModelの状態であり、ViewやControllerがそれぞれ実行中の状態(及び遷移モデル)を持っていても良い。
GUIフレームワークでは、Viewの状態とControllerの状態が分離されない(全て同じ箇所に書けてしまう)ので、意識的に努めて行わない限りは、ViewとControllerが分離されない。というか、現実的には(単純な例題でもない限り)ViewとControllerとの状態の分離はやり切れないと筆者は思う。

同じく[6]のコメント欄より:

Some definitions of MVC call for a single controller, supporting multiple models, each with a view. Obviously, Flash apps almost never work like that - Flash MVC is really distributed MVC, often different views have their own controllers, and views are all managed by a master state management controller.

Adobe FlashのアプリをMVCに当てはめると、M:V:Cの関係が通常の多対多対1の関係ではなく、ViewはそれぞれControllerを持ち、それとは別の巨大なControllerによって制御される、"distributed MVC"である。

なお、Cocoa MVCでは、"Responder"(Controllerへの参照のようなもの)と"Responder chain"(イベントを処理できるハンドラーの検索順序を決めるリスト)という概念によってGUIフレームワークとMVCとを整合させているそうだが、本当にこれによってControllerがViewの構造とは独立の構造を持ち、例えばViewだけが交換可能になるのかどうかは、筆者にはよくわからなかった。(View毎にResponder chainを用意するというのは、View毎にControllerの構造を用意するというのと同じようなものなのでは…)

●2. MVCパターンは、リッチなクライアントを持つWebシステムには適さない
本来、MVCパターンでは、Modelが変化すればViewがその通知を受けて、ViewがModelの状態を取得して表示に反映するものであるが、通常使われるHTTPのような(PUSH型でなく)PULL型の通信手段を使用しているWebシステムでは、Modelの本体がサーバー側、Viewの本体がクライアント側にあるので、Modelの変化に伴ってViewを更新することができない。

Webシステムでよく使用される、J2EEやStrutsやFlexのような、いわゆる”Model 2”のフレームワークでは、サーバー側の構成にMVCパターンの考え方が採用されており、WebシステムでMVCと言えば大体これのことを指す。しかし、クライアント側も含めてMVCパターンにすることをサポートするフレームワークは存在しない。
"Model 2"でも、サーバー側、クライアント側それぞれ別々にMVCパターンを適用することは可能であるが、それはアプリケーションのGUIのみをMVCにするようなものなので、クライアント側をMVCにするメリットは少ない。

クライアント側がHTMLを表示するだけのViewの機能と、HTMLブラウザの機能だけで実現できるフォームやボタン程度のUIであれば、クライアントはViewのみとしてもまず問題にはならないが、AjaxやFlashを用いるなどして、クライアント側のUIをリッチにすることが可能になり、例えばユーザーの入力にリアルタイムに反応してボタンの有効状態が変わるようにしようとすると、それによってボタンが有効になるべきかどうかをキーストロークの度にいちいちサーバー側に問い合わせるか、Modelに相当するビジネスロジックをクライアント側にも含める必要が出てくる。

このような問題をまともにMVCパターンで解決するには、Webシステム全体としての機能をMVCに分割し、Model, Controllerそれぞれをサーバー側とクライアント側に配置し、Model同士、Controller同士がそれぞれ最小の通信量で済むように協調動作することが必要になる。それ自体が容易でないのに加えて、そういう構成をサポートするMVCフレームワークは現存せず、それに既存の"Model 2"のMVCフレームワークが使えないので、サーバー側とクライアント側を合わせてMVCパターンにすることは困難であろう。
ついでに言うと、"Model 2"のMVCと本来のMVCは異なるので、Webシステムで本来のMVCパターンを取り入れようとすると混乱が起こるのではないだろうか。

参考リンクからいくつか引用する。
[1] "MVC As Anti-Pattern"より:

But what if you are building a web application with a rich client?
[snip]
Do you still need an MVC framework?
I think the answer is most likely no.
このページ全体がこのテーマで、論旨がよくまとめられているので、興味のある方は是非読んで頂きたい。
Developers who use modern rich-client frameworks do not have to create controllers to manage events. What a runtime and its frameworks do internally is a different story.
この"rich-client frameworks"は、View内の構成要素それぞれがController(イベントハンドラー)を備える、通常のGUIフレームワークのことを指す。

[3]より:

I’ve come to the conclusion that… drumroll please… MVC is probably not needed for most RIAs.
RIAはRich Internet Applicationsの略で、クライアント側もリッチなアプリケーションであることを意味しており、この記事の所々にある"FrontController pattern"は、その対極に、クライアント側はシンプルで、何もかもサーバー側のFrontControllerを経由する構成のことを意味している。

You often see architectures in which a single controller handles all of the “actions” for the entire application. I see this as a necessary evil, not a “good thing”. The world of the giant switch statement is exactly what we wanted to avoid with OO programming, right?
ここでは、MVCのControllerは巨大なswitch文の塊になりがちであることが問題にされている。例のmost controversialなSkinny Controller, Fat Modelと同時期に呈された、MVCに対する不満である。

[3]のコメント欄より:

I agree, separate model and view is enough, controller sometime is abused
M-Vの分離には意味があるが、Cの分離はえてしてやり過ぎである。

[6]より:

Fact: No one codes Flash using pure MVC.
このページに書かれているのは主に一般論だが、なかなか深く鋭い話で、一読に値すると思う。

[3]のコメント欄より:

In addition, just consider the case (in the real Business world) when you need to deploy a program (which also holds the Controlling) on the client machine
クライアント側にView以外のロジック(ビジネスロジックそのものや、サーバー側のビジネスロジックを扱うController)を含めることについての問題提起であり、一般にクライアント側でのプログラムの実行は難しいので、故にV-Cの分離は重要だと述べられている。確かに考慮すべき事柄ではあり、V-Cの分離が現実的に可能であれば一理あるが、実際にはV-Cの分離に労力を掛けるより、クライアント側でプログラムを実行することに労力を費やす方が、コストが小さく、何よりも自由が利くので、労力に見合うのではないだろうか。

[3]のコメント欄より:

Anyone who justifies MVC as a way of making a web app “like” a desktop application’s MVC architecture is fooling themselves. The internet just doesn’t work that way. I’ve seen IT budgets balloon and schedules run late because of the blind, over-zealous switch to MVC, while chanting “industry standard” to anyone who asks “why?”

●3. MVCパターンは、リッチなGUIには向かない
MVCパターンでは、UIの制御は、必要であればViewに(カーソル位置、スクロール位置等の)表示状態を問い合わせながら、Controllerが主となって行うことになっている。それにより、ControllerはViewの画面構成に依存せず、Viewのみの交換や、表示の異なる複数のViewの追加が可能になるのである。

しかし、そのようなViewの抽象化は、全てのViewに「タイトルバー」と「メニューバー」があり、「メニューバー」は「項目」または「項目グループ」を含む、など、ある程度はUIの形状が決まっていないと困難である。

また、Viewの状態が複雑になるだけ、Controllerも複雑になる。いくら何でも、マルチウィンドウ状態を持つVを、1つのループとswitch文でなるControllerが制御するのは限界があるので、必然的にControllerは分解することになるが、その分解方法はある程度Viewの構造に依存しないと複雑になる。Viewを抽象化し切るのが困難になり、抽象化する労力が効果に釣り合わなくなるのである。ウィンドウマネージャーくらいの機能だとまだ実現可能かも知れないが(それでも相当大変だ)、クリックされた位置がどのような抽象状態の時にどうする、とか、メニュー画面がスクロール可能な場合はどうでページ送りの場合はどうで、とかいうのを、Viewを抽象化してViewがどんな形でも動けるようにするのは至難の業である。

ウィンドウシステムのあるマルチアプリ環境にMVCパターンを当てはめることを想像すると、アプリ毎にMVCがある構造を思い付くのが自然ではないだろうか。アプリの追加/削除まで考えると、仮にMはアプリケーション間で共通のものを使用できるとしても、VやCをも共通にできると思う人は居ないだろう。それができるとすれば、メニュー画面しかないアプリくらいである。つまり、アプリケーションマネージャーを除き、Viewの数だけControllerが存在すると思うのが自然である。1つのアプリケーションでも、巨大になれば構造を分解することになるが、マルチアプリ環境と同じ発想で、ViewとControllerを複数のV-Cのセットに分解する構造にすることを考えることが可能なので、それに行き着くのが自然であろう。GUIが大規模になれば、Controllerの構造がViewの構造に一致する、というより、ControllerがViewの構造に縛られるのは宿命なのである。

このことは、実はoriginal MVCでも少し触れられている。前の記事にも挙げた、"The Model-View-Controller (MVC) Its Past and Present"のP-8 "Tools for Tasks"とP-9 "Tool as a composite"がそれであり、タスク依存のV-CはToolとして独立させれば良く、ToolはToolを組み合わせて構成すれば良いということなので、これは、現在主流のGUIフレームワークを使うと自然とその形に誘導される、GUIをウィジェットの入れ子構造で実現することとほぼ同じことである。これでアプリケーション全体がToolの組み合わせだけで実現することがMVCパターンの範囲に入るなら、現在主流のGUIフレームワークはMVCパターンから逸脱していないことになるが、さすがにアプリケーションのトップレベルがMVCの構成でないのはMVCパターンとは呼べないであろう。また、通常MVCという用語が使われる時に、P-8やP-9の意味を含むことは稀である。

また、GUIが複雑になれば、GUIそのものをMVCに分ければいいという意見が出てくることもあるが、一般にV-Mが強結合だとMVCはうまくいかないことが知られているし、それこそMVCパターンを使うことが目的になり、何の為にMVCにするのかがわからなくなってしまう。

[3]のコメント欄より:

One of the reasons that people build controllers is to isolate the interaction logic from the view, with the goal of being able to swap out the view. The problem with this approach is that with the richer UI vocabulary that RIAs allow, I believe it is not possible to do this.
UIがリッチになると、UIのロジックをViewから切り離すのは不可能だと思うと書かれている。
筆者も、M,CをそのままにVだけ交換可能にするというのは、スキン(画像ファイル)の交換くらいが限界だろうと思う。GUIの見た目が変わって操作が全く同じということはほとんど無いだろう。

●4. MVCパターンは、エディターには向かない
テキストエディターや文書作成アプリや画像編集アプリなどは、MVCパターンへの当てはめ方が難しいアプリケーションの例としてよく挙げられるものである。
何をModelにするべきかという問題ではあるが、デザインパターン、つまり共通の知識としてのMVCの一般的なコンセンサスとして、Modelの状態は永続的なものであることと、ModelはViewに依存しないことがあるが、それからすると、テキストエディターのModelの責務は、文書を保存して読み出すことくらいになってしまう。

人間にわかり易い形、つまりビジュアルなデータ編集ツールの中心部を、ビジュアル抜きに形式化するのは困難である。かと言って、ビジュアルがメインのアプリケーションなら表示状態もModelに含めれば良いかというと、そもそもPresentationとModelが分離される背景には表示系はよく変更されるという前提があり、表示系が変更するとMにもVにもCにも変更が入ってしまうのでは、何の為にMとVとCとに分離するのかがわからない。

また、通常、ControllerとModelとの間のセッション状態(ひとまとまりの協調動作における状態)をModel側に持たせることは好ましくない。C-Mの関係においてはControllerが能動的、Modelが受動的に動作するので、セッションの開始と終了がわかるのはControllerだからである。例えば、テキストエディターの編集中の状態をModelに含めると、同時に編集するデータの数に比例してModelの状態が複雑になってしまう。(セッションの数だけModelのオブジェクトを作成するならさほど複雑にならないが、それはControllerがセッション管理しているのと同じことである)

Original MVCでも"THING-MODEL-VIEW-EDITOR"という分け方があるように、オブジェクトの中身を操作するUIはMVCパターンの中でも特殊な位置付けであり、"EDITOR"はMVCに当てはまらないものの代名詞とも言えるのである。

エディターの他に、MVCの適用が困難な例としては、ゲームがよく挙げられる。エディターと同様に、画面表示がメインのアプリケーションは、ModelとViewの結合が強いので、MVCの分割に向かない。また、HMI(Human-Machine Interface)や感性モデルやバーチャルリアリティーのように、変化しないModelを確立するのが難しい分野にも向かないと考えられる。現実世界のモデル、現実のビジネスモデルとHMIは追究の仕方が異なるので、開発スタイルも異なるはずである。

アプリケーション全体としては直感的にMVCに分けることができても、部分的にMVCに分けるのが困難になる例としては、Modelの実行中の確認ダイアログが有名である。
そのビジネスロジックにおいてその手順中にユーザー確認が必要であったら、その情報はModelにあるべきであるが、ダイアログ画面の表示制御はControllerの仕事である。確認ダイアログ1枚くらいなら、Modelの処理をその前後で分けて、Controllerが前半の処理とダイアログ制御と後半の処理を順に行えば簡単であるが、一連のビジネスロジックの処理中に何枚かのユーザー入力画面が必要だったり、それらが画面遷移モデルを伴ったりすると、問題は一気に複雑になる。あくまでControllerがビジネスロジックを含めない方針にすると、ModelはControllerへの応答だけで次に出す入力画面を伝える必要があり、ユーザーからの入力が不正かどうかをControllerは判断できないので、Modelは不正入力を判断して、場合によってはエラー表示を伴う異常処理を行わなければならなくなる。
ビジネスロジック固有のUIの画面遷移と、タスクやView固有のUIの画面遷移とがあり、View固有としてV-C側に置かれていた画面遷移が、Viewが増えた時にView間で共通のビジネスロジックだと決まると、Modelに移動させる必要が生じたりする。
そもそも、ビジネスロジックがUIを伴っても、その実行時の画面遷移状態はセッション状態であり、Modelの状態としてはふさわしくない。
かくして、UIを伴うビジネスロジックがControllerに置かれるようになり、Modelの存在意義が希薄になり、Controllerがビジネスロジックの塊となるのである。

これも突き詰めると、MVCでは、UIを伴う処理をModelに取り込むのは厄介だということであり、エディターがMVCに向かないのと共通点の多い問題である。

各々のプロジェクトにおいて、何をModelにするかという方針を明確にしさえすれば、そんな問題は起こらない、という意見もあるが、対象が物理モデルやH/Wでも無い限りは、どこまでをビジネスロジックと見做すのかは、それほど一意に決まらない。始めはアプリケーション固有ロジックであっても、それが再利用されるようになって初めてビジネスロジックになることも少なくない。どう考えてもpresentation logic、そのViewでしか使わない固有の処理だろうと思ったものが、実はそのビジネスモデルで共通だったというのは、よくあることである。
UI intensiveなエディターをどうMVCに分解するかというのは、SmalltalkのMVCでも同様の問題がある気がするのだが、筆者はこれについて何らかの解が示されているwebページを見た記憶が無い。筆者は、エディターからModelを迷い無く切り出せるようなModelの定義は存在しないと思っている。

参考リンクからいくつか引用する。

[2]より:

By letting the service layer refer to sessions, I am also creating a Model that is hard to test without having an entire application in place.

ここでは"service layer"はModelの上層部、ControllerやViewから直接呼び出されるI/Fを含む層を指す。Modelがセッションを参照すると、Modelを単体でテストするのが困難になる、と書かれている。

同じく[2]のコメント欄より:

This makes me think of what Misko Hevery was saying in a previous blog post - that the more testable something is, the better it is probably architected. And, the more easily tested it is, the more it likely decoupled from other objects.

One of the huge benefits of separation of concerns is testability. If you've designed your components properly, you should be able to write test cases that can give you some peace of mind.

[4]より

If you are designing a graphics intensive program, like a game, you would probably couple the View and Model classes much more tightly than what MVC suggests. As we can see from the Basic sample application, when programming a very simple application it is common to combine the controller with the view classes.

[3]のコメント欄より:

Things, of course, get tricky when user interaction comes into play. For example, if you need to pop up an alert that asks the user a question before completing the transaction.

[3]のコメント欄より:

If you’re doing a word processing application, is the keypress sent to the controller, added to the document via the model, then the view is refreshed via the controller? Or is the model used to save changed files and the view handles all the live updates to the text?

[6]より:

MVC says that the entire application state is supposed to reside in the model. However, many applications maintain application view state in the controller, and the data state in the model.

MVCではアプリケーションの状態は全てModelに置かれることになっている(Original MVCではある程度のGUIの状態はViewが保持してもいいことは省略されている)が、実際にはControllerがViewの状態を保持していることが多い、ということ。

ControllerにGUIの状態を置かないことが実際には難しいことを暗に言っていると思う。
Original MVCでControllerがViewに表示状態を問い合わせても良いと書かれているのは、当時はGUIの状態は少なく、リスト上の表示範囲やカーソル位置など、View固有の状態だけで表現できるという前提があったから、もっと言うとSmalltalkではアプリケーションの形をある程度決めることができたからそういう設計思想が通用したのであり、ボタンの状態や文字色やパネルの入れ子や不定形なGUIやフォーカスやアニメーションなど、昨今のバリエーション豊富なGUIでは、むしろModelよりも状態数が多いことも少なくない。

●5. MVCパターンは、リアルタイムなUIに向かない
これは、Viewの画面更新はModelの変更完了後になされ、ユーザー入力を受けるControllerがそのままViewの画面更新を同時に行わないことによる。
よくあるMVCの実装だと、Model-Vew間のObserver patternがきっちり作られているため、ModelとViewが非同期的に動作し、Modelに何か変化があったという通知をViewが受けて、ViewがModelに最新の状態を問い合わせるという、Model内のデータの更新と画面の更新とを同時に行うのに比べて無駄の多いシーケンスとなる。Controllerから見ると、もう画面をどう更新すればいいかがわかっていても、イベント配信のスケジューラーに任せる形で、表示更新を後回しにするしか無いことがある。

もちろん、Viewは必ず非同期に動作しないといけないということは無いので、Modelの更新中にViewに更新要求しつつ、即座にViewに実行権を明け渡すような同期処理にすることも可能だが、Modelの更新に同期して都度表示更新するのがユーザーレスポンスが再短時間になるとは限らない。すなわち、Modelの更新がある程度済んでから描画する方が速いケースも少なくない。
一続きのModelの更新が完了したタイミングがわかるのは基本的にControllerなので、必要な時はModelの更新前にViewに表示更新禁止指示を出し、Modelの更新後にViewに表示更新要求を出せば良いのだが、そのような必要が多いシステムであれば、VewをModelのObserverにする意味が無いであろう。

また、MVCパターンでは表示に関するビジネスロジックもModelにあるのが前提(Viewが交換可能なのが前提だから)なので、必然的にViewからModelへの問い合わせが増えることも考えておく必要がある。Modelへの問い合わせが、メソッドの同期呼び出し等、同じ実行コンテキスト内の同期処理であればオーバーヘッドは小さいだろうが、ViewとModelが並列動作していて排他制御が必要だったり、Modelがネットワーク越しにあったりすると、一部のビジネスロジックはViewにも含めるような、MVCパターンから外れる対策が必要になるだろう。

参考文献[5]より:

The first problem area is to deal with setting the color of the variance. This shouldn't really fit into a domain object, as the color by which we display a value isn't part of the domain.
表示する値(ここではvariance)に定性的な意味合いに基づいて色付けする場合にその色の決定処理をどこに置くかという例題で、この文章に続いて、MVCパターンにおける、表示に関するビジネスロジックの扱い方について、深く考察されている。長くなるので詳細は割愛するが、オブジェクト指向言語だとtext fieldのsubclassを作るのが好みだと書きながら、結論としてはMVCの他に"Presentation Model"(View側のビジネスロジックを含める部分)を設けることによって大抵解決できると書かれているのが興味深い。

●6. MVCは絶対的な指針にはならない
人々のMVCパターンの理解がこれほどまでにばらつく理由は、Web系のフレームワークにおいて絶対的な地位を築いた"Model 2"や、かのGoF本に書かれた技術的なロマン溢れるMVCなどの、原典であるSmalltalkのMVCからかけ離れた亜種がSmalltalkのMVCより圧倒的に有名になったことが直接の原因だと思って間違い無さそうである。そのように多種多様なMVCが生まれる原因には、それらのいずれもがMVCを誤解したものだと言われないことがあると思うし、それは、MVCに正解が存在しないからであろう。

Smalltalkの開発中に考案されたMVC理論は、複雑すぎて人々に理解されず、そして、SmalltalkのMVCは現代の進化したGUIを持つアプリケーションにそのままでは使えなくなったので、MVCの原典を繙く人が増えなかったのだと思う。
さらに、ソフトウェアの規模が大きくなったのに合わせてMVCに色々と改良が加えられても、人々が納得する決定版が現れないので、人々のMVCの理解がばらつくのを止められないのだと思う。

人々は、ある理論を理解することを放棄する時、それが全てではないでしょ、という言い訳をする傾向がある。ソフトウェア設計者は、既存の設計が理解できない時、もっとシンプルにできるはずだと言い訳して放棄する傾向がある。ことこれほど広まっているMVCパターンに限って、理解できないという言い訳は敗北と考えて、せめて割り切ってMVCと接することができるように理解に努めてきたが、筆者は、MVCの理論にも適用可能範囲にも絶対的なものは無い、つまりMVCとして絶対不可侵な範囲も無く、こういうアプリケーションに有効と言える対象も無く、ましてやどんなアプリケーションでもMVCパターンに従うべきだということは無いと確信するに至った。

MVCパターンは、ただ闇雲に何が何でもMVCにすれば良いものではない。理論を知らなくても、忠実に守っていれば何かご利益があるような便利な知識ではなく、オブジェクト指向と同様、単なる設計理論の1つであり、設計思想の追求の仕方、設計の突き詰め方のヒントを与えてくれる程度のものである。

[6]より:

Patterns are architectural heuristics. Understanding them should widen your solution spectrum, not narrow it. They provide you with proven ways of solving a specific problem in programming. The reality is that you will rarely encounter that specific problem in the wild, but you will encounter variants of that problem

デザインパターンを理解すると問題解決能力が上がるべきであり、逆にそれに従うことによって問題解決手段の選択肢を狭めるものであるべきでない、という意味だと思う。
筆者は、デザインパターンは将棋の定跡のようなものだと思う。実戦において、定跡と全く同じ局面になることは滅多に無いので、ただ手順を覚えるだけではあまり役に立たないが、似たような局面になることはよくあり、定跡手順の意味を理解していると、大きなヒントになる。
同じ手でも、定跡を知らずに指すのと、定跡手順を知った上で外すのとでは、試行錯誤の質が大きく異なり、その後の勝率に差が出る。定跡を知らなくても、終盤で逆転するとか、力づくで勝ち続けることは可能だが、限界がある。終盤は法則化、体系化できないので、勝率を上げるには終盤力、つまり計算速度を上げることしか無くなる。
同様に、ソフトウェア設計も、コーディング能力に頼るのでは限界がある。それは個人の資質にも依存する職人芸であり、何よりも、習得方法、指導方法の確立が難しいので、伝達及び維持が困難である。この業界は、技術は盗むもの、とか言っていて間に合う世界ではない。だからこそ、アルゴリズムや設計理論やデザインパターンを知ることに価値があるのである。

[6]のコメント欄より:

I only like to talk about MVC now when there is something resembling Smalltalk MVC triads at work and controllers are required to move messages between triads. Otherwise I think the GOF book got it right to not include MVC as a pattern.

When people say they are using MVC I take that to mean they see their application as being organized somehow into three parts that can be worked on separately:

1. data and business logic,
2. presentations of that data, and
3. some sort of event management and propagation system.

I don't really think MVC means much more than that anymore. Consequently its value in discussing real-world designs has become suspect.

[3]のコメント欄より:

One of my main issues with the whole framework/mvc debate is that it has been my experience that one size can not fit all.

どんな規模の開発にも有効なフレームワークやデザインパターンは存在しない、と言い換えても良いと思う。

[3]のコメント欄より:

The most important part of an abstraction is not what patterns it strictly adheres to, but what it provides the programmer in terms of power, maintainability and ease of use.

… and back to the discussion on MVC: my ‘tool’ abstraction encapsulates user input processing, drawing transparent scratch information (for example to display a selection rectangle) and model object manipulation. Is the tool a controller or a view? Could it even be a model (at the application level as opposed to the document level)? The answer: who cares. These labels we use are for architectural communication and education, not as rules that must be adhered to regardless of the problem domain.

A more important question than “do your developers implement design patterns?” is “do your developers *understand* design patterns and spend the time creating, analysing and discussing abstractions that may enhance the design of your application?”

問題の抽象化の目的は、作り易さ、メンテナンス性、扱い易さなどの実際の成果であり、デザインパターンに厳密に従うことではない、と書かれている。
また、デザインパターンを使っているかどうかでなく、デザインパターンを理解して使っているかどうかが大事な問題だ、とも書かれている。

参考文献
[1] MVC As Anti-Pattern
 MVCパターンは、リッチなクライアントを持つWebシステムには適さない、という主旨で、よくまとめられている。MVCパターンの限界を考える出発点として最適だと思う。
[2] A Better Understanding Of MVC (Model-View-Controller) Thanks To Steven Neiland
 MVCにきちんと従うためのわかりやすい指針というか、格言のようなものが考え出されている。
[3] MVC considered harmful
 本文には大したことが書かれていないが、コメント欄の議論は非常に深く、参考になる。
[4] Best Practice Software Engineering - Model View Controller
 簡単なMVCの紹介。1ヶ所、上記の引用部分の記述が非常に鋭いと感じたので、挙げておく。
[5] GUI Architectures
 その筋では有名な、Martin Fowler氏による、Webアプリケーションのフレームワークの進化の歴史が書かれている。必読。
[6] On Design Patterns and Reality
 一般論が多いが、なかなか深く鋭い話で、一読に値すると思う。

See more ...

Posted at 23:46 in PC一般 | WriteBacks (1)
WriteBacks

(1)から読ませていただきました。 よくまとまっていてとても勉強になりました。 ありがとうございます。

Posted by at 03/20/2013 10:34:26 AM