難航していたが、ようやくまとまってきた感ある。
*
今までのファイル構成は、dropmakerのホームディレクトリ直下にベース設定ファイル、パッケージディレクトリ、ゲームディレクトリがあった。
セーブデータディレクトリなんかも置く予定だったけど、まだそれが必要になる段階まで到達していない。
実際には、ベース設定ファイル以外のディレクトリはベース設定ファイル内で指定した位置なので、ホームディレクトリの下に置く必要はないが。
それはともかく、パッケージディレクトリにはゲーム本体、プログラムやら、画像とか音楽とかのリソースやらが配置される。
一旦配置されたら、削除されるまで変更がかかることは基本的にない。
ゲームディレクトリには、ゲームパッケージを起動するための情報を記述した設定ファイルを置く。起動するパッケージ名やらモジュール名やらゲーム名やら。パッケージがなんらかのインターフェースを使用する場合、その実装モジュールの指定もする。
プレステで例えれば、パッケージディレクトリはCD、ゲームディレクトリ(とセーブデータディレクトリ)はメモリーカードである。
*
今回の構成変更で、とりあえずゲームディレクトリは無くした、というより分割した。
その前に、モジュールの扱いを変えたのを説明するべきか。
今までは、モジュールはゲームディレクトリの設定ファイルでインターフェースとの関連付けを行っていた。
今回の変更で、その関連付けはやめた。
その代わり、パッケージディレクトリの設定ファイルに、各モジュールが実装しているインターフェースを記述しておく形にした。
ゲームディレクトリの設定ファイルでは、使用を許可するモジュールのリストアップのみ行う。
あるインターフェースの使用が要求された場合、使用が許可されているモジュールの中から、そのインターフェースが実装されているモジュールを検索し、使用する。
もし該当するモジュールが2つ以上ある場合には、衝突が発生しているので要求は失敗となる。
該当するモジュールが存在しない場合には、インターフェースを定義するパッケージが提供するダミーモジュールを使用する。
ダミーモジュールを使うことで、例えばネットとの通信モジュールを使用するゲームであっても、ユーザー側でその機能を無効化する、ということも可能になる。
とはいえ、ゲーム側がネットとの通信ができないなら動作しないようにしたりしていれば、そもそもゲーム自体できなくなってしまうわけだが、それはまた別の問題だ。
*
で、ゲームディレクトリをコンフィグディレクトリとショートカットディレクトリの2つのディレクトリに分けた。
コンフィグディレクトリでは、パッケージディレクトリとセーブディレクトリとの関連付けや、前述の使用を許可するモジュールのリストアップなどを行う。
パッケージが他のパッケージに依存する場合、対象のパッケージについての設定が書かれたファイルの指定などもできる。
ショートカットディレクトリでは、起動に関する設定をまとめる。
起動するゲームの指定、メインウィンドウのサイズの指定など。
*
まとめると、
パッケージディレクトリにゲーム本体をつっこみ、
コンフィグディレクトリにパッケージの設定をつっこみ、
ショートカットディレクトリにゲーム起動時の設定をつっこむ、といった具合か。
あと、セーブデータディレクトリにゲームのセーブデータをつっこむ。
*
一部の設定ファイルについては、拡張子が一致していれば複数のファイルに分割することも許可する予定。具体的にはベース設定とパッケージ設定。
コンフィグディレクトリに配置する設定ファイルについても複数のファイルに分割できるようにしたいが、いい案が浮かばない。
前述のファイル分割は、見やすさ、扱いやすさの向上などが目的だが、こちらは違う。
パッケージとセーブデータの関連付けと、使用許可モジュールリストを別ファイルに分けることで、例えば使用するモジュールはそのままに別のセーブデータを使いたいとか、同じセーブデータを使うけど使用するモジュールは変えたいとか、そんな感じだ。
ならばその2つの設定を別のディレクトリに分けてしまえば、と思わないでもないが、そこまですることだろうか。少なくともパッケージとセーブデータの関連付けの方はデータ量が少なすぎる。
細切れになるばかりで扱いにくくなりそう。
その辺は後々対応することにしようかしら。そのくらいなら、全体的に修正をかけなければならない、というレベルのものでもないだろうし。
*
ともかく、現時点ではファイルとディレクトリの構成を考えただけであり、実際にそれを運用するためのプログラムは存在していない。
今までのdropmakerは断片的にしか役に立たないだろうし、1から作っていくとしよう。
色々考えていたが、dropmakerはまた作り直した方がいいかもしらんね。
*
どうにもモジュールの管理周りがうすらぼんやりとしてよくない。
指定したモジュールから関数のアドレスを取得する関数を、全てのロード済みモジュールから関数のアドレスを探して取得する関数に変えようとするだけでもなんかうまくいかない。
うまくいかないというより、変更が大きくなりすぎるというか、1から作り直した方が早そうというか。
この際、ディレクトリや設定ファイルの構成らへんから練り直すべきかもしれない。
現状では、直接起動するパッケージ以外はセーブデータを持てないし。
依存モジュールの解決も、今は単純な構成だからましだけど、ちょっと複雑な構成になるとすぐだめになりそう。
*
大まかな構成はテキストに書き出したので、土日に実際にディレクトリや設定ファイルを並べてみるつもり。
日をまたいでしまった。
*
次の日のやる気に影響するかもしれないので、こんな時間まで作業するべきではない。
さて、fg::Windowからfg::Screenを分離した。
デモを使って正常に動作することも確認済み。
ここまでは大したことはない。ファイルの移動と記述の置換、それと多少の修正でできた。
ここからが本番。メインウィンドウの生成と、それをゲーム側から触れるようにする。
*
sucrose-screen-*モジュールを追加したので、パッケージ設定ファイルに記述を追加していて思ったが、もうちょっと要素を追加した方がよさそうだな。
そのモジュールがなんのインターフェースの実装なのかを示す情報が現時点では存在しない。
今のところはモジュール名を同じにすることでそのインターフェースの実装モジュール、ということにしているが、それだと実装が複数存在する場合に対応できない。
そこで、モジュールの設定ツリーになんのインターフェースの実装なのかを示す要素を追加してみようか、などと考えている。
そこまで優先度が高いわけではないので、後々実装する予定。
あるいはもっといい設計が思い浮かぶかもしれないし。
ウィンドウを2つの型に分離することを検討している。
*
OpenGLコンテキストの扱い方については昨日の調査で大体把握できたので、以降はメインウィンドウをどのように実装するか考えていた。
しかし、どうにもうまくまとまらない。
今のところ、ウィンドウはfg::Windowという型で表現しているが、これを直接使うと色々問題がある。
例えば、fg::Windowはfg::close()を使えばウィンドウクローズ、fg::resize()という関数を使えばリサイズなどの要求が行なえるが、メインウィンドウに関してはこれらは必要ないように思う。
また、fg::free()を使えばウィンドウの破棄ができてしまう。
破棄はメインウィンドウを生成、制御するゲーム起動側でやるので、ゲーム側ではできないようにしたい。
他にも色々あるが、要はウィンドウを表現するという意味ではfg::Windowでいいのだが、その機能などがメインウィンドウと合致しない、といった感じだ。
*
ぱっと思い付いた対応として、例えばfg::MainWindowという型を作り、内部的にfg::Windowを持たせ、fg::MainWindowにはfg::Windowの持つ関数のうち一部だけを用意すればいいのではと考えた。
しかしこれはあまりいい対応ではない。
簡単に言えば面倒なのである。fg::MainWindowの内部にfg::Windowを持たせ、fg::Windowと似たような関数を用意するということは、fg::Windowに新たに関数を増やし、それがfg::MainWindowでも使えるようにすべき関数である場合、fg::MainWindowにも同じように関数を増やさなければならない。
当たり前ではあるが、こういうのは忘れてしまう可能性があるし、またfg::MainWindowの方の関数はfg::Windowの同じ関数を呼び出すラッパー程度の関数になるとはいえ、記述ミスによるバグを抱えてしまう可能性もある。
*
そこで考えたのが、ウィンドウを2つの型に分離する、という方法だ。
今はウィンドウを表現する型はfg::Windowのみだが、これとは別に表示領域を表現する型を用意する。
仮にfg::Screenということにするが、fg::Screenは自発的に生成、破棄することができない。
再描画やリサイズなどのイベントについてはイベントハンドラを設定できるようにするが、リサイズリクエストはできないようにする。
例えば、fg::Windowの内部的にfg::Screenを持たせる、という感じにする。
fg::Windowを生成すれば、それに対応したfg::Screenも同時に作られる、ということだ。
それで、fg::Windowの関数としてfg::getScreen()とかいう関数を用意して、fg::Screenの参照を取得できるようにする。
メインウィンドウについては、ゲーム側からはfg::Screenの参照のみを扱えるようにする。
fg::Screenの参照だけ、つまりメインウィンドウの表示領域だけなら、破棄もできないし勝手なクローズやサイズ変更の要求もできない。
おまけに、ラッピングした型でもないのでラッパー関数を作る必要などもない。
*
うまくいきそうな気はしている。
OpenGLのコンテキストについて色々試したので、整理しておこう。
*
ある1つのウィンドウを、複数のスレッドのカレントコンテキストに設定することは可能。
ある1つのOpenGLコンテキストを、複数のスレッドのカレントコンテキストに設定することは不可能。
やろうとすると、glXMakeCurrent()を呼び出すタイミングでセグメント例外が発生する。
一度、スレッドのカレントコンテキストを別のOpenGLコンテキストに変えるなりすれば、別のスレッドのカレントコンテキストに設定することはできる。
しかしその場合、別のスレッドのカレントコンテキストに設定する前に行なわれ、まだ画面に表示されていない描画処理は無かったことになる。
なので、一度カレントコンテキストに設定したOpenGLコンテキストを、別のスレッドのカレントコンテキストに移すとかいうのは、あまり意味がないように感じる。
少なくとも、ゲーム起動側がメインウィンドウに描画を行なうためのOpenGLコンテキストを、ゲーム側から参照できるようにする必要性は、ほぼないことが確認できたと言っていいだろう。
OpenGLコンテキストを複数のスレッドで使えないので、複数のウィンドウに対して1つのOpenGLコンテキストを割り当てる、とかいう構成も当然無理。
*
今の構成は、fg::GLContextを生成し、その参照とfg::Windowの参照を使ってfg::GLCurrentを生成、それを使ってOpenGLの関数を呼び出す、といった具合。
別のスレッドのカレントコンテキストに設定し直す意味があまりないということは、fg::GLCurrentなんて作らず、fg::GLContextの生成時にOpenGLコンテキストの生成とカレントコンテキストの設定を行ない、破棄時にカレントコンテキストからの設定解除とOpenGLコンテキストの破棄をやってしまえばいいのでは、ともちょっと思ったけど、それもまたちょっと違うんだろうな。
例えばテクスチャの情報などはOpenGLコンテキストに関連付けられるだろうし、ウィンドウを一度破棄してもう一度ウィンドウを作り、以前と同じような表示をしたい場合、その度にOpenGLコンテキストの生成を行なっていたのでは再度テクスチャの情報なども作り直さなくてはならない。
OpenGLコンテキストの生成・破棄とカレントコンテキストの設定・設定解除を分離しておけば、その辺うまく使い回せそうな気がする。
今まで中途半端に作っていたデモをきちんと整えるなど。
*
メインウィンドウ、どうやって実装しようか考え中。
実質的に、ウィンドウのイベントは再描画要求以外触れないようにしたいところ。
加えて、必要に応じてゲーム起動側、つまりメインウィンドウを生成した側でも画面に描画を行なえるようにしたい。
それはつまり、ゲーム起動側にメインウィンドウで使用するOpenGLのコンテキストを持たせるわけで。
それがカレントコンテキストになっているスレッドも持たせる必要があるわけで。
描画することを考えなければカレントコンテキストの件は無視できるので、とりあえずそれでやってみようかなぁ。
1つのウィンドウを、複数のスレッドでカレントコンテキストにする、とかってできるのかなぁ。
今まで試したことがないから、明日試してみようかな。
その結果次第では、OpenGLコンテキストはゲーム側から参照できるようにしなくても済むかも。
昨日書いた通り、OpenGLの関数を使えるようにした。
*
ほとんど前のプロジェクトで書いたものを流用しただけだが、やはり少し手を加えなければ使える状態にはならなかった。
しかし、OpenGLの関数や定数は数が多いため、一気にファイルの総行数が1万行ほど増えてしまった。
定数が書かれたファイルでおよそ5500行、関数が書かれたファイルでおよそ3500行あり、これだけで9000行に達するので仕方ないが。
垂直同期については対応した。
*
あとはOpenGLの関数だが、前のプロジェクトで書いたものを流用すれば明日にも完了できる気がする。
垂直同期の対応についても、基本的には前のプロジェクトのものを流用したわけだが。
しかしながら、xlibのDisplayをグローバルでなくしたなど、構成を変えているために修正は必要だったが。
OpenGLの対応の残件は、OpenGLの関数を使えるようにするのと、垂直同期対応。
*
今日かコンテキストをカレントに設定する処理を作っていた。
それに付随して、バッファの切り替え処理も対応した。
現時点ではglClear()とか使えないので、バッファを切り替えてもゴミデータが表示されてしまうだけだが。
これであとは、OpenGLの関数を使えるようになれば描画ができるようになる。
*
OpenGLの残件を対応したら、メインウィンドウ機能を対応したいところだ。
ゲームの実行前に自動的に作られ、ゲーム側に渡されるサイズ変更不可、移動検知不可のウィンドウ。
事前に設定することでサイズを変更できたり、フルスクリーンで実行できたりなどというのを考えているのだが。
昨日のは勘違いだった。
*
見るデータが間違ってて、実際にはおそらく問題なかった。
なんであんな勘違いをしたのか。
あの勘違いをしたやつは直ちに名乗り出ろ。
*
OpenGLの対応を進めている途中で、試しに動かしてみてどうしてもOpenGLの関数を呼び出すところでこけてしまう問題が起きていた。
1時間程度試行錯誤して、valgrindを使って実行するとこけた気がするのを思い出して、valgrindを使わずに実行したらすんなり動いてがっくりした。
無駄に疲れた。
もうこんなことはしたくないものだが、忘れた頃にまたやりそう。
排他処理、スレッドや、ウィンドウについてはとりあえず出来上がっている。
*
今はOpenGLの対応を進めている。
その途中で、スレッドのライブラリにwaitやnotifyを行なうためのインターフェースを追加したり、その実装を作ったりもした。
とにかく画面に描画をできるようにしたいところ。
*
valgrind使っててなんかおかしいと思ったら、dlopen()したライブラリがdlclose()されてなかった。
なんかdlclose()にnullが渡されてるから、クローズされないし失敗もしない。
ていうかNewModuleの型がおかしい。
なんでこんな型になっているのか。
この処理を書いたやつは直ちに名乗り出ろ。
イベントハンドラの対応、一応できた。
*
しかし、排他処理のインターフェースを作っていなかったため、まだ実用的でない。
ロックしてないから、データがおかしくなるかもしれない。
やはりスレッドのインターフェースも作るべきだろうか。
ロック関係をそこに含めるか。
まためんどくなってた。
*
sucrose-windowを作成中。あとはイベントハンドラの対応のみ。
色々考えて、また少しイベントハンドラの扱いを変えた。
イベントハンドラ部分については、ウィンドウシステムの実装とは無関係だと思ったので、sucrose-window-commonというライブラリに分離させた。
linux用の、xlibを使う実装はsucrose-window-xlibとして作成してある。
で、イベントハンドラ部分をあまりめんどくなさそうな感じに設計を変えたので、明日ぐらいには作り終えられるのではと考えている。
順調、と言いたいがやはりスローペースな気がする。
*
とりあえず、sucrose-jsonは使えるようにした。
あとはsucrose-mainを使えるようにすれば、dropmakerを動かせるが。
しかし、シンボルの取得とかは処理を書き直さねばならないだろう。
CからC++にした影響だ。
よくよく考えたら、イベントハンドラでつまづいただけだったなぁ。
*
なので、dropmakerに関しては、ほとんどそのままソースを持ってくればいいか、という気分になっている。
細かく修正を入れようとして時間食うよりずっといい。
昨日加えた修正の件も考えて、持ってきたソースの中の不要になるconst_castを消していっているのだが、参照系のクラスは全部2種類用意するべきかもしれないなぁ。
というのも、fg::JsonObjectPairsで似たような状態が発生しているのだ。
体調がびみょう。
*
このところ、そのせいか分からんがびみょうに思考がまとまりにくいし、集中もできてないような。
そんな感じで進みも悪い。
*
文字列参照型を2種類にした。
std::basic_stringみたいな位置付けのやつだが、std::basic_stringと違い、自身では文字列の領域を保持したりせず、他のところに配置されている文字列を参照する型なのだが。
今までの構成だとコンストラクタがchar *なので、const char *を入れようとするとconst_castしなければならなくてめんどすぎる。
なので、コンストラクタにconst char *をつっこむのと、char *をつっこむやつの2種類の型を作ることにした。
後者の型を前者の型の子クラスにしたので、わざわざオーバーロードした関数を用意しなくても済むし。
10日に書き忘れた。
*
gitのログ見てみたらなんかおかしいな。何回sucrose-commonの生成ルールを追加してるんだ。
直すのもめんどくさいが。
*
とりあえず、sucrose-commonとsucrose-strconv-iconvを作った。
前のプロジェクトではstrconvは一度に1種類しかビルドできなかったが、今のプロジェクトでは複数の実装を一緒にビルドできるようにしてあるため、名前を変えた。iconvを使っているので末尾にiconvと足してある。
commonとstrconvを作ったので、コマンドライン引数を処理するだけのプログラムがビルドできるようになった。
が、リンクする静的ライブラリ名をハードコーディングしているので、それはどうにかするべきだな。
必要に応じてコマンドライン引数から指定できるようにしたいが、どうやって処理するべきか。
前のプロジェクトからソースをどんどん持ってきている。
*
2種類のライブラリを生成する件についてはどうにかなった。
違うプロジェクト間で、wscriptを同一にしなければならないという決まりがあるでもなし、プロジェクト毎に不要なものは削除し、必要なものは追加して対応することにした。
早いところ、今までに作った部分は移行してしまって、新機能を作成していきたいところ。
ビルドの簡略化はできた。
*
とりあえず前のプロジェクトからソースファイルをそのまま持ってきて、fg-commonの生成がきちんとできるのは確認した。
正確には、fgppからソースを持ってきたのでFGPPをFGに、fgppをfgに置換したりはしたが。
あれ、よく考えたらあるライブラリについて、静的ライブラリと動的ライブラリの2種類作るのはできなくないか?
どうしよう。ぱっと思い付く修正案はあるものの、それでいいのかどうか。
早速、作り直し中。
*
ビルドの簡略化を行なっているが、想定よりちょっとめんどかった。
あるパッケージに存在するモジュールの一覧を取得する処理が、そう簡単にはいかなかった。
dir()はロード済みのモジュールしか取得できないし。
調べたら、ModuleScannerというのを使えばいいというのは分かったので、それを使ってコマンドラインオプションを作るところまではやった。
次は、そのコマンドラインオプションを使用した際に、対象のモジュールのビルドを実行する処理を作る。
うまくいけば、新たにモジュールのビルドルールを追加しても、他のところにその処理を呼び出す処理を追加しなくても済む。
configure時にコマンドラインオプションを追加するだけで、そのビルドルールでモジュールが生成されるようになる。
また、コマンドラインオプションで指定しなければ、モジュールをビルドしないようにもできる。
って、いかん。何また難しくしようとしてるんだ。
*
まずはC/C++だけ、もしくはC++だけ扱うのでもいいじゃないか。
まだ一つも完成させてすらいないのに、最初から事を大きくしすぎだ。
JavaとかPythonとかPerlとか、まだ必要な場面ではないのだ。
*
また迷走して時間ばかりかけてしまうところだった。
メインのライブラリはどうしよう。また作り直すべきだろうか。
今度は本格的に処理は丸々使えるが、今のプロジェクトに修正を加える形でもいい気がする。
しかし、この機会にビルドの仕組みの簡略化もしてしまいたいし。
それをする場合、やはり作り直すべきな気がする。
というか、インターフェースライブラリはそのままでいいな。
いや、ビルドの仕組みの簡略化はインターフェースライブラリもやるけど、それは修正を加える形でいいだろう。
この際だから、Cのラッパーライブラリも作らず、完全にC++だけの構成にしてしまってもいいかもしれない。
ラッパーというか、他の言語に同じインターフェースがあると、一方に修正を加えたらそちらにも同じ加えたくなるし。
コードの量は増えてもできることは増えてないという、効率の悪いことになるし。
*
うむ。C++のインターフェースライブラリ「fg」、実装ライブラリ「sucrose」、ライブラリ利用環境「dropmaker」という3つのプロジェクトから成る構成にするかな。
あと、ライブラリ利用デモ「fgdemos」の4つか。
結局、C++のライブラリをメインにした方がいい気がしてきた。
*
イベントハンドラの関係で、C言語のライブラリをメインにしているとラッパーライブラリがうまく作れないのだ。
逆に、C++のライブラリをメインにして、C言語のそのラッパーライブラリにすればうまくいくんじゃないかなぁ、といったところ。
そもそもC言語のライブラリをメインに持ってきたところで、それを直接使うわけでもないし。
C言語を直接使ってゲーム作るのとかやりたくないし。
しかし、その程度で大きく変更をかけるのは場当たりすぎる気がする。
もうちょっとこう、他の言語との連携周りなのだから、その辺どのように対応するのかを考えてから処理したいところだが。
現状ではどうあがいてもC/C++などで作られた、lib*.soとか*.dllとかのライブラリしか対応できないわけで。
例えば.jarやら.pyやら.pl、.pmやらも使えるようにしてみたいところ。
ウィンドウ制御処理、大体はできたのだが。
*
ウィンドウ内全てについて再描画処理をリクエストする処理についてはまだ。
XGetGeometry()の処理が完了せず止まってしまうことがある。原因はまだ不明。
おそらく、このへんも昔に通った道のはずなのだが。
もしかしたら、xorg-serverのバージョンアップが影響しているかもしれないが、多分そんな大げさなことはないだろう。
というか、この関数本当に必要だろうか。なんとなく必要な気がする、というだけで存在させている感は否めない。
やりたいなら、ウィンドウサイズ変更イベントのイベントハンドラを設定しておいてウィンドウサイズを取得し、それを使ってウィンドウ内全てに再描画リクエストを投げてもいいのだ。
ウィンドウの位置についても、どうもxlibではうまいこと制御できない感があるので、というか前にもこれは体験した気がするので、ウィンドウの位置は制御できないようにした。
これと同じように、関数自体を消すという対応でいい気もする。
イベント発生時のイベントハンドラ呼び出し処理を追加している。
*
イベントハンドラの設定とかその辺については、ウィンドウ制御側からは触らんわけだし、イベント発生時の処理を書いた後でもいいかなって感じで。
イベント発生時の処理はリセット前のプロジェクトとほぼ同じで大丈夫なので、さくっと片付けてしまいたいところ。
おかしいな、イベントハンドラの形式が古い。
*
set形式は色々だめだと判断して、add/remove形式に変えたはずなのだけれど、その形式にしたソースが見つからない。
そんなもんだから、今のAPIはset形式になってしまっている。
直さないといかんなー
*
ウィンドウ制御については、とりあえず表示と、イベント処理スレッドの起動までは作った。
で、イベントハンドラの呼び出しとかが必要になってきた時点で、前述の問題に気が付いた感じ。
終了関数については、結局後回し。
*
xlibを使ったウィンドウ制御について実装を始めている。
リセット前のプロジェクトを参考にしているものの、前の物よりは分かりやすいものにしようと考えながら記述している。
その中で、ラムダ関数への変数のムーブキャプチャを使ったのだが、なんかエラーが消えないと思ったら、どうやらconstでキャプチャされるらしい。
参照キャプチャではそんなことはなかったようなのだが。ふしぎ。
ひとまず、初期化処理呼び出しの位置変更は完了した。
*
しかし、終了処理についてはまだ考え中。
初期化関数の引数に、関数ポインタの参照を渡してそれに終了関数のアドレスを設定するという方法はあまりよくない気がする。
直感的でないというかなんというか、分かりにくいというか。
そんな回りくどい方法を取るくらいなら、設定ファイルに初期化関数名と終了関数名のペアを書いておくとかの方がいい気がする。
どうも暴走していたようだ。
*
昨日書いたことはどうにも浅はかすぎる。終了処理の呼び出しは同じように書くべきではないと思った。
初期化処理の呼び出しと同じように、終了関数を設定ファイルで指定することで、モジュールをアンロードする前に終了処理を呼び出す、というのは安直だが、安直すぎてだめだ。
それだと、初期化処理が途中で失敗した場合、どの終了処理を呼び出すべきで、どの終了処理は呼び出すべきでないのか。
こういうのはよく、終了処理は全て絶対に呼び出し、終了処理の中で、初期化してあるなら処理を行ない、初期化していないなら何もしない、と書くべきなどと言われるが、私はそういうのが嫌いなのだ。初期化してないなら終了処理は必要ないのだから、最初から呼び出さない方がすっきりする。
例えば、初期化処理の引数として関数ポインタの参照を渡しておき、初期化処理が正常終了する場合に対応する終了関数のアドレスを設定する、などすればいい感じになりそう。
*
しかしながら、現状では安直な方法ですら行なうのが困難な状況だ。
モジュールのアンロード処理で、ロードしたモジュールを表現するvoid*しか情報をもらっていないため、どこかに終了処理の関数名を含めるとかいったことができないのだ。
構成を間違えた感じがある。モジュールのロード、アンロード処理に、初期化、終了の処理を含めるべきではなかったのだ。
単純に巻き戻してしまってもいい気がする。
初期化処理できあがり。
*
次は、初期化処理と共通の部分は共通化して終了処理の呼び出しを作成する。
厳密には、現時点ではまだ必要ないと思われるが、ほぼ同じ処理なわけだし、ちょっとしたらすぐ必要になると思うし。
作っておいて損ないだろう、といったところ。
初期化処理の追加、途中までできた。
*
初期化処理を走らせるところはできたので、次は走らせる処理を指定するところ。
それが出来たら、終了処理についても同じように追加する。
しかし、ModuleInfoとModuleKeyを分けている意味がないような気がする。
一緒にしちゃってもいいんじゃないかな。
*
ビルドの仕組み、簡略化すべきかも。
現状では、あるモジュールについて、configureする時に実装を指定することで、その実装を使ったモジュールをビルドするのだが、モジュールの名前を別にすることで、複数の実装を同時に存在できるようにしようかと。
現状では、例えばウィンドウ管理のモジュールは、linux用のxlibを使った実装でも、windows用のwin32を使った実装でも、作られるモジュールの名前はlinuxならlibfg-window.so、windowsならfg-window.dllになる。
でも、例えばxlibを使った実装ならfg-window-xlibとか、win32を使った実装ならfg-window-win32とかにすれば、実装が異なる複数のウィンドウ管理モジュールを共存させることができる。
その環境はビルドできない実装の場合は、configureでビルドしないように指定すればいいのだし。
この変更をすることで、多分ビルドの仕組みは今より簡単になるはずなのだ。
困った。
*
初期化が必要なモジュールについての考慮が足りていなかった。
今まで通り、各OSに用意されている機能を使ってもいい気がするが、あれはあまりよくない。
初期化時に処理が失敗した場合にどうするとか、そういったことができない。多分。
きちんと調べたわけではないのだが、少なくともwindowsでは関数が1つ固定、関数名すら固定だし、それを使うくらいならば自分で機能を作ってしまった方がいい気がする。
今のところの考えでは、パッケージ設定に記述するモジュールの設定に、初期化関数を指定できる形にしようかと考えているがどうなんだろう。
総ソースファイル行数が1000行ほど減った。
*
今まで、共通化できそうだけどめんどうなので放置していた場所を、マクロで共通化した結果、そんな感じになった。
これで、あとは実装部分をリセット前のプロジェクトから持ってくればウィンドウを扱えるようになるだろう。
というわけで、ウィンドウの対応を始めた。
*
リセットする前のプロジェクトからファイルを持ってきて、ちょっと修正を加えて使えるようにしたりなど。
若干インターフェースやマクロの仕様が違うので、完全にそのまま使うわけにはいかない。
一気にファイル数が増え、総ソースファイル数が300に迫る勢い。
明日には300を越すだろう。
しかし、メインウィンドウの生成はどこでやるべきか。
コンテキストに参照を持たせることは決まっているが、コンテキスト生成処理に混ぜるか、生成後に参照をコンテキスト生成処理に渡すか。
前者でいい気がするのだが、なんかしっくりこないような。
とりあえず、コマンドライン引数対応は終えたのだが。
*
この次、本当にパスの扱いについてやるべきだろうか。後回しでもいい気がしてきた。
いい案がぱっと浮かばないし、なによりやる気があまりしないのだ。
それよりも、リセットする前のプロジェクトには実装されていたウィンドウや入力機器、音声機器の管理機能をサクサクッと実装して、試しにゲームでも作ってみるべきな気がする。
ゲームを作らんと、いつまで経っても話が進まんのだ。
どうにも作業速度が思うように上がらないと思ったら案の定だった。
*
そんなわけでまだ出来上がっていないが今日は疲れた。
このまま出来上がるまで続行するというのは昔はよくやっていた気もするが、これをするとその後一層気分がよくなくなるのだ。
よくない連鎖は発生する前に止めるに限る。
とりあえずコマンドライン引数対応を始めた。
*
一応、リセットする前のプロジェクトでは実装されていた機能なので、大体はそこから持ってくればいいだけなのだが。
その後の処理は完全に1から書くことになるわけで、出来上がるのは早くて明日だろうな。
今日はこれ以上続けるとだらだらしちゃうからやめやめ。
ちょうど1ヶ月開けてしまった。でも過去ログ見る限り、3月丸々書いてなかったみたいだしまぁいいかなって
*
前の記事にミニチュアを作るとかどうとか書いてるな。作った作った。
とりあえず限定的なJSONパーサを作った。扱える要素はオブジェクトとリストと文字列のみ。
で、それを使ってJSONぽい形式の設定ファイルを読み込んで解析し、必要なデータ抽出して、みたいな処理も作った。
一応、ゲームの起動処理を書けたのだが、勢いで書いたため今整えてるところ。
書けたと言っても、想定している機能を全て実装できているわけではない。
今の状態ではまだ使い物にならない。ローダの分離もしてないし。とりあえずCの関数を呼び出せるってだけ。
手を加えたいところだが、滅茶苦茶な書き方をしたため修正や機能追加など、手を加えることが非常に困難ぽい。
なので、処理を複数のソースファイルに分けたり、などということをやっている。
それが済んだら、次は依存関係の実装かな。
ローダの分離は一番最後でいい気がする。それとも最初にやるべきなのだろうか。でもテンション上がらないだろうし。
ミニチュアを作るべきだと思った。
*
肝心なところがまだもやついている。しかし、これは実際にやってみればぱっと解決する気がする。
なので、まずは簡易的なミニチュアで表現し、色々試してみることにする。
最初から実際のコードでやってみようとするから、躊躇したり混乱したりしてしまうのだ。
想定していた設計ではどうもうまくできそうにない。
*
今まで作っていた部分については、土台として問題なく使えると思うし、前に作っていた部分についても、大体そのまま使えそうなイメージはある。
しかし、その中間に作るものが、どうもだめだ。
根本的な思想からしてだめだった感が否めない。
どう実装するかについても考慮しつつ、きちんと考え直す必要がありそうだ。
wafの挙動で納得できなかった点が解決した。
*
python3でwafを動かしビルドすると、変更点が無い場合でもコンパイルやリンクが走るのがなぜなのか分からなかった。python2で動かすと再現しないこともあって奇妙だった。
今日、それとは別件でwscriptを修正していたら、偶然原因が判明した。
どうも、python3とpython2で、セットの挙動が違うらしい。
セットに文字列をつっこむ場合、同じ順序で同じデータをつっこんでも、python2の場合は常に同じ順序になるが、python3の場合は順序が変わっていることが多い。
セットの性質を考えればpython3の挙動でも全然問題ないのだが、ソースファイルをセットに詰めて渡していたため、python3で動かした場合に毎回ソースファイルの順序が変わってしまい、同じコマンドだと判断されず、コンパイルやリンクが走ってしまっていたようだ。
そこで、セットではなくリストに詰める形にしたところ、問題は解決した。
ideoneに検証用コードを上げてリンクを張っておこうとしたのだが、なぜかideoneのpython3では再現しなかった。実装が違うのかしら。
*
結局、また作り直している。どうも私の作り方は変な気がする。なんか知らんが変なところから作り始めている気がする。
今回はちゃんと、先頭から順々に作っていっている。はず。呼び出す処理自体はこれまでに作った処理を大体そのまま使っている。
ベースディレクトリが書かれているファイルのパスを生成し、ベースディレクトリを読み出し、まで書いた。
次はベースディレクトリに配置されているコアライブラリのロード、実行。コアライブラリにはJSONパーサのようなものを搭載し、JSONで書かれた設定ファイルを読み込み、ローダライブラリをロードし、うんぬんかんぬん、のような予定。
dlopen()とLoadLibrary()らへんの違いを確認した。
*
結論から言うと、実現は容易いが少しめんどうだ。
linuxの共有ライブラリは、ビルド時に依存する共有ライブラリをリンクしなくてもビルドできる。
一方、windowsの動的ライブラリは、ビルド時に依存する動的ライブラリをリンクしなければビルドできない。
linuxにおいて、dlopen()を使って共有ライブラリを実行中にロードする場合、対象のライブラリが依存している共有ライブラリはLD_LIBRARY_PATHの環境変数が示すパスに存在していなければならない。
windowsにおいて、LoadLibrary()を使って動的ライブラリを実行中にロードする場合、対象のライブラリが依存している動的ライブラリはどこかに存在していなければならない、ということはない。
*
今作っている複数のライブラリの中には、作っている別のライブラリに依存しているものも多いが、このライブラリはLD_LIBRARY_PATHなどの環境変数が示す場所に置いておくつもりはない。
なので、linux用にビルドする場合には、依存ライブラリを指定せずにビルドし、実行中に自分でライブラリの依存関係を解決し、ロードする必要がある。
ビルド時に依存ライブラリを指定してしまうと、ロード時にこけてしまう。
windows用にビルドする場合は、実行中については同様だが、ビルド時には指定しないとこけてしまうため、依存ライブラリを指定しなければならない。
*
私が作っているライブラリの生成時にだけ影響するというのならいいのだが、ここで言うライブラリというのは、ゲーム自体のプログラムも含まれる。
つまり、ライブラリを利用する側の、存在するかもしれない私以外の開発者にも、そのようなルールを強いなければならないわけだ。なかなか心苦しいものがある。
どうしようもないことではあるのだが。
前の日記から1ヶ月ほど空いてしまった。
*
えらくローペースながら、進んではいる。コマンドライン引数の処理をして、その引数を元にゲームを起動しようとするところまで書けた。
しかしこの部分については、後々変更することになりそうだ。今の状態は、あくまで一時的なもの。
ゲームをロードする仕組みらへんについても大体考えがまとまってきたし、悪くはない感じ。
ただちょっとやる気が出にくいのと体調がよくなりにくいのが気になるところ。
また最初から作り直した方がいいのではないかという気持ち。
*
とりあえず、fg4cppの位置付け、これ完全にミスってる気がする。
fgppのインターフェースを全てC++のインライン関数にして、コード上ではfgppのインターフェースを使いつつも、実際にはfg用のプログラムができあがる、というものなんだけど。
現実にはそううまくいっていない。fgでは関数ポインタにしているが、fgppでは関数オブジェクトになっているところとか、ラッピングがうまくいっていないのだ。
全てインライン関数で済ましたいのだけれど、そういう関数ポインタ周りの部分には、fg4cppでインターフェースを定義し、ライブラリの方でfg4cppに対応するためのインターフェースを実装しないと使えない、といった感じだ。
fg4cppを、内部的にはfgの関数を呼び出すfgppの実装の一つ、という位置付けにした方がラッパーとしてきちんとできそう。
*
文字列の扱いについても、失敗してる感がすごい。
文字列型は、fgにはFgString等の宣言だけで、定義は実装するライブラリに記述、という形を取っているけど、定義もfgに定めておくべきかも。
定義を書いていないせいで、生成方法も実装するライブラリに関数として実装する必要があるが、その関数からログを出力したくても、ログのライブラリが文字列生成・破棄関数を使用することになるので、循環参照になってしまうため出力できないのだ。
そもそも文字列型なんていうのはそう複雑であったりとか、環境によって実装がまちまちなんてものではない。
文字型の配列というだけだ。加えて、長さが分かって各要素へのランダムアクセスができればそれでいい、という程度のもののはず。
その型に対して、例えば文字コード変換とか複雑なことをすることはあっても、型自体は単純なはずなのだ。
などなど色々言ったが、早い話が文字列型を定義してないせいで、他のライブラリに依存させたくないログのライブラリが文字列ライブラリに依存する結果になってしまっている、それはやだなぁ、ということ。
*
文字列型とかいう基本データ型に修正を加えるとなると影響範囲すごいしクソめんどくさそうなので、そんくらいなら1から作るべきかなーとか。バグ作り込んでしまいそうだし。
イベントハンドラは基本的に複数設定できるようにするべきかもしれない。
*
現在の状態では、イベントハンドラは1つしか付けられないようになっている。
理由としては、簡単に言えばめんどかったからである。
1つであれば、イベントハンドラが設定されているかいないか、しかない。
イベントハンドラを解除する、という処理をするなら、設定されているものがあればそれを削除すればいいだけの話だ。
これが複数となるとそうはいかない。どのイベントハンドラを削除するのか、とかいう話になってくる。
そうなると、イベントハンドラに結び付く情報がどうとかいう話になってきて、なかなかめんどくさい。
*
その辺の理由から、1つだけ付けられる形にしてきたわけだけど、1つしか付けられないとなると、複数の箇所からイベントハンドラを設定するような感じになってくると、非常に使いにくくなる。
イベントハンドラを設定する、とはつまり、既存の設定を上書きしてしまうことになるわけだ。これはよくない。上書きされた側は、意図せずイベントハンドラの設定が解除されることになる。
これで問題が発生しないとはとても考えられない。
*
とかいうのが、ログ関係のイベントをどうするかとか、ライブラリの依存関係がどうとか考えていたら出てきた。
ログライブラリは、出力するために文字コード変換が必要になるので文字列ライブラリに依存しているんだけど、それではよくないと思ったのだ。
それをすると、文字列ライブラリがログライブラリに依存できず(循環参照になるため)、文字列ライブラリからログの出力ができなくなるのだ。
そこで、ログライブラリは文字列ライブラリに依存させず、ログライブラリと文字列ライブラリの双方に依存する、ログ出力ライブラリというのを用意することを考えた。
ログライブラリはログを受け取るインターフェースと、ログを受け取った時にイベントハンドラを呼び出すだけにして、ログの文字列変換などは行なわない。ただの橋渡しだ。
ログ出力ライブラリはログライブラリにイベントハンドラを設定して、ログ発生時のイベントを受け取り、ログを文字コード変換し出力する。
そこまで考えたのはいいのだが、ログのイベントハンドラが1つしか設定できないと、ログ出力だけで占有する形になってしまう。
私の想定では、ログをファイルに出力しつつ、エラーログが発生した際には画面にアラートを出したりとか、そういうことをしたいのだ。そして、アラートを出すのはログ出力ライブラリの役割ではない。
*
とまぁそんな感じで、イベントハンドラは1つしか設定できないようにするべきではないなぁ、という結論に至ったわけだ。
なんかどんどん、JavaのAWTみたいな感じになっていく気がするが気のせいだろう。
ログ出力のインターフェースはできた。
*
printf()で標準出力に出力するだけのしょうもない仮実装も作った。
ログを出力したら、それを別のところでイベントとして受け取り、イベントハンドラを呼び出す、とかもやりたいのだけれど、具体的にどういうインターフェースにするのかが定まっていない。
なのでその辺はまだできない感じかな。
とりあえずは、今までに作った箇所でログ出力が必要なところに記述を追加するとしよう。
ビルド時に、バックエンドライブラリを指定できるように修正した。
*
というのをやってから、またやる気減退していたがまたがんばる。
設定ファイルのフォーマット定義、読み取り処理とか、C以外のプログラムの起動とか、その辺の対応をしたくはあるのだが、まだもやもやしていたために手を出せなかった。
それらを作るために、まずはロガーを作ることにした。
*
ロガーは、異常系にユーザが対応するための重要な要素になる。
異常系に対応するための要素と言えば、例えばステータスコードのリターンとか、例外のスローとかあるけど、今回の場合どちらも不適格だと思う。
基本的に、何か問題が発生したとして、自動的に何かをしなければならない、ということがないのだ。
ステータスコードを返したり、例外を送出したりというのは、プログラム内でどういう問題が発生したのか判別し、発生した問題への対処を自動的に行なうためのものだ。
逆に言えば、その場でプログラム内で問題に対処する必要がないのなら、成功ならtrue、失敗ならfalseで構わないと思う。
ただ、それだけでは発生した問題の種類も分からず、ユーザが対応することもできない。
そこでロガーというわけだ。問題が発生した時点で、それがどういう問題なのかをログとして出力する。
処理の結果としては、失敗しました、エラーログを参照してください、のようなものだけ表示する。
その時に、ログを参照すれば問題の原因を特定でき、対応が可能、という感じだ。
*
ログ出力はほぼ全てのライブラリが利用するはずなので、できるだけ早い段階で、インターフェースだけでも定義しておくべきだろう。他のライブラリのインターフェースにも影響するからだ。
そんな感じで、次はロガー対応。
年末年始はのんびりしてたけど、数日前からちょびちょび再開している。
*
とりあえずは、インターフェースはそのままに、実装の気になるところを修正していた。
ダミー関数の定義自動生成もやっつけたし、他にも色々修正した。
今まで作ったところで修正するべきところはまだあると思うが、前のプロジェクトで作った機能についてもどんどん追加していきたいところ。
ひとまず、bitbucketの課題リストにつっこむところから始めるべきか。
新しいプロジェクトにしてから、まだ課題リスト使ってなかったしな。