すっかり忘れていました。
マルチプラットフォームにすると困るのがフォントですね。実際は単一のプラットフォームを対象にしてても問題になる厄介な物ですが、なにせ、どのプラットフォームにも(デフォルトでインストールされている)共通したフォントがないです。
吉里吉里のようにノベルゲームを作れるような環境では、書体がプラットフォームで微妙に変わるのはかまわないとしても、字詰めが変わるのは困る話です。
解決法としては以下のような物が考えられると思います。
1番目は、フォントの各グリフのサイズに関する情報だけを定義してしまい、実際に表示する文字は、各プラットフォームにインストールされているフォントを使用するという方法になります。たとえば Windows の 「MS Pゴシック」で各文字の幅を取得しておき、他のプラットフォームでは、たとえ違うフォントを使ったとしても、サイズは「MS Pゴシック」の物を使う、と言った具合です。日本語の全角を全て固定ピッチとして見なす、というのもこれに類する解決法になると思います。これならば字詰めが変わることはありません。
2番目は、今の吉里吉里にもあるように、ビットマップフォントとしてあらかじめ用意して使う物。
3番目は、2番目と似ていますが、フォントそのものを作品とともに添付する形です。これならばそのフォントを必ず使うことが出来るので、各プラットフォームでの違いは無くなります。
1番目を実現するにはフォントメトリクスを作成するツールを作ったり、それを使用するための下層構造も作らなければならないし、そもそもこの仕組みの必要性も説明しないとならないのでなかなか大変です。
2番目と3番目のような用途にあった無料のフォントは?というと書体関係 Wikiにまとまっています(thanks ごうさん)。実際の使用条件は各フォントを参照してください。
IPAフォント5書体が公開、限定された改変再配付も許可 にある「IPA フォント」はなかなか高品質なフォントで、使える物ならば是非使いたいのですが、配布条件が自由ではないので吉里吉里で使うには大変だと思います。というかライセンス関連がはっきりしないので、現時点では残念ながら使用は出来ないと考えた方が良いかもしれません。IPA フォントにまつわる話は「Re: [suse-linux-ja] 9.3-Live-DVD が公開されました」なども参考になると思います。
あと、各プラットフォームでの差異をなくすために FreeType を用いてフォントのレンダリングを行いたいと思います。
wx-devcpp で wxWidgets 用の GUI 設計をしてみるテスト。とりあえず wx-beta-6.8 をさわってみています。
…自動生成されるコードで
wxBoxSizer* WxBoxSizer1 = new wxBoxSizer(wxVERTICAL);
WxBoxSizer1->SetFont(wxFont(8, wxSWISS, wxNORMAL,wxNORMAL, FALSE, wxT("MS Sans Serif")));
ってあるんですが wxBoxSizer は SetFont を持って無くてコンパイルエラーになります。コメントアウトするならばコンパイルは通りますが、コードが再生成されるたびにこうなってしまうのでトホホな感じです。
そのほかはなかなかいい感じ。
sizer 系がよく分からないですね。C++ Builder のフォームデザイナに慣れてしまうとこの sizer がひどく面倒なものに見えます。
とりあえずウィンドウサイズが変わっても一通りデザインが崩れないような物をと思っていますが、なかなかうまくいかない。いろいろサンプルを見てみようと思います。
ファイル名の大文字と小文字の問題はそれはそれでイヤな問題ですが、ファイル名にまつわる話はUNICODE正規化の話も絡んでもっともっと複雑なようです。
そもそも Windows ってファイル名の大文字と小文字を同一視してるの?というと厳密にはそうではなくて、Windows 9x では、たとえば ΑΒΓ (全角大文字のアルファ・ベータ・ガンマ) と αβγ (全角小文字のアルファ・ベータ・ガンマ) は、大文字・小文字の関係にあるにもかかわらず、区別されています。Windows NT 系だとちゃんと同一視されています。
MacOS X は大文字と小文字の区別はインストール時のオプションで指定できるようですが、こっちはこっちで別の問題(いや正しく扱うのであれば全く「問題」ではないのですが)を持っていて、たとえば「が」のような文字が、ファイル名としては「か」(U+304B)と濁点である「゛」(U+3099)の2つのコードポイントに分解されて保存されています。これはファイルシステムに用いられる際にUNICODE正規化(のNFD; Normalization Form D)が行われているためです。(参考: Q: Unicode 文字列を合成済みの文字の形式に変換する方法はありますか? ) thanks ごうさん
ローカルのファイル名が吉里吉里に入るときには何らかの正規化をすれば内部的な表現の統一はそれなりに計れると思いますが、逆に正規化されたファイル名でローカルのファイルを探そうとなると、一個一個、ディレクトリ中の全てのファイル名を検査して、どのファイルがその正規化されたファイル名に該当するのかを探さないとならなくなります。
いったん XP3 アーカイブとかの中にファイルが入ってしまえば、吉里吉里の環境内で完結する問題なので、ファイル名の問題はなくなります。しかしセーブデータの保管など、ローカルファイルをさわらない訳にはいかないのが実際です。
いろいろ考え中。とりあえず、一案。
吉里吉里が管理するストレージ領域 (こちらは吉里吉里のルールが適用される) と、OS が管理するストレージ領域をきっぱりと分けてしまうというのがいいかなと思っています。これならあんまりここら辺の難しい話は考えなくていいかな、と。
たとえば、file: は OS のファイルシステム、data: はアーカイブファイル、storage: はセーブデータなどの保存用ストレージ領域という具合です。file: には、そのプラットフォームのファイルシステムのルール(大文字小文字の区別の有無など) が適用されますが、data: や storage: には吉里吉里のルール (常に大文字・小文字の区別あり) が適用される、とするなど、です。
file: にアクセスする際は自己責任でそのOSのルールに従ってください、と言うことになります。
data: についてはアーカイブファイルと書いてありますが、実際はコンテンツの開発中ではいちいちアーカイブファイルにまとめて作業をするわけではないので、data: 以下は今の吉里吉里2の file: スキームのようにアーカイブだけではなくて通常のファイルも扱えるようにすると思います。ただ、プラットフォームを越える場合は、原則的にはアーカイブファイル化しないとアクセスは保証しませんと言うことになりそう。Windows プラットフォームでも、アーカイブファイル化したときの混乱を防ぐため、ファイル名の大文字と小文字を区別するように吉里吉里側でチェックを入れるようになりそうです。
あまり関係ありませんが、実は現行の吉里吉里2も内部的にはこういう風にスキームを分けることができ、個々に正規化も扱うことができるようになってますが全然活用してません。
これやっちゃってイイのかなぁ(汗)
UTF-16 は扱いづらいし、Shift-JIS や EUC-JP よりは UTF-8 でしょう、ぐらいの認識しか自分には無いです。
なので、吉里吉里3ではテキストファイルの入出力を UTF-8 固定にしようと思います。
ちなみに吉里吉里2は出力は常に UTF-16で、入力はシステムのロケールに従ったMBCS文字コード (Windows の日本語環境だと Shift-JIS) で記述されたテキストファイルか、UTF-16 を受け付けます。
考えられる混乱としては、普通に Shift-JIS で記述したファイルを読み込ませてエラーになると言う物ぐらいでしょうか。Shift-JIS で書かれたファイルは UTF-8 とは容易に判別できるので、読み込み時にエラーとしてしっかりはじくのであればそれほど大きな問題にはならないとは思いますが………。
過去に吉里吉里2は、吉里吉里1との互換性をばっさりと捨てました。
吉里吉里3は吉里吉里2との互換性をやはりある程度は捨てます。
吉里吉里、同じメジャーバージョン (1や2など) 内でも積極的な後方互換性の確保はしていなかったです。これは吉里吉里が、それのバージョンを対象とした作品とともに常に配布されるため、そもそもあまり互換性を重視しなくてもよいからです。
いろいろ検討していますが、以下の仕様変更があると思います。
吉里吉里2は吉里吉里3とともにメンテナンスを進めますが、吉里吉里2には今後あまり大きな機能追加は無いと思います。
いつか宣言しておかないとと思ったのですが、吉里吉里3を作ります。
正式には 23 日にあった 吉里吉里勉強会 で発表しました。
このあいだトラックバックをして頂いた 冬星さんのところのblogエントリを参考に IBM の JRE を使用するようにしたらフリーズはしなくなりました。
補完や定義の検索が重いのは重いです。まあ定義の検索をいちいちgrepで探し回るよりはマシぐらいかなぁ。
どうも MinGW ld は未使用の関数を自動的に削除してくれません。バイナリを覗くと気づくのですが、明らかに必要のない (他から参照されてない) 関数がバイナリに残っていたりします。
実際は ld は、未使用かどうかはオブジェクトファイル単位でしか見ていなくて、関数等のより細かい単位では見てくれていないよう。
調べてみると ELF ターゲットでは メモ:gccで不要な関数をリンクしない方法 のように -ffunction-sections -Wl,--gc-sections でうまくいくらしいのですが、どうも MinGW では (PEターゲットだとできないらしいのでたぶんcygwinも) できないようです。
ld のオプションを調べていくと -x (discard locals) というオプションがあるのでなんだろうと思って付けてみると、先ほどのバイナリのサイズが 1.68MB から 1.57MB に。うーん、でもこれでも関数単位での削除はやってないみたいですけど。
ただ、たしかに最終的なバイナリのサイズは気になるところではあるのですが、本質的な部分ではないので保留します。
wxWidgets で作成したアプリがデカすぎるのでダイエットを試みました。
とりあえず configure オプションでダイエット。
要らなさそうな機能がたくさんついてるので --disable-xxx オプションを付けていろいろな機能を無効にしてみました。configure は通るのに make でエラーになったり、あるいは最終的にアプリケーションと static リンクするときに undefined symbol エラーになったりで何回も configure → make → アプリとリンクを繰り返しました。以下のオプションでようやくビルドに成功しました。
./configure --with-expat=no \ --with-opengl --with-odbc=no \ --with-libmspack=no --with-regex=no \ --enable-exceptions --enable-catch_segvs \ --enable-mousewheel --enable-unicode \ --enable-mslu --disable-xrc \ --disable-mimetype --disable-debugreport \ --disable-plugins --disable-sound \ --disable-ole --disable-fontmap \ --disable-fs_inet --disable-fs_zip \ --disable-url --disable-mdi \ --disable-printarch --disable-postscript \ --disable-datepick --disable-grid \ --disable-coldlg --disable-dirdlg \ --disable-html --disable-shared \ --disable-stopwatch --disable-zipstream \ --disable-dnd --disable-dataobj \ --disable-metafile --disable-metafiles \ --disable-gif --disable-pcx \ --disable-iff --disable-pnm \ --disable-xpm --disable-splines \ --disable-sockets --disable-help \ --prefix=/mingw && $MAKE
で、できたバイナリサイズは 2.05MB から 1.68MB に。まだまだ削れる機能はあるのでもうちょっと小さくなりそうです。
それにしてもこの日記はどれが最新の記事なのか分かりづらいっすね。日ごとの記事は基本的に新しい物ほど上に出てきますが、同じ日の中では新しい記事は下に出てきます。
うーん、直そうかな。
リンカに渡すライブラリの順番が問題でした。
-lunicows -lwx_mswu_core-2.6 -lwx_baseu-2.6 -lkernel32 -lgdi32 (省略)
ではなくて
-lwx_mswu_core-2.6 -lwx_baseu-2.6 -lunicows -lkernel32 -lgdi32 (省略)
じゃないとダメですね。
MS純正のunicows.dllを使い、Windows Me でサンプルを動作させることができました。ちなみにオープンソース版の opencow は、実装されている API が少なすぎて、libunicows のテスト用プログラムさえ動作しませんでした。
ちなみに現状の吉里吉里はWindowsNT 系では *W 系の Unicode 系 API を自動的に使用するようになっています。もっとも VCL が ANSI でしか動作しないので完全に対応してるとは言い難いのですが。
Dev C++ はどうなんだろということでテスト。
動作は CDT より軽くていい感じです。
ソースコードの文字コードは ShiftJIS しか選べないのかな (コードエディタ自体が UNICODE に対応してないようなので望みは薄そうですけど )。デバッガはすこし使いづらい感じです。
コード補完は、wxWindows のヘッダファイルを解析させるようにしたらヘッダファイルの解析だけで3分ぐらいかかるようになりました。ファイルを保存するとその都度解析をするのでこれでは使い物になりません。とりあえず #include されてるファイルまでは見に行かないように設定すればそこそこ使えそうです。
もっとも自分は最近 Sakura エディタのコード補完(同じファイル中にある単語から候補を表示するだけ) で十分と感じてきたので、コード補完はそれほど欲しい機能ではないです。
しかしまあ もう IDE じゃなくて Makefile とエディタでいいやという気に ...
で、できた Hello world プログラム (ウィンドウが作成されて、メニューの項目を選択するとメッセージボックスの表示やアプリの終了ができる単純なアプリケーション) なんですが、Release ビルドのサイズ、デバッグシンボルをstrip後で wxWindows を static リンクしている物ですが、がなんと 2.05MB。もちろん wxWindows からいらない機能を削っていけばもっともっと小さくなるでしょうが、ちょっと大きすぎてびっくりです。
今回は opencow を使い、Windows NT 系 OS では UNICODE アプリケーションとして、Win9x では ANSI アプリケーションとして動作するようにしてみました。Windows 2000 にバイナリを持って行った場合では正常に動作しましたが、WindowsMe に持って行った所、エラーは出ないものの、ウィンドウも表示されずにプロセスがすぐに終了してしまって動作しませんでした。opencow じゃなくて純正 MSLU だと動くんだろうか。これは課題。
Eclipse CDT を入れてみました。うちは cygwin が既に入ってるからか CDT がツールキットとして cygwin の方を使ってしまうのでなんとか頑張って mingw の方を使うように。MSYS/mingw は PostgreSQL 8.0 をコンパイルしようとしたときに既に導入していたのですが、すっかり使わなくなっていたのでパスからはずしていました。パスの先頭に mingw の bin ディレクトリを入れてとりあえず cygwin との競合の問題は回避。
wxWindows をとってきて ここらへんを参考に MSYS 上で適当にコンパイル。もちろん UNICODE と MSLU サポート込みで。
で、そこにあった Hello world をコンパイルできるまでに悪戦苦闘すること1時間半。もうごちゃごちゃしてて何やったか覚えてないですが、 wx/setup.h が無いと言われました。探すと lib/wx/include/msw-unicode-release-static-2.6/wx/ にあったのでインクルードパスを通して完了。
コンパイラの入力文字コードはデフォルトで UTF-8 のようです。-fexec-charset で変えられます。とりあえずソースコードの文字コードは UTF-8 にしたいとかなり前から思ってたので歓迎。
で、問題発生。この Hello world のソースですが、CDT の Indexer (ソースコードを解析して関数/変数リストなどを作成し、コード補完などに使うための情報を作成する機構だと思われる) がハングアップします。このソースが原因かどうかは分かりません。Eclipse の終了もまともにできなくなってしまい、javaw.exe をタスクマネージャから強制終了するしか無くなります。
デバッガはgdbのフロントエンドとなってますが、割と使いやすくて(自分のイメージと合っていて)好みなんだけどなぁ。
普通のコンソールアプリだと Indexer もハングアップすることなく、コード補完は普通に動きます。が、予想を超えてトロかった。Borland C++ Builder のコード補完もイライラするほどトロいのですが、それをさらに上回って死んでしまうほどトロいのです。
吉里吉里3になるかも知れないものに向けての調査を進めます。
とりあえず、コアをマルチプラットフォーム化する前段階として、現状吉里吉里が使っている Borland の VCL (Visual Component Library) から全速力で抜ける事にします。理由は、C++ Builder を持ってないと誰もビルドできないからとマルチプラットフォーム化に問題があるからです。
とはいっても VCL から抜けたいのはコアの部分だけなので、krdevui.dll に入ってるような開発用のツール (Releaserとか) はまだ当分 C++ Builder で開発を続けることにはなりそうです。
最初は Windows をターゲットに開発をしますが、のちに他のプラットフォームに移植しやすいことを大前提として、使用ライブラリ等の選定を進めます。
ライセンスは現状のライセンス (GPL と 独自ライセンスのデュアルライセンス) でありたいとおもいます。つまり、GPL only なライブラリは使いづらいです。
調べなければならないのはざっと以下の点です。
これから、いろいろと気づいた点をこの日記に書きためていきたいと思います。
TJS2 は現状でもクロージャの機能を持っているのですが クロージャが表す事ができるコンテキストはオブジェクトに限られています。たとえば以下のコードでは、匿名関数 lambda は、そのすぐ外にあるローカル変数 foo にアクセスできません。
function make_function()
{
var foo = 0;
var lambda = function ( ) { return ++foo; };
lambda(); // lambda が呼び出され、foo は 1 になる
return lambda; // lambda を返す
}
var func = make_function ();
var a = func(); // a には 2 が入る
今考えてる新しいクロージャの機能は、本家 JavaScript と同じように、匿名関数などが、より外側のローカル変数にアクセスできるようになるものです。
上記の例で言えば、匿名関数 lambda は、それよりも外側にあるローカル変数 foo にアクセスできるようになるため、この foo の値をインクリメントした結果を返すことができます。また、lambda を返していますが、このように、たとえ lambda がそれが生成されたスコープを抜けても、ローカル変数を保持したまま lambda を実行できます。クロージャのコンテキストとしてローカルのスコープも扱えるようになると言うことになります。
実際は現状の TJS2 のクロージャの機能との互換性を考えなくてはならないので、上記の通りの仕様になるかどうかは決まっていませんし、実装の時期も決まっていません。
ただ、これが実現できると、いろいろ応用ができるようになります。
たとえば 指定範囲 a ~ b の整数に対して順番に関数 f を呼び出す range(a, b, f) という関数があるとすれば、1 ~ 100 の整数の合計を得るコードは以下のように実装することができます。
var sum = 0;
range(1, 100, function(n) { sum += n; } );
このようにコールバック関数を、コールバック関数を書いた時点でのコンテキストで実行できるため、だいぶ使い勝手がよくなります。
ここにあるような bind のような機能もよりスマートに実装できるようになりますね。
半年近く続けてきた 2.25 beta の開発をおえ、吉里吉里2 2.26、つまり次期安定版のブランチを立ち上げたいと思います。
ちなみに TJS2 の for in は 2.25 beta のうちに実装しようかと思っていたのですが、より高度なクロージャの実装も視野に入れたいため、先送りすることにしました。
2.26 を出すのならば、 2.26 の先はどうするのか、というのも考えなければなりません。吉里吉里3となるものの開発を始めるのかを決断しないとならない時期にあると思います。