tekuto.net

メール:g.tekuto@gmail.com

twitter


2019/03/13 14:00

ユニークポインタ周りの新しい仕様を模索していた。

本当は画面周りを進めようと思っていたのだが、謎のタイミングで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()といった標準関数を利用可能

これら全ての要件を満たす仕組みを構築する必要がある。

そしてそれは実際形にできた。

いい加減長すぎるので、それについてはまた次回。


2019/03/09 02:45

パソコンを新しくすることにした。

能力的には問題ないのだが、大きさとか重さとか、その辺がよくない。

私のパソコンはパーツを集めて組み立てた、いわゆる自作パソコンだ。

作った当時はただなんとなくでかいのに憧れがあったので、それを反映したかのようにでかくて重い。

大きいと自然と空冷ファンも大きいものになり、空冷ファンというのは大体大きいほど静かになりやすいので、静音性はなかなか気に入っている。

また、拡張性が高く、後々何か追加することもやりやすく、その大きさ故にパーツのサイズによって干渉を起こす、ということも起きにくい。

しかしながら、大きく重いととりあえず動かしにくい。

今はゲーム専用に使っている、少し大きめのディスプレイにつなぎたくても現状では難しい。

ディスプレイが多少離れたところにあるため、パソコンを動かさなければならないからだ。

大きく重いので、動かすだけでも一苦労。

他にも掃除が面倒というのがあるが、これは大きさというより使ってるケースが悪いんだろうな。

静音性については、あまり気になるようなら回転数を落とすなり静音性を売りにしている物に買い替えるなりで対策できる。

拡張性なんてのは、大抵の場合何に使うかが明確でない場合に後から対応するためのものであり、使用目的が明確であれば必要性は薄く、デッドスペースにしかならない。

実際、今のマシンはハードディスク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インチのものを積む予定なので、マウンタなんかも必要になるだろう。

その辺は実物を見ながら決めていく。

土日に組み立て、動かせるようにしようと思う。


2019/02/06 03:00

もやもやしっぱなしだが、とりあえずReenterEvent対応。

明日にはEnterEventを削除し、さっそくReenterEventを使用した処理を作る予定。


2019/02/02 03:00

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では直前のステートを特定できないといけないわけだ。

ステートに対応する列挙型を用意し、それを管理ステートに持たせて現在のステートの把握に使う、という手もあるだろうけどどう考えてもバグの温床になるし。

他にもっといい方法があればいいのだが、思い付かないものは仕方がない。


2019/01/31 15:10

記事を年単位で分割した。

編集はvimでやってるけど、開くのに時間かかるのがいやだったので。

前は月単位で分けてたけど、正直そんな頻繁に分割作業したくないので年単位にした。

skiaに対応するために、OpenGLコンテキスト周りを修正した。

skiaではステンシルバッファが必要だそうだが、現状ではステンシルバッファのサイズが0でサイズ指定もできなかったので、その辺のパラメータを設定できるようにした。

また、skiaで生成した画像をテクスチャとして扱う、みたいなことをするにはskia用のコンテキストは別に用意した方がよさそうだが、その場合コンテキスト間でリソースを共有できた方がいい。

なので、コンテキスト間のリソース共有もできるようにした。

これでkasuteraの作り直しに入れるかな。


2019/01/26 03:10

skiaを使った簡単な描画処理は書けた。

最初の生成処理はあれでいいのか不安だが、ヘッダファイルを見ても他の関数は見当たらないし、多分いいのだろう。

書いてみた感じからすると、fgインターフェースにskia用の関数を追加するとか、そういうものは必要なさそうだ。

バックエンドにOpenGLを使用する場合の関連付けも、カレントコンテキストに対してのみ行なわれるため、fg::GLContextに対して直接なにかしたり、といった処理は必要ない。

skiaをモジュールとして扱うために、設定ファイルを追加すれば問題なく扱えそう。


2019/01/25 14:55

例の仕様変更については対応済み。

テストについても、初期化関数を比較することで目的の画面に遷移していることを確認できるようになった。

で、kasuteraを作り直そうと思ってるけど、その前にグラフィックライブラリを導入することにした。

画面に表示する画像を用意するのが面倒なのだ。

1つや2つで済むならいいけど、これから画面作るたびにその画面を画像を描かないといけない、というのは面倒で気が滅入る。

なので、フォントファイルを参照して文字を描画したりなどできるライブラリを導入することにした。

適当に検索かけて、まず見つけたのはcairo。

このマシンにも導入されていて、ウィンドウマネージャのawesomeやwebブラウザのfirefox、chromeなどから使われているようだ。

既に導入されているというのは手が出しやすくていい。

更に調べていくと、skiaとかいうのも見つけた。

androidやchromeの描画に使われているらしい。

chromeはcairoにも依存してるんだけど、使うライブラリを切り替えたりできるんだろうか。

chromeだけでなくfirefoxでも使われているようだ。

cairoとはなんだったのか。

ともかく2つのライブラリが見つかった。

skiaの方が高速だの、有名なソフトウェアで使われててフィードバックが多いから開発が進んでるだのとskiaを評価する声が多いように思えるので、とりあえずskiaを使ってみようと思う。

思ったのはいいのだが、skiaのサンプルコードがあまりネットに落ちてない。

必死に色々調べた結果、skiaのリポジトリ内に参考になりそうなソースファイルがあった。

それを見ながら、実際に動作するコードを自分で書いてみるとしよう。


2019/01/21 16:10

ちょっと詰まってる。

画面遷移についてのテストの記述が直接的でなく、私はとても不満げだ。

現状、特定の画面に遷移したかどうかのテストは、その画面で行なわれるべき動作が行なわれるか、という反応を見ている。

その画面に遷移したなら、コントローラをこのように操作することでこういう結果になるはずだ、ということだ。

はっきり言って回りくどく、非常に分かりづらい。

テストコードがテスト可能になるまで、必要な記述も多すぎる。

前述のようにコントローラ操作をテストに含めようとすると、その下準備もテストに記述する必要があるからだ。

しかも、下準備を書いてなくても動作自体はしてしまう。

動作するが、テストが成功することはない。

実装が間違っているのか、テストの記述を間違えているのか見分けがつかず、とても不安になってしまう。

これはとてもよくない。

画面におけるコントローラ操作のテストであれば、下準備をする必要があるのでまだ分かるが、なぜ単なる画面遷移のテストでそれらをしなくてはいけないのか。

大体、コントローラ操作が行なわれない画面だったら、この方法ではテストできなくなってしまう。

長々と書いたが、要するにそのfg::Stateがなんの画面のfg::Stateなのか、特定する方法が存在していないのはまずい。

fg::Stateに、画面を特定するためのデータを持たせる必要がある。

テストのためだけに余計な情報を持たせる、ということはしたくないが、テスト以外でも有効に活用できそうな気はする。

イベント中から画面特定データにアクセスできるようにすれば、1つのイベント関数を複数の画面で使い回し、画面ごとに処理を多少変える、といったことができるようになるだろう。

そういった処理は複雑化の元だからあんまりやりたくないけど。

で、問題は画面特定データとして何を持たせるか、だけど、fg::Stateの初期化関数のアドレスを持たせようかな、と。

現状扱ってるデータで考えるとそれが妥当、というかそれしかない。

fg::Stateに文字列で名前を付けるとかIDを振るとかも考えられるけど、扱いきれなくなって後悔する予感しかしない。

というわけでそんな感じの修正をして、テストも修正しよう。

いやむしろ、kasuteraについては作り直した方がいいかもしれない。

kasuteraは最初テストができず、テストをできるようにするためにbrownsugarを追加し、新たに追加した画面についてはテストができている。

まだあまり量書いてないし、未テスト部分にもbrownsugarを使ったテストコードを追加しようとしているけど、テストができなかった影響か構成がだめなのだ。

なんとなくでコードを書いてしまい、処理は一応まともに動いているようだが関数の引数などのインターフェースが悪い。

簡単に言えば扱いにくい、テストがしにくい。

それを修正してまともな形にすることもできるだろうけど、いっそ最初からテストファーストでまともなものを作った方がよさそう。

まだあまり量書いてないわけだし。


過去ログ

2018

2017

2016

2015

2014

2013