CoreManagerは排除することにした。
*
存在理由が不明瞭なものを残しておいて後々混乱するぐらいなら、きれいに消し去って明瞭な構成にしておく方がずっと賢い。
いつもそのようにするのが正解とは言えないが、変更にあまり労力がかからなそうならやっておくべきだろう。
幸い、CoreManagerの依存箇所は少なかったため、今日の修正でmain()内のCoreManager生成箇所以外は排除できた。
明日から、CoreManagerで行っていた処理を分かりやすい形に構成しなおしていく。
新仕様のショートカットファイルの処理完成。
*
前より色々と明確になった部分が多いので、どういう構成にするかというところで少々時間を取られてしまった。
とはいえ、ひとまず作り切れたのでよし。
*
次は新仕様に合わせた起動処理を作るが、candymaker::CoreManagerをコピーして作るべきかな。
正直、CoreManagerとかいう謎の物体が本当に必要なのかどうか、あまり確信がないのだが。
理屈で言えば、処理の流れ自体は既存のものから変える必要はないはずなので、とりあえず動かすためにはそれが最適解のはず。
新仕様の起動ファイル読み込み処理完成。
*
その読み込んだ内容を利用して起動処理を書き直したいところだが、先に新仕様のショートカットファイルの未実装部分の対応かな。
新仕様の起動ファイル読み込み処理の、システムパスを含めた場合の処理を作ったことで、そっちもどう対応すべきか大体把握した。
いや、今見直してみたらもっとシンプルな方が扱いやすそうだな。
とにかく、そっちを終わらせてからだ。
新しい仕様の起動ファイルの読み込み処理が大体できた。
*
セーブデータのパスを指定できるようにしたり、既存の部分も構成を変えたりした。
大きな追加は、テンプレートファイルを読み込めるようにしたところだ。
C言語における#includeみたいなもので、この機能によって複数のファイルに記述を分割できるようになった。
テンプレートファイルの内容自体は起動ファイルと全く変わらず、重複部分は後に読み込まれたファイルの内容が優先される。
*
ファイルの内容は全く変わらないため、ファイルの読み込みとテンプレートの展開を1つのソースファイルにまとめてしまったのだが、これは間違いだったかもしれない。
1つのソースファイルが1000行を超える程度に膨れ上がってしまった。
それに、どう考えてもファイル読み込み処理とテンプレート展開処理は別物で、別ファイルに分断するのも大して難しそうには思えない。
テンプレート展開処理を作成している最中に、ファイル読み込み処理に手を加えた覚えがない。
後々のために、その辺の解決をしてから先に進もう。
*
システムパスを含めた場合の処理が手付かずなのだが、せっかくだからそれも対応してから次の機能に進むべきか。
作り始めの時はどう対処したものか決めかねていたが、今はそれなりにイメージが固まっている。
kasuteraをfgの新しい仕様に合わせた。
*
いよいよ機能追加を進めたいところだが、また別の仕様変更が必要かもしれない。
コントローラの設定をどのようにセーブするか、というところがまだ定まっていないのだ。
そこに手を付けようとすると、他のまだ定まっていない部分も決めたり、変更したりが必要になりそう。
セーブデータのフォーマット、それをどこに置くのか、セーブデータを扱うためのインターフェース、ユーザーが扱うための画面をどうするのか、等々だ。
セーブデータの配置については、他の設定ファイルの配置なども一緒に決めていくことになるだろう。
*
今考えているのは、モジュールのセーブデータとパッケージのセーブデータを別物にする、というものだ。
前者は配置が固定されており、ユーザーの操作を介さずにセーブを行う。
モジュールの設定を保存するためのもので、コントローラ設定はこれで保存する。
後者はユーザーが配置を決めることができ、ユーザーの操作を介してセーブを行うこともある。
ゲームの進行状況を保存するためのものだ。
*
コントローラ設定などの内部的なものは、ユーザーに存在を意識させたくない。
意識させたところで、ただややこしいだけだと思うし。
一方、ゲームのセーブデータについては、ユーザーが存在を認識できるようにしたい。
普段は内蔵ストレージに保存するが、場合によってはUSBメモリなどの外部ストレージに保存して、持ち出せたりしたら便利だろう。
そのような特性の異なるデータを、1種類のセーブデータのみで表現するのは少々無理があるように思う。
*
いや、どちらも同じ場所に保存するだけで問題ないような気もするな。
一方はセーブ画面を出さずに保存し、もう一方はセーブ画面を出して保存する。
しかし、ゲームのセーブデータの保存に内部的なもの向けの保存機能を使う、とかはさせたくないんだよなぁ。
そうなると、やはりインターフェースは別々にするべきか。
サンプルゲームの修正完了。
*
brownsugar以外はバージョンを確定させた。
brownsugarについては、kasuteraのテストのためにもうちょい追加が必要になるはずなので、それを終えてから。
サンプルゲームは、わずかだがコードの量が減った。
ゲームが利用するfg::Stateをゲーム側で生成していたのを、candymaker側で生成するようにしたためだろうな。
*
これで2つの大きな変更のうち、1つが完了した。
もう1つは、今はやらなくていいかな、という気がしている。
ゲーム側にはほとんど影響しない変更、のはずなのだ。
やってみないと正確なところはなんとも言えないが、ゲーム側からしたら機能が1つ増える、というだけの話のはず。
*
というわけで、kasuteraの方を進めようと思う。
既存部分をfgの新しい仕様に合わせてから、機能追加を進める。
最後に触ったのいつだっけ、と確認したらどうやら6月のようだ。
5ヶ月近く放置してしまったのか。
結局10月いっぱい、いやちょっとオーバーか。
*
なんとか、zarameの修正まで終わらせた。
サンプルゲームによる動作確認はまだできていない。
*
以前と構成が大きく変わった影響で、更に機能の追加が必要になった。
その影響で時間も更にかかってしまった感じ。
*
来週の頭にサンプルゲームによる動作確認を行なう。
デッドロックは解決した。
*
今はウィンドウ周りの構成について見直している。
前に、ウィンドウ本体とウィンドウのイベント処理をする型を別々にしていたのを1つに統合したのが今の状態。
その変更はどうやら失敗だったらしい。
OpenGLを使う場合、必要な型を生成するためにウィンドウ本体を参照する必要があるが、生成した型はウィンドウ描画イベントで使用する。
ウィンドウの型が統合されていると、ウィンドウ本体が作られてからウィンドウ描画イベントが始まるまでの間にOpenGLの準備をすることができなくなる。
以前とは関係する型の構成が変わってるから全く同じように戻すことはできないが、以前の状態を参考にしつつ修正していこう。
*
ちゃちゃっと修正して、zarameの修正も終わらせたいところ。
非常にまずい。
*
デッドロックを起こしてる。
fg::Windowを抱えるfg::Stateを破棄しようとするとデッドロックする。
fg::Stateが持つスレッド終了待機と、稼働を続けているfg::Windowのスレッドが衝突している感じ。
*
どうしようかと考えたところ、一番の問題はXNextEvent()だな。
こいつは次のイベントが存在しない場合、次のイベントが発生するまで永遠に待機を続けてしまう。
この作用により、fg::State破棄時にスレッドを開始できないようにしているにも関わらず、それ以前からfg::Windowのスレッドが待機を続けているせいで、その機能がうまく働いていない状態になっているのだ。
つまり、必要なのはタイムアウトだ。
XNextEvent()が一定時間でタイムアウトする仕組みだったなら、fg::State破棄後にタイムアウトした場合、以降のスレッド開始は失敗するので、デッドロックにはならない。
問題は、XNextEvent()にタイムアウトを設定することなどできないところだが。
*
調べたところ、どうやらポーリングができるらしい。
ConnectionNumber()とかいう関数の戻り値を使えば、select()でタイムアウト付きの待機ができる。
タイムアウトしなかった場合にはイベントが来たということなので、XNextEvent()をしても待機が発生しない、というわけだ。
*
しかしながら、これどうやってテストしたらいいんだろう。
最初に書いた通り、fg::Windowを抱えさせたfg::Stateを破棄し、問題なく処理が終了することを確認すればいいのかな。
コメントで、構成によってはデッドロックを起こすことがあるのでその対応、とか書いとかないと、なんのためのテストなのか分からなくなりそうだな。
sucroseの書き直しは完了。
*
brownsugarにfg::BasesystemStateを追加し、テストで実用的に扱うための機能も追加した。
次はzarameの書き換えだ。
ペースは悪くない。
*
brownsugarにcandymakerから機能を移植し、sucroseの書き直しに必要な機能を最低限用意した。
その機能を使い、sucroseの機能を書き直していて、残るはudev関係のみとなった。
ちゃちゃっと書き換えて次に進もう。
*
brownsugarにzarame書き換え、というかベースシステム作成時のテストに必要な機能を追加し、zarameを書き換え、動作確認をし、不要になった機能の削除。
負荷高い作業もなさそうだし、来週中には完了できると思うのだが。
*
ベースシステムに実装する機能のインターフェースを1つのディレクトリにまとめたいが、名前はどうしようかな。
ベースシステム起動処理の書き直しまでで精一杯だった。
*
zarameを新しい仕様に合わせて書き直す前に、sucroseの旧構成依存の処理書き直しが先だな。
そしてその前に、brownsugarを新しい仕様に合わせるのが先だな。
全部完了するまでに、今月いっぱいかかってしまいそうな気がする。
ペース下がってる。
*
ベースシステムの起動処理書き直しに際して、既存の処理で使われてる型を排除しようとしたら妙に色々なところで参照されてて困った。
多分、ファイルから読み込んで生成した型をできるだけそのまま扱い、必要になったタイミングでその型から文字列などの具体的なデータを生成する、という方針で書いたんだったような。
それにより、データ生成時に生成処理で必要な型を参照する必要がある。
しかし、今見返してみた感じでは変な依存関係ができて複雑化してしまうだけで意味がなさそう。
なので、早いタイミングで具体的なデータを生成するように変更し、データ利用時点で変な依存関係ができないように修正している。
なかなか骨が折れるが仕方がない。
他にも、例外を送出しないように記述していた名残りなども見受けられるので、見つけ次第修正している。
*
そんなわけでやはり今週中は無理だった。
前述のような修正を始める前に、どこからどのように手を付けたらいいのか悩まされたのも大きい。
来週中にはどうにかしたい。
作業の進行具合は悪くない。
*
fg-stateの機能をfg-coreに移し、実装もsucroseからcandymakerに移行している。
スレッド周りも一緒に移行する。
既存の機能で残りはイベント関係の型。
この辺は、前にも書いたが仕様がちょっとおかしい部分があるので、せっかくなので解消しておこうと思う。
*
機能の移行が済んだら、次はベースシステムの起動処理を書き直し、zarameも新しい仕様に合わせて書き直す。
sucroseの不要になる機能に依存している処理を書き直したら、不要になった機能の削除。
最終的にfgやcandymakerからも不要になった機能を削除したら完了、という感じか。
来週中に完了、はちょっと厳しい気がするなぁ。
再来週中には完了したいところ。
昨日、未検証の部分を検証した。
*
その結果、私の考えている構成が私の考えていた方法で実現可能だと確認が取れた。
しかしながら、それを実現するためには根本部分に変更が必要になる。
実現できるのはうれしいのだが、少し気が滅入ってしまった。
大きな変更を終えてなお、大きな変更が必要になるとは。
*
必要な変更は大まかに2つ。
1つは昨日検証した部分とは直接の関係はない。
既存の機能をちょちょいといじったものを作り、その後に大きな変更が必要になると思われる。
もう1つの変更は前述の変更が前提になっている部分があるので、後に回そう。
古い機能の削除と、細かい修正完了。
*
これでバージョンを確定。
マルチスレッド周りでバグが起きていたのが厄介だった。
きちんと直ったのか、あまり自信がない。
一応動いてはいるが仕様がちょっとおかしい部分とかあるので、後々その辺の修正はした方がいいな。
*
最終的な全体の構成のイメージが大体まとまった。
まだ未検証の部分があるので、その辺の確認が必要だな。
想定より時間がかかった気がするが、機能追加完了。
*
brownsugarの方を全く触ってなかった。
brownsugarを新しい構成に合わせて構築し直せば、ゲーム側を除けば古い機能を利用している部分はなくなるはずだ。
そうなれば、いよいよ古い機能を削除できる。
古い機能の削除まで含めて明日中に終わればいいんだが、さすがに無理かな。
へびゲームが動作することを確認。
*
多少sucroseに修正が必要だったが、確認が取れたのでひとまず安心。
ただ、実際に新しい仕様を利用してみて、少々機能の不足を感じた。
不要になる機能の削除は、その機能の追加が終わってからだな。
量は多くないし、明日中に追加を終えられると思うが。
これで機能追加は済んだかな。
*
メインウィンドウの描画イベントとコントローライベントに対応した。
明日、へびゲームを新しい仕様に合わせた形に書き換えてうまくいくか確認しよう。
確認が取れたら、不要になる機能を削っていく。
fg-controller自体への変更は多分必要ない。
*
というわけでzarameに手を加えている。
zarameにメインウィンドウ描画イベントをほぼ追加した。
結論としては、fg::GLTaskExecutorみたいのは必要だった。
fg::GLTaskExecutorはスレッド処理の後に描画バッファのスワップをするので便利。
というか、OpenGLコンテキスト使わないと描画バッファのスワップができない仕組みになっているので、ないと困る。
なので、zarameのメインウィンドウ描画イベントは、今のところ描画してもバッファがスワップされないので画面に反映されない状態になっている。
それでは使い物にならない。
*
fg-gl_newが必要だ。
新しい構成に合わせたfg::GLTaskExecutorのようなものが必要になる。
fg-window_newほぼ完成。
*
メインウィンドウに関する処理以外はできた。
インターフェースを作ったところで、実装はzarameでやることになるし後回しにしようかと。
*
次はOpenGLの対応かなと思ったけど、必要ないかもしれない。
fg::GLTaskExecutorとか、スレッドで処理している型はあるが、新しい構成では必要なさそう。
むしろ、これなんで必要だったんだっけ?とすら考えている。
なんで必要だったんだっけ。
*
そうなると、次はfg-controllerかな。
後はベースシステム用のインターフェースを整え、zarameに実装し、不要になる既存のライブラリを削除し、名前を整える。
そんな感じか。
宣言通りにfg-state_newの改良は昨日のうちに完了して、fg-window_newに取りかかっている。
*
fg-state_newの構成変更により、fg-window_newは多少構成をシンプルにできそうだ。
以前のfg-windowでは、ウィンドウの実体とウィンドウのイベント処理を別の型に分ける、という構成になっていたが、そんなことせず1つにまとめられそう。
ただ、1つにまとめると内容が多くなるので、結構複雑になりそう。
まずウィンドウの実体のみ作るように書いて、そこにイベント処理を追加していく形で進めて行こうと思う。
最初から全てつっこむのは無謀な気がする。
ひらめいたのでfg-state_newを改良中。
*
この変更により、一度に複数のイベント関数を有効にする、という処理ができるようになる。
明日中には完了したいところ。
fg-state_new完成。
*
関連する機能の修正が済んだら旧版のfg-stateを削除し、名前をfg-stateと改める。
*
しかしこの構成でうまくいくのだろうか。
旧版なら複数のイベント関数を一度に全て変更する、という処理ができたが、新版で全く同じ処理はできない。
有効だったイベント関数を一度に全て無効にする、という処理はできる。
一度に複数のイベント関数を有効にする、という処理ができないのだ。
どうにかできたらいいのだが。
ようやくfg-stateの練り直しが終わりそう。
*
ほぼ完成しているが、型名や関数名の変更した方がいいものや、追加した方がよさそうな機能などがちらほらある。
名前変更は2つ、機能追加は1つだが関数としては4つか。
それが済めばfg-stateの新版は完成かな。
あとは、それを利用する機能、つまりウィンドウやコントローラ周りを合わせればようやく完成。
明日のうちに、細かい修正は全て終えてしまいたいところ。
fg::Stateは一段落。
*
よく分からん死に方については、多分どうにかなった。
問題は処理自体でなくテストコードの方だったようで、作り直す過程でテストを見直し、修正したら問題は起きなくなった。
しかしながら、問題の原因は特定できず、直した後は今のところ起きてない、という状態なので、実は直ってないがたまたま問題が発生してないだけ、という可能性も否定できない。
多分大丈夫だと思うんだけど。
*
次はfg::StateManagerかな。
完成し次第、fg::StateManagerが必要なfg::Stateの処理も追加し、完成させる。
fg::Stateの作成に苦戦中。
*
一応は動くのだが、たまによく分からん死に方をする。
マルチスレッド処理はよく分からん問題がたまに起きるのが困る。
決まっていつも同じ死に方をするのであれば、解決も楽なのだが。
一部の関数を作り直すことで解決しようと思い、テストと処理をコメントアウトしたところで今日はおしまい。
やってもやっても直らんエラーの対応は精神を削りすぎる。
fg-stateのインターフェース練り直しに苦戦中。
*
イベントの管理方法を変更したことで、他の部分も変更した方がよりよくなる部分が出てきた。
それらをうまくまとめようとしているが、なかなかうまくいかない。
イベントの管理をfg::StateStack単位でなくしたことで、積み重ねるfg::Stateを一元管理する必要がなくなった。
よってfg::StateStackそのものは必要なくなり、より単純なもので代替可能になった。
fg::Stateの中に次のfg::Stateを構築する、連結リストのような感じだ。
ただし、fg::State自体は常に触れる状態にしたくないため、直接は構築できないようにする。
先頭要素を内部で構築、保持する型として、fg::StateManagerという型を用意する。
fg::StateManagerが、fg::StateStackのより単純な代替型というわけだ。
*
そのように変更しようとしたが、実際に利用する上で不便がないようにまとめるのはなかなか困難だ。
大体まとまったようにも思うが、まだ不十分な部分もある。
できるだけ早く仕上げたい、というか時間かけすぎだ。
やる気下がってるのも影響してるが。
sucrose::ThreadCacheを練り直してる。
*
fg-stateのインターフェース変更に伴い、スレッドを管理する部分について後々削除することになるfg-taskから独立させようとした。
しかし、改めて記述を見てみるとなかなかひどかったので、作り直すことにした。
標準的なスレッド、複数起動を許可しないスレッド、キャンセル可能なスレッド、といった様々な種類のスレッドを、1種類のスレッドのパラメータ違いで混在させていて、妙に複雑になっているのだ。
そこで、違う種類のスレッドは違うものとして扱うことで、単純化を図った。
*
その単純化はおおむね成功した、というのが昨日。
sucrose-udevや、sucrose-udev-joystickの内部で使用しているスレッド処理を、新しく作ったsucrose::ThreadCacheに差し替えて従来通り動作することが確認できた。
が、キャンセル可能なスレッドの破棄時、原因がよく分からないSIGABRTが発生することがあり、今日はそれに四苦八苦していた。
それで、結局キャンセル可能なスレッドそのものを使わず、記述を全て破棄することで一応は解決した。
しかし、問題の根本はそれで解決したわけではない。
新しいsucrose::ThreadCacheの構成が、古いsucrose::ThreadCacheの構成に引っ張られている部分が多いのが問題なのだ。
スレッドが保持するデータはsucrose::Threadとして分離してあるのに、std::threadを使って実際に稼働させる処理はsucrose::ThreadCache内に存在している。
std::threadによるスレッド稼働部分もsucrose::Threadに分離した方が、もっと単純になるはずだ。
*
というわけで、新しいsucrose::Threadを作るところまでやった。
明日中には、再度新しくしたsucrose::ThreadCacheを作り切りたい。
できることなら、キャンセル可能なスレッドことsucrose::CancellableThreadについても、今度はちゃんと作ってみたい気もするが、やっぱり必要ないかなぁ。
POSIXであればキャンセルにpthread_cancel()を使うので、大体どの辺りでキャンセルが起きるのか分かる。
しかしWindowsの場合、多分TerminateThread()を使うことになる。
よく調べてないというのもあるが、TerminateThread()はキャンセルタイミングがよく分からない。
というかTerminateThread()ってこれキャンセルじゃなくてスレッド強制終了だろ。
正常系で使うべき関数じゃない気がするんだよなぁ。
であれば、Windowsでは表現できないわけだし、キャンセルに準ずる機能を使用しない構成にするべきだと思う。
fg-stateのインターフェースを練り直した。
*
まだ完了はしていないが、大まかにはできている。
既存のインターフェースよりもよくなっているように思う。
fg::Stateに対し、インデックスと関数ポインタをペアで設定するインターフェースはやめた。
そもそも、イベントの管理をfg::StateStack単位で行なっているのがよくなかったのだ。
そのせいで、ステート間に妙な依存関係ができてしまっている。
ステート単体の動作テストがやりにくいのも、その辺りが影響している。
なので、イベントの管理をfg::State単位で行なうことにした。
これにより、ステートが単体で完結する構成になったはずだ。
動作テストもやりやすくなるだろう。
代わりにステート1つ辺りの記述は増えてしまうだろうが、既存のものが横着していた部分を正したんだと思えば、理不尽なものでもないだろう。
*
既存のインターフェースからそのまま引っ張ってきている部分も半分くらいはあり、その中には変更した方が良さそうな部分もまだある。
また、自発的に非同期実行を開始するインターフェースの追加もできていない。
明日中には完了させ、sucroseに実装していきたい。
fg-taskをfg-stateに統合するべきかもしれない。
*
現在は全体的にfg-stateありきの構成になっているのだが、fg-taskはfg-stateより前に作ったため、噛み合わない部分が出てきている。
fg-taskはスレッドによる非同期実行を管理するモジュールだが、fg-stateと全く関わりがないため、ステートを意識した処理は自前で書かないといけないのが現状だ。
ステートの表示をアニメーションさせたりする際に、fg-taskを用いて非同期処理しているが、他のステートに移行する前には停止する処理を記述しないと、ステートの移行後も前のステートの非同期処理が動き続けてしまう。
そういった動作が有効に働く場面もあるだろうが、大抵の場合は望まない動作となるだろう。
ステートが移行した時点で、基本的に前のステートは動作を完全に停止するべきなのだ。
*
sucroseではudevによるデバイス管理などでスレッド処理をしているため、そのために現状と同じ仕組みはそのまま残す必要があるだろう。
fgからfg-taskを削除し、fg-stateのインターフェースを一新、実装も新しく作り直す、といったところか。
*
しかし、これは今やるべきなのかどうか。
インターフェースが大きく変わるわけだし、早めにやっておかないと困ることになるのは確実なのだが。
とりあえず、コントローラ設定の導入画面を作り切るのが先か。
アニメーションを手軽に作れるようにするための型、fg::Motionは大体完成した。
*
しかし、実際に使うとなると足りない機能が色々出てきて、まだタイトル画面の書き換えは完了していない。
もう少しだと思うのだが。
残件は、経過時間を表す型が整数型の場合に、値が滑らかに変動しない件の修正。
経過時間を浮動小数点にキャストできれば解決するが、これがそれほど単純にはできない。
例えば経過時間を表す型がstd::chrono::durationの場合、直接浮動小数点型にキャストできないからだ。
手順としては、count()で数値を取り出し、それをキャストする、となる。
キャストをファンクタで行うようにし、必要に応じてキャスト処理をユーザー定義の処理に差し替えられるようにすれば対応できるだろう。
*
モチベーション的に土日に作業するのは避けたいのだが、このくらいは済ませてしまうか。
アニメーションを手軽に作れるようにするための型を作っている。
*
コントローラ設定の導入画面で、何かボタンを押してください、みたいな文字列を点滅させるのが目的だ。
そういった処理を書くのは、そこまで面倒というわけではない。
似たような処理はタイトル画面の処理で作っているのだ。
しかしながら、この先似た処理は頻繁に出てくることが考えられ、そのたびに別の画面を参考にして処理を書く、なんていうのはコピペに近い。
それに、コピペに近いとは言えコードを追加するということは、毎度毎度似たようなテストコードを書くはめになり、そうなってくると非常に面倒だ。
というわけで、ロジック部分をテンプレートで表現した型をfgに追加することにした。
*
とりあえず、3つほど型を作った。
まず、開始時間から終了時間までの間に、値を開始値から終了値まで変化させる型。
次に、一定の間隔で値を開始値から終了値まで変化させ、その後は今度は開始値に向けて値を変化させ、と値を振り子のように変化させる型。
そして、それらの型をまとめる型だ。
1番目の型は、フェードインやフェードアウトといった、1回変化させたらそれで終わり、というアニメーションに使う。
タイトル画面で行なっているアニメーションもそういうタイプなので、完成したら差し替える予定だ。
2番目の型は、点滅や振動など、継続的に変化させる必要があるアニメーションに使う。
最初に述べた、コントローラ設定の導入画面で使用する予定だ。
3番目の型は、複数のアニメーションを同時に動かす場合に使う。
単純に仕組みだけで考えれば、1、2番目の型があればアニメーションの表現は可能だ。
しかし、それを複数同時に動かす場合、それぞれ個別に現在値を更新する関数を呼び出すのは面倒だ。
この型があることで、1回の関数呼び出しで複数のアニメーションに対して処理を適用できる。
書き忘れなどによるバグも起きにくくなるはずだ。
*
現在想定している型はあと2つ。
1つは一定の間隔で開始値から終了値まで変化させ、その後は再度初期値に戻して同じことを繰り返す型。
現時点では必要ないし、使い道があるかどうか疑問だったが、よく考えたら回転アニメーションを表現する場合には必要になるだろうし、作ることにした。
もう1つは、アニメーションを管理する型だ。
これがないと、今まで作った型を動作させられない。
*
明日中には完成予定。
完成したら、タイトル画面を書き換えてみよう。
タイトル画面の描画処理を作った。
*
描画処理なんて内部的には特に関係ないんだし、最初は後回しにするつもりだった。
でも、なんでもかんでも後回しにするのもどうかと思ったので、skiaの利用に慣れるのも兼ねてやることにした。
で、アニメーションさせてタイトル画面を表示する処理を作った。
割と簡単にできていい感じだ。
*
次はコントローラ設定画面か。
見た目ができればなんか進んだ感触があるし、これも見た目から作っていこうかな。
kasuteraに文字列描画処理追加などしていた。
*
sucroseで例外を投げるように変更したり、candymakerでモジュールのロード順を修正したりなどもした。
色々片付けていたら、追加した方がよさそうな機能もまた見えてきた。
そろそろディレクトリ構成について考えた方がよさそう。
現状存在しているカレントディレクトリ以外にも、ホームディレクトリ、システムディレクトリを扱えるようにしたい。
*
しかしながら、それよりも先にkasuteraの方を進めるべきな気もする。
土台固めばかりしていても前に進まない。
kasuteraや、ゲームの制作を進めて行かなければ前に進まないのだ。
改修完了、したはず。
*
ただ、別件でもうちょい修正が必要だな。
brownsugarとfg::DataElementListとかfg::DataElementMapに修正が必要だ。
先週までに終わらせたかったけど無理だった。
*
先週の火曜ごろに高熱が出てくたばってたので。
今も微熱が残っているけど、作業に支障はない。
*
zarameまで修正できた。
あとはcmsnakeを修正して動作の確認取れたら、いよいよ古い処理は消していくか。
いや、kasuteraの修正を終えてからか。
やはりデータ構造の修正はきつい。
*
ユニークポインタの新しい仕様への変更がまだ終わらない。
あと少しではある。
残りはコントローラとOpenGLのライブラリだ。
それが終わったら関連するプロジェクトの記述を差し替えて、それが済んだら古い仕様の記述を削除する。
遅くても再来週中には完了したい。
*
前回最後に書いた、形にできたとかいう新しい仕様?
そのうちbitbucketの方にpushするから興味ある奇特な人はそっち見て。
ユニークポインタ周りの新しい仕様を模索していた。
*
本当は画面周りを進めようと思っていたのだが、謎のタイミングでSEGVで死んでしまう原因が、データ解放にローカルのfree()を呼ぶ予定のはずが標準関数のfree()が呼ばれていたため、とかいうふざけた記述ミスが原因だったので気が抜けてしまった。
なかなか原因を特定できなかったので、無駄に気力を消耗してしまったのだ。
*
で、ユニークポインタだけど、色々と問題はあるのだがまず記述がC++風になっていないことだ。
C++風と言っていいのかは分からないが、要するにメンバ関数の利用ができない、ということである。
例えばウィンドウを生成し、生成したウィンドウに再描画要求を投げる、という記述を現状の構成でやると以下の通りだ。
*
auto windowUnique = fg::newWindow( ... );
auto & window = *windowUnique;
fg::repaint( window );
*
fg::newWindow()はfg::Windowという型のユニークポインタ、fg::Unique< fg::Window >を返す。
fg::Unique< fg::Window >というのは、大まかにはstd::unique_ptr< fg::Window * >のエイリアスである。
実際にはデリータも設定しているが、細かいことはともかく破棄時に自動でfg::free()という破棄関数を呼び出して破棄する。
この記述だと、名前空間の記述が2度登場していてよくない。
この例だとfgと短いからいいが、非常に長い名前空間だと記述がごちゃごちゃして分かりにくくなる。
using namespaceを使用すればそこにまとめられるだろ、という意見もあるだろうが、私としてはあまり頻繁に使いたくない機能だ。
関数の記述を見た時、一目でどこの名前空間の関数なのか、というのが分かりにくくなってしまう。
それ以外にも、この記述だとrepaint()とwindowの関連性がいまいち低いように思う。
fg::Windowの関数であるというのなら、window.repaint()の方が分かりやすい。
もっと言うなら2行目の、わざわざユニークポインタを参照にする記述も書きたくない。
よって、大体以下のような記述にしたいのだ。
*
auto window = fg::Window( ... );
window.repaint();
*
しかし、単純にそうするわけにもいかない。
コンストラクタやメンバ関数を記述するということは、その型の定義を明確にする必要があるが、それができないのだ。
なぜなら、fg::Windowのデータ構造は一定ではないからだ。
環境によって実装を変えるし、またバグの修正や仕様変更の際にfg::Window利用側で再コンパイルの必要がないように、利用側には型の宣言のみ提供し、詳細な定義を提供したくない。
詳細なデータ構造の記述を避ける場合、その型にはコンストラクタもメンバ関数も記述できなくなる。
まるでCのような、メンバ関数を一切使用しない構成にしていた理由はそこにあるのだ。
*
fg::Unique<>は私としても知恵を搾り、できるだけ利用側に負担をかけないような構成にしたつもりだ。
以前なら、生成処理は以下のように書かなければならなかった。
*
auto windowUnique = fg::unique( fg::newWindow( ... ) );
*
現在の記述は、これと処理は全く変えず、よりシンプルにした記述できるようにした形なのだ。
しかし、それでも不都合が出てきた。
先ほど挙げた問題以外の問題だ。
fg::Unique<>という名前はfg内の型に対して提供する型だが、fgを利用するプロジェクトでも同様の機能が使えるように、マクロで手軽にUnique<>を記述できるようにしている。
kasuteraでもその機能でユニークポインタを使おうとしたのだが、そこで問題が発生した。
kasuteraでは名前空間を複数に分けていたのだが、ユニークポインタの定義を1つにまとめようとすると不自然な記述になってしまうのだ。
例えば、以下のような記述をしたとする。
*
auto aObjUnique = kasutera::a::newObj();
auto bObjUnique = kasutera::b::newObj();
*
どちらも生成関数だが、これらの生成関数が返す型をそれぞれkasutera::Unique< kasutera::a::Obj >、kasutera::Unique< kasutera::b::Obj >にしようとすると、破棄関数は両方ともkasutera::free()になる。
生成関数はkasutera::a::newObj()なのに、破棄関数はkasutera::free()。
これはちぐはぐでよくない。
関数の宣言をする際にも、以下のようになってしまう。
*
namespace kasutera::a {
struct Obj;
kasutera::Unique< Obj > newObj();
}
namespace kasutera {
void free( a::Obj & );
}
*
本来なら、以下のようになるべきだ。
*
namespace kasutera::a {
struct Obj;
Unique< Obj > newObj();
void free( a::Obj & );
}
*
しかしそのためには、各名前空間ごとにUnique<>を用意する必要がある。
いくらマクロを使っていて手軽とはいえ、至るところにユニークポインタの定義があるというのは不自然極まりなく、気持ちが悪い。
以前からもっと改良するべきだと考えていたが、不都合が出てきてしまっては本腰を入れて取り組む必要がある。
*
ここまでの内容から、要件をまとめると以下の通りだ。
・データへのアクセスはメンバ関数を用いる
・定義1つでどの型でも利用可能
また、fg::Unique<>で満たしていた要件は以下の通り。
・データ構造を隠蔽可能
・できるだけシンプルに記述できる
・必要な関数(破棄関数)が存在しない場合、コンパイルエラーで検知できる
・std::move()やstd::swap()といった標準関数を利用可能
これら全ての要件を満たす仕組みを構築する必要がある。
そしてそれは実際形にできた。
いい加減長すぎるので、それについてはまた次回。
パソコンを新しくすることにした。
*
能力的には問題ないのだが、大きさとか重さとか、その辺がよくない。
私のパソコンはパーツを集めて組み立てた、いわゆる自作パソコンだ。
作った当時はただなんとなくでかいのに憧れがあったので、それを反映したかのようにでかくて重い。
大きいと自然と空冷ファンも大きいものになり、空冷ファンというのは大体大きいほど静かになりやすいので、静音性はなかなか気に入っている。
また、拡張性が高く、後々何か追加することもやりやすく、その大きさ故にパーツのサイズによって干渉を起こす、ということも起きにくい。
しかしながら、大きく重いととりあえず動かしにくい。
今はゲーム専用に使っている、少し大きめのディスプレイにつなぎたくても現状では難しい。
ディスプレイが多少離れたところにあるため、パソコンを動かさなければならないからだ。
大きく重いので、動かすだけでも一苦労。
他にも掃除が面倒というのがあるが、これは大きさというより使ってるケースが悪いんだろうな。
*
静音性については、あまり気になるようなら回転数を落とすなり静音性を売りにしている物に買い替えるなりで対策できる。
拡張性なんてのは、大抵の場合何に使うかが明確でない場合に後から対応するためのものであり、使用目的が明確であれば必要性は薄く、デッドスペースにしかならない。
実際、今のマシンはハードディスク6台搭載できるスペースがあるが全て空だ。
よって今のマシンのような大きさは必要ないと判断。
必要な構成をぴったり収めた、移動させやすいパソコンを作ることにした。
*
移動をしやすくするため、マザーボードのサイズは小さいMini-ITX。
より小さいMini-STXのベアボーンというのもあったが、さすがにUSBポートの数が少なすぎたり音声がオプションだったりと不便そうだったのでパス。
今のマシンからパーツをいくらか引き継いで安く済ませるのも考えたが、CPUとメモリの規格が1つ古いようだ。
CPUはAMDのAM3+、メモリはDDR3。
今の主流はAM4にDDR4で、互換性はないため流用できない。
さすがにストレージは可能だが、そのくらいだ。
私はAMDとRadeonが好きなのだが、最近のAMDのGPU搭載CPUに搭載されてるGPUの性能はなかなか馬鹿にできないらしい。
オンボードと言えばまずゲームには向かない、とりあえず表示ができればいいもの、という印象だったものだが、最近のは新しめの3Dゲームも割と動かせるのだとか。
というか、今のマシンに積んでいるびみょうな性能のRadeonグラボよりも性能がいいらしい情報もある。
私はパソコンで最新鋭のゲームなんてやらないし、AMDの比較的安価なCPUでグラボも必要ないとなれば、かなり費用が抑えられる。
今回購入を決めたのも、実はその辺の理由が大きい。
グラボを追加しなくて済むなら、ケースのサイズもかなり小さいもので大丈夫だろうし。
*
というわけでCPUにRyzen5 2400Gを用いた構成のパーツを注文し、今日全部届いた。
費用はおよそ6万円。
とはいえ、小さいサイズ故にパーツの干渉などが不安なので、絶対に必要でまず干渉も起こさないと思われるものに絞ってある。
後々光学ドライブとハードディスクを足す予定。
ハードディスクはケースに3.5インチベイしかないが2.5インチのものを積む予定なので、マウンタなんかも必要になるだろう。
その辺は実物を見ながら決めていく。
土日に組み立て、動かせるようにしようと思う。
もやもやしっぱなしだが、とりあえずReenterEvent対応。
*
明日にはEnterEventを削除し、さっそくReenterEventを使用した処理を作る予定。
fg::StateEnterEventとfg::StateLeaveEventの仕様を変更することにした。
*
今までこれらのイベントは、fg::Stateをプッシュした時やポップした時に発生していた。
プッシュ時には、まず元のfg::Stateについてのfg::StateLeaveEventが発生する。
次にプッシュしたfg::Stateについてのfg::StatePushEvent。
最後にプッシュしたfg::Stateについてのfg::StateEnterEvent、という順だ。
ポップ時には元ステートについてのLeaveEvent、PopEvent、ポップ後のステートについてのEnterEvent、という順に発生する。
*
この仕様を変更して、プッシュ時におけるEnterEventと、ポップ時におけるLeaveEventを発生しないようにしようと思う。
1つのイベントが複数異なる状況で発生することで、扱いにくくなっているように思うのだ。
現在の仕様では、ポップ時に発生するEnterEventは処理したいけど、プッシュ時に発生するEnterEventは無視する、といったことができない。
そもそも、プッシュ時のEnterEventや、ポップ時のLeaveEventなんて無くても問題ないのだ。
プッシュ時はPushEvent、ポップ時はPopEventで代用できる。
なぜ無駄なものが付いていたのかと言えば、EnterEventとLeaveEventでリソース管理を手軽にしようとしてた、というのが主目的だった気がする。
EnterEventでリソースの確保、LeaveEventでリソースの解放を行うようにすれば、ステートが有効になるタイミングでリソースが確保され、またステートが無効になるタイミングでリソースが解放される。
しかし、そのようなやり方は少々安直すぎたように思える。
そのステートがプッシュされて有効になった、という状況と他のステートがポップされてステートが有効になった、という状況を完全に同一に捉えてしまうのはどうかと思う。
実装するにしたって、その2つの状況で同じ処理をさせる場合、処理を関数化しておいて各状況から同じ関数を呼び出す、という形にするべきだろう。
*
というわけで、EnterEventはポップ時、LeaveEventはプッシュ時にしか発生しないようにする。
そのような挙動の都合上、EnterEventについては名前も変えて、ReenterEventにしようと思う。
*
それとは直接関係ないんだけど、ReenterEventで直前のステートを特定できるデータを取得できるようにしようと思う。
ステートの管理はスタック構造により、あるステートに遷移した後元のステートに戻る、という関数のような、縦のつながりとでも呼べばいいのか、そのような挙動は表現できるようになっている。
問題は、あるステートから別のステートに遷移する、というGOTOのような挙動の表現だ。
*
現状絶対に表現できない、というわけではない。
あるステートをポップし、直後に別のステートをプッシュしてやればいい。
問題はそれをどこでやるのか、ということだ。
ステート内に、別のステートへの遷移処理を記述してしまうのはまずい。
ステートの遷移が1回に留まらず、何度も遷移を行なうような構成の場合、あちこちに遷移処理が書かれることになりとても把握できなくなってしまう。
まさにGOTOの濫用によるスパゲッティコード状態になってしまうことだろう。
*
そこで考えたのは、管理ステートという概念だ。
管理ステート自体は画面の描画やコントローラの入力などの処理は行なわない。
管理ステートは、別のステートへの遷移のみを行う。
管理ステートがプッシュされたら、管理ステートのPushEventでステート1に遷移する。
ステート1の処理が終了し、ポップされたら管理ステートのReenterEventでステート2に遷移する。
ステート2が終了したら次はステート3に、といった具合だ。
この構造なら、遷移処理は管理ステートに集約される。
で、このような構造を実現する場合、ReenterEventでは直前のステートを特定できないといけないわけだ。
ステートに対応する列挙型を用意し、それを管理ステートに持たせて現在のステートの把握に使う、という手もあるだろうけどどう考えてもバグの温床になるし。
他にもっといい方法があればいいのだが、思い付かないものは仕方がない。
記事を年単位で分割した。
*
編集はvimでやってるけど、開くのに時間かかるのがいやだったので。
前は月単位で分けてたけど、正直そんな頻繁に分割作業したくないので年単位にした。
*
skiaに対応するために、OpenGLコンテキスト周りを修正した。
skiaではステンシルバッファが必要だそうだが、現状ではステンシルバッファのサイズが0でサイズ指定もできなかったので、その辺のパラメータを設定できるようにした。
また、skiaで生成した画像をテクスチャとして扱う、みたいなことをするにはskia用のコンテキストは別に用意した方がよさそうだが、その場合コンテキスト間でリソースを共有できた方がいい。
なので、コンテキスト間のリソース共有もできるようにした。
*
これでkasuteraの作り直しに入れるかな。
skiaを使った簡単な描画処理は書けた。
*
最初の生成処理はあれでいいのか不安だが、ヘッダファイルを見ても他の関数は見当たらないし、多分いいのだろう。
書いてみた感じからすると、fgインターフェースにskia用の関数を追加するとか、そういうものは必要なさそうだ。
バックエンドにOpenGLを使用する場合の関連付けも、カレントコンテキストに対してのみ行なわれるため、fg::GLContextに対して直接なにかしたり、といった処理は必要ない。
skiaをモジュールとして扱うために、設定ファイルを追加すれば問題なく扱えそう。
例の仕様変更については対応済み。
*
テストについても、初期化関数を比較することで目的の画面に遷移していることを確認できるようになった。
で、kasuteraを作り直そうと思ってるけど、その前にグラフィックライブラリを導入することにした。
画面に表示する画像を用意するのが面倒なのだ。
1つや2つで済むならいいけど、これから画面作るたびにその画面を画像を描かないといけない、というのは面倒で気が滅入る。
なので、フォントファイルを参照して文字を描画したりなどできるライブラリを導入することにした。
*
適当に検索かけて、まず見つけたのはcairo。
このマシンにも導入されていて、ウィンドウマネージャのawesomeやwebブラウザのfirefox、chromeなどから使われているようだ。
既に導入されているというのは手が出しやすくていい。
更に調べていくと、skiaとかいうのも見つけた。
androidやchromeの描画に使われているらしい。
chromeはcairoにも依存してるんだけど、使うライブラリを切り替えたりできるんだろうか。
chromeだけでなくfirefoxでも使われているようだ。
cairoとはなんだったのか。
*
ともかく2つのライブラリが見つかった。
skiaの方が高速だの、有名なソフトウェアで使われててフィードバックが多いから開発が進んでるだのとskiaを評価する声が多いように思えるので、とりあえずskiaを使ってみようと思う。
思ったのはいいのだが、skiaのサンプルコードがあまりネットに落ちてない。
必死に色々調べた結果、skiaのリポジトリ内に参考になりそうなソースファイルがあった。
それを見ながら、実際に動作するコードを自分で書いてみるとしよう。
ちょっと詰まってる。
*
画面遷移についてのテストの記述が直接的でなく、私はとても不満げだ。
現状、特定の画面に遷移したかどうかのテストは、その画面で行なわれるべき動作が行なわれるか、という反応を見ている。
その画面に遷移したなら、コントローラをこのように操作することでこういう結果になるはずだ、ということだ。
はっきり言って回りくどく、非常に分かりづらい。
テストコードがテスト可能になるまで、必要な記述も多すぎる。
前述のようにコントローラ操作をテストに含めようとすると、その下準備もテストに記述する必要があるからだ。
しかも、下準備を書いてなくても動作自体はしてしまう。
動作するが、テストが成功することはない。
実装が間違っているのか、テストの記述を間違えているのか見分けがつかず、とても不安になってしまう。
*
これはとてもよくない。
画面におけるコントローラ操作のテストであれば、下準備をする必要があるのでまだ分かるが、なぜ単なる画面遷移のテストでそれらをしなくてはいけないのか。
大体、コントローラ操作が行なわれない画面だったら、この方法ではテストできなくなってしまう。
長々と書いたが、要するにそのfg::Stateがなんの画面のfg::Stateなのか、特定する方法が存在していないのはまずい。
fg::Stateに、画面を特定するためのデータを持たせる必要がある。
*
テストのためだけに余計な情報を持たせる、ということはしたくないが、テスト以外でも有効に活用できそうな気はする。
イベント中から画面特定データにアクセスできるようにすれば、1つのイベント関数を複数の画面で使い回し、画面ごとに処理を多少変える、といったことができるようになるだろう。
そういった処理は複雑化の元だからあんまりやりたくないけど。
*
で、問題は画面特定データとして何を持たせるか、だけど、fg::Stateの初期化関数のアドレスを持たせようかな、と。
現状扱ってるデータで考えるとそれが妥当、というかそれしかない。
fg::Stateに文字列で名前を付けるとかIDを振るとかも考えられるけど、扱いきれなくなって後悔する予感しかしない。
というわけでそんな感じの修正をして、テストも修正しよう。
*
いやむしろ、kasuteraについては作り直した方がいいかもしれない。
kasuteraは最初テストができず、テストをできるようにするためにbrownsugarを追加し、新たに追加した画面についてはテストができている。
まだあまり量書いてないし、未テスト部分にもbrownsugarを使ったテストコードを追加しようとしているけど、テストができなかった影響か構成がだめなのだ。
なんとなくでコードを書いてしまい、処理は一応まともに動いているようだが関数の引数などのインターフェースが悪い。
簡単に言えば扱いにくい、テストがしにくい。
それを修正してまともな形にすることもできるだろうけど、いっそ最初からテストファーストでまともなものを作った方がよさそう。
まだあまり量書いてないわけだし。