Gentooの「依存USEフラグ」をお掃除しよう

Gentooってべんりですよね. たとえば, あるパッケージでpython2系のコードはいれとかなくてもいいな〜と思ったら, そのパッケージのUSEフラグからPYTHON_TARGETS=python2_6を落としておけば, python2.6のコードなしでインストールができちゃう.

ところが, 時にはこれがめんどいことになりますね. 新しくパッケージを入れようとしたら, python2しかまだ対応してなくて, おかげで依存するパッケージ群にPYTHON_TARGETS=python2_6を生やしてまわることになったりしますね. まあ--autounmask-write使えば, だいたい勝手に書かれるんですけども.

実際に問題になるのは, このほしかったパッケージがpython3対応した時で. ここでほしかったパッケージからPYTHON_TARGETS=python2_6を落とせるわけですが, 依存するパッケージからPYTHON_TARGETS=python2_6を落とせるかどうか, それは注意深く依存関係をたどっていくか, 実際に落としてビルドを試すまでわからない. こんなふうに依存で入ってきた「USEフラグ」というのは後片付けがとてもめんどい.

これが依存パッケージであれば, emerge --depcleanでいいんだけれど, 「依存USEフラグ」まではきれいにしてくれない. とてもつらい. しかも, それが何段も依存関係を作ったりもする. かなりつらい.

と, いうことで, そういう「依存USEフラグ」がworldパッケージまでちゃんと依存のチェーンがつながっているかを解析するスクリプトをひとつ書きました. 「依存USE」になるのはこういうの:

TARGET_REGEXP = r"^((?:ruby|python)_targets_|python_single_target_|abi_x86_)"

github.com

実行するだけで, こんな感じで消してもよさそうなUSEフラグを出してきます. あとはこれを眺めてお掃除しちゃるといいわけですね.

$ python3 chain-dep-scanner.py|grep ruby22|head
dev-ruby/ruby-clutter:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-clutter-gstreamer:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-gtk2:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-gdk3:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-vte3:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-clutter-gdk:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-clutter-gtk:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-webkit2-gtk:0[ruby_targets_ruby22] is not pulled by any world packages
dev-ruby/ruby-gnome2:0[ruby_targets_ruby22] is not pulled by any world packages

ただ実行してみるとわかるんですが, 現状あちこち問題はあります.

たとえば, "app-accessibility/espeak:0[abi_x86_64] is not pulled by any world packages"のように, グローバルでオンにしてるようなUSEに関しては出てきてもあんまりうれしくないです. また, PYTHON_TARGETS=python3_6としてるけど, python3.5までしかまだ対応してないよというパッケージも, disableにする余地がないので出てきてほしくないわけですね. 多分pythonだったらこれにしたい〜, rubyはこれにしたい〜という願望を引数に渡して, それが実現できそうなpackageだけリストするモードとかあるといいんでしょうね. そのうち実装します(それかだれかPRでも・・・・)

GCE でemerge -e world, インスタンスはどれにする?

今後記事で詳しく書いていく予定なのだけれど, 最近Gentooの開発用のDockerイメージを作っている. Gentoo 公式のDockerイメージの gentoo/stage3-amd64 から ACCEPT_KEYWORDS=~amd64 emerge -uDN --with-bdeps=y world したものだと思えばよい.

https://hub.docker.com/r/naota/gentoo-devel/

毎日, そういうビルドを自分のマシンで回すというのもめんどうなので, GCE (Google Compute Engine)でやっていくことにした. こんな感じの ansible playbook でドーンとVMとsystemdのserviceがデプロイされる.

github.com

 

さて, ここでインスタンスをどうしするのがベストかな〜というのが問題となる. f1-micro や g1-small といった共有コアインスタンスから, 最大96コアのインスタンスまでいろいろありますね.

マシンタイプ  |  Compute Engine ドキュメント  |  Google Cloud Platform


とりあえず Gentooのビルドにどれがいいか調べてみよう. 標準マシンタイプでコア数を1, 2, 4, 8, 16まで変えてみる. (それに応じてメモリ量も増える) ワークロードとしては, emerge -e world (システムに入っている全パッケージのビルドし直し) をやっておく. Dockerのボリュームは, 永続SSD上に載っていて, ビルドもこのSSD上で行われる.

(なお, 16コアまでなのは, デフォルトで24コアまでのクォータがかかってるから. 96コアまで制限外してって言ってみたら先にマネーをある程度チャージしてくれとのこと, なるほど〜)


はい, ではemerge -eの時間はどうなったかなというのが下. 1コアだと4時間ぐらいかかっているのが, 16コアだと80分まで減ってる. すごい!!

f:id:meech:20180222211951p:plain


いや, でもちょっと待って. インスタンスはコア数によって価格が変わってしまいますね. 1コアのプリエンプティブ料金で1時間0.01ドル, 2コアで0.02ドルと, コア数 X $0.01 の料金がかかる (ゾーンはアイオワ, 多分一番安い). ということで, このワークロードを毎日1ケ月, 30日まわしたらおいくらかかる?というのが下

 

f:id:meech:20180222212023p:plain

いやぁ………コア数増やすほどコストは上がってますね…かなしい.

 

さて, ここでもう1つワークロードを見ておきましょう. 上記のように開発用のイメージを作るという場合であっても, 毎日同じパッケージをビルドする必要はありません. 一度ビルドしたものはバイナリパッケージとして保存しておいて, 新しい(アップデートされた)パッケージだけビルドしたらよくない? ということですね.

ということで, 次のワークロードは全部バイナリパッケージがあるという状態で, emerge -e world します. バイナリパッけージを展開して全部いれ直すというやつですね.

結果はこちら, ついでにコストも. コア数が1つで16分, 16個で10分とそれなりに減ってはいるけれどIOの方が多いワークロードだろうし, ビルドと比べるとそこまでは減っていってない. そんなもんだね

f:id:meech:20180222212049p:plain

f:id:meech:20180222212100p:plain


ということで, GCEでGentooの(最初の状態のシステムでの)ビルドをまわすとだいたいこの2つの線の間になる, ということでしょう. 月5ドルぐらいなら16コアでもいいかな〜とかコスパを考えて1コアかな〜とかそこはいろいろですかね.

 

f:id:meech:20180222212128p:plain

 

あとは上の方のインスタンスだとメモリが十分にあるので, /dev/shm でビルドしたりできるかな. そのへんはまた今度.

それと, これはstage3初期からのemerge -e worldだから, コア数によるスケール効果が出にくい小さなパッケージが多い, ということもある. もしこれがKDE全部ビルドするなどなると, きっといろいろ変わってくるでしょうね.

(それにしても, 112パッケージ(圧縮した状態で, 223M)をバイナリでインストールで10分ってちょっと遅いような気もする. 他だとどんなもんなんだろ. Archとかはやそうだが)

GentooのBINPKGの複数インスタンスとか

FEATURES=binpkg-multi-instance の話をもっとと見かけたのでもっと

これまで Gentooのバイナリパッケージを生成すると, /usr/portage/packages/app-shells/bash-4.4_p18.tbz2 のような感じで, /usr/portage/packages の下に "<category>/<package>-<version>.tbz2" というファイル名で保存されてきた. この保存ファイル名は, Gentooにしては異常に窮屈なものだ.

Gentooのパッケージというのは, USEフラグをいろいろ変えたり, ビルド時に依存関係が書きこまれたりする. バイナリパッケージがあっても, もし, これらUSEフラグなどが異なればそのバイナリパッケージを使うことができない. もともとのファイル名だと, 「最後にビルドした時」と同じUSEフラグなどでなければ, そのパッケージは使えない. Gentoo でそんなことできるはずがない. いろんなマシン用にバイナリをホストしだすとますます大きな問題になってしまう. だからGentooでバイナリパッケージがはやらないんだ(ちがう?

さて, では FEATURES=binpkg-multi-instance にするとどうなるの? バイナリパッケージのファイル名が "app-shells/bash/bash-4.4_p18-1.xpak" のように "<category>/<package>/<package>-<version>-<buildID>.xpak" となる. <version>の後に<BuildID>がついた. このIDにより, 同じパッケージ・バージョンでも別のバイナリパッケージを置けるようになるのだ. やったぜ

BuildID はどうやって決まるのか? これはパッケージごとに最初は"1", その次は"2"みたいに単調にインクリメントされる. (なんかびみょうな実装なような気がするぞ, いいけど) そして, いまのところquickpkgは同じconfigかどうかなどチェックしていないようだ. すなわち, FEATURES=binpkg-multi-instance して, 同じパッケージを quickpkg するとどんどん BuildID が増えていく.


BINPKGの複数インスタンスは, こんな話でした


ちなみにだけれど, この新しいファイル配置は昔のファイル配置と互換性があるみたい. なんで, さくっと FEATURES=binpkg-multi-instance しちゃっていいと思う. いろんなマシン用にバイナリパッケージ作っている人には, ほんとにべんりになると思う.

GentooのBINPKGの圧縮とか複数インスタンスとか

ひさしぶりにman make.confなど見ていると, `BINPKG_COMPRESS` という変数があったのに気がついた. Gentooのバイナリパッケージの圧縮方法を選べるやつで, "bzip2, gzip, lz4, lzip, lzop, xz, zstd"に対応している.

話題のzstdもあるじゃん. どんぐらい圧縮時間やらサイズやら変わるんだろうね, ということでやってみた

 

結果とスクリプトはこちら

gist88666f8b36ae927edefef14486572565

とりあえずグラフにしてみよう.

f:id:meech:20180221125656p:plain

f:id:meech:20180221125708p:plain

(しれっとlzopないけど、package入れてなかったのでとばした)

サイズでいうとlzipかxz, まあxzの方がよく見るしxzを選ぶといい感じだろうか. ただパフォーマンスは悪い

パフォーマンスでいうとlz4とzstdがよい. ただサイズも見ると, zstd一択といったところか.

よく使われるbzip2とgzipは, よく知られている通りのぼちぼちな結果で, 互いにサイズとパフォーマンスにトレードオフがある. そうだね.


サイズで選んでxzなのか, パフォーマンスで選んでzstdなのか, (デフォルトのbzip2のぼちぼちでいくか)Gentooのバイナリパッケージにはどちらがよいだろうか.

xzの場合, 全体で300秒ほどかかっているが, 今回作ったパッケージは45個なので1つあたりの平均6.76秒.

一方, xzはzstdに対して72%のサイズになる.

5,6秒ならコンパイル時間に比べれば些細なものだし, サイズをとってxzでいいのではないか. 展開は速くていいみたいだしね, debianもそうしてるよね

 

 

 

 

投稿して気づいたけど、 複数インスタンスの話をしてねえ!!

FEATURES=binpkg-multi-instance ってのが増えて, USEフラグ違いとかdepend違いで同じebuildに対して個別にbinary packageを作れるようになった。べんり〜。これでいろんなUSEとかでもbinary packageを活用できるようになったぞ!

Gentooのbinary packageの夜明けじゃああ!

今日のLKML: テストしてテストしてからbitを立てる

こういうpatchが投稿されている

[10/12] writeback: only allow one inflight and pending full flush - Patchwork

full flushが一度に1つまでしか走らないようにしたいね、というものなのだけど、中身は本題には関係はない

patchの中に以下のようなコードがある

+       if (test_bit(WB_start_all, &wb->state))
+               return;
+
+       set_bit(WB_start_all, &wb->state); 

 "wb->state"のWB_start_allのbitが立っていたらreturnして、立ってなかったら立てる、というもの。このWB_start_allで"full flush"の排他をしていることになる。

厳密には、これは排他になっていない。1つ目のスレッドがtest_bit()してから、set_bit()するまでの間に、2つ目のスレッドがtest_bit()でbitを見にいくことがある。その場合、2つのスレッドが同時に"critical section"に入ってしまう。

ここを厳密な排他にするには、test_and_set_bit()を使う。

linux/atomic.h at master · torvalds/linux · GitHub

もちろんそんなことはkernel developerには、わかっている。patchのコメントは以下のように書いてある

It doesn't matter if we race a little bit on this, so use the faster separate test/set bit variants. 

 もともとfull syncが2つ以上一緒に走ると重いよね、ぐらいのものなのでここで厳密な排他は必要ないのだ。

こういうatomicityが必要でない場合には、test_bit()とset_bit()とを分けた方がいい時もある。

test_and_set_bit()を使うと必ずbitを立てる、すなわち値を更新してしまう。すると、cache lineがdirtyになる。頻繁に呼ばれ、多くの場合にbitが立っているような場所では、この「cache lineを汚すかどうか」が大きな違いになってくる。

 

で、頻繁に呼ばれるところで無駄にcache lineを汚さずにtest_and_set_bitしたいな〜となると以下のようなコードを書くことになる

if (test_bit(bit, addr) || test_and_set_bit(bit, addr))
...

 スレッドはpatchの内容をすっかり離れて、こうした"idiom"のdocumentないし、それもあわせてなんか関数作っとく?という話になる。そこで関数の名前の話になり、"test_and_test_and_set_bit"がいいとか、"test_then_test_and_set_bit"がいいとか、patchの話どうしたみたいな感じになっていってる。

まあ以下のように解説があるぐらい知られている話なのだけど、関数名として見ると"test and test and set bit"ってなんかむずっとする名前だよな・・・と思った次第

Test and test-and-set - Wikipedia

systemd-networkdがオンラインにならないの

sysmtedはなんでもできるつおい子なので、ネットワークの設定もやってくれる. つおい

どうやって設定書くの? ということで、 man systemd.network すると, こんな風に使える感じのサンプルが載っている

Example 2. DHCP on ethernet links

# /etc/systemd/network/80-dhcp.network
[Match]
Name=en*

[Network]
DHCP=yes

This will enable DHCPv4 and DHCPv6 on all interfaces with names starting with "en" (i.e. ethernet interfaces).

だいたいDHCPでよろしくやるとべんりなので、これをこぴぺするわけだが、これで困ったことになったという話。ちゃんと名前を明示しないと、オンラインになるまで待つserviceがタイムアウトまで待ってしまう

NFSと自動生成サービス

systemdは/etc/fstabからそれぞれのmount pointに対応したserviceを自動的に生成する。 mountもserviceの起動という形で行われる。たとえば、以下のようなfstabを書いておく

naota:/linux /remote/linux nfs defaults,hard,intr 0 0

 すると、以下のようにservice fileが自動生成される

$ sudo systemctl cat remote-linux.mount
# /run/systemd/generator/remote-linux.mount
# Automatically generated by systemd-fstab-generator

[Unit]
SourcePath=/etc/fstab
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
Before=remote-fs.target

[Mount]
Where=/remote/linux
What=naota:/linux
Type=nfs
Options=defaults,hard,intr

 この時mount先がNFSなので、ここには明示的には書いていないが、"network-online.target"への依存が自動的に付加される。すなわち, networkがオンラインになってからNFSにつなぎにいく, ということになる。

systemd-networkdでのnetwork-online.target

systemd-networdを使う場合、network-online.targetの前に、systemd-networkd-wait-online.serviceが起動される。

$ sudo systemctl cat systemd-networkd-wait-online.service
# /lib/systemd/system/systemd-networkd-wait-online.service
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.

[Unit]
Description=Wait for Network to be Configured
Documentation=man:systemd-networkd-wait-online.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
Requires=systemd-networkd.service
After=systemd-networkd.service
Before=network-online.target

[Service]
Type=oneshot
ExecStart=/lib/systemd/systemd-networkd-wait-online
RemainAfterExit=yes

[Install]
WantedBy=network-online.target

 このserviceは、"/lib/systemd/systemd-networkd-wait-online"を起動する、というもの。 このプログラム(systemd-networkd-wait-online)は、マシンのnetwork interfaceをなめて全て(loなどはのぞくが)つながるまで待つ。

具体的にどうやって「つながった」と判定されるのか見ていこう。/run/systemd/netif/links/*にinterfaceの数だけのファイルがある。たとえばloだとこんな感じ。

$ cat /run/systemd/netif/links/1
# This is private data. Do not parse.
ADMIN_STATE=unmanaged
OPER_STATE=carrier

特に管理対象じゃありません(ADMIN_STATE=unmaged)となっている。

一方でDHCPにつないでるやつだとこんな感じ。 DHCPIPアドレスをもらっていて、 ADMIN_STATE=configuredとなっている。

$ cat /run/systemd/netif/links/3
# This is private data. Do not parse.
ADMIN_STATE=configured
OPER_STATE=routable
NETWORK_FILE=/etc/systemd/network/80-dhcp.network
DNS=なんたらかんたら
NTP=なんとかかんとか
DOMAINS=
ROUTE_DOMAINS=
LLMNR=yes
MDNS=no
ADDRESSES=.....
ROUTES=.....
DHCP4_ADDRESS=.....
DHCP_LEASE=/run/systemd/netif/leases/3

 DHCP待ちなどのタイミングでは、これが"ADMIN_STATE=configuring"となっている。

結局のところ、systemd-networkd-wait-onlineはこのへんのファイルをなめて、ADMIN_STATE=configuring(やpending)の間ループするといった動きをする。

もしも、configuredにならない場合にはdefaultで120秒のタイムアウトで終了する

LANケーブルをさしてない口があるとつらい

といった状況でLANケーブルをさしていないportがあると、その口は"configuring"のままなのでsystemd-networkd-wait-onlineはタイムアウトするまで待ちつづける. 結果として、あちこち起動が遅れてめんどいなあという気持ちになる

それでどうするの?

Name=en*にしとくとdevice名見なくてもあれこれできるけど、オンライン待ちがうざいのでちゃんと名前を書こうねという話だった

 

変愚蛮怒をXft対応する話 Part1 X11における変愚蛮怒の描画コード

変愚蛮怒というRoguelikeゲームはLinuxでも動作するけれど, TrueTypeフォントが扱えなくてフォント指定がかなりだるいし, レンダリングがびみょう. 変愚蛮怒の描画をXftで行うようにした. フォントがきれいになって, お好きなフォントを使えて超ハッピー. この経験をふまえて, libX11のものをXft対応ってどうやったらいいのかを書く.

リポジトリはここ

github.com

変愚蛮怒ってなに

変愚蛮怒はMoria/Angbandから始まる*band系ローグライクゲームのバリアント(変種)の一種です。直接にはZangbandから派生しています。
鉄獄100Fに潜むラストボス『混沌のサーペント』を撃破して『*勝利*』を遂げるためには、キャラクターのレベルや装備だけでなく、*あなた*自身の習熟が求められます。

変愚蛮怒 公式WEB

まあ日本で派生したこんな感じなローグライクゲームで, 日本で派生しているので斬鉄剣持ったサムライっぽい人とか, 光の剣をはなつ不老不死にして史上最強のエスパーとか, 石仮面を持った吸血鬼とか出てくる. 主人公の方も剣術家になって天翔龍閃したり, 鏡使いになってラフノールの鏡したりできる.

https://pbs.twimg.com/media/DBjx6C6VoAA231H.jpg:large

最近だと東方成分の入ったバリアントもあるみたい. 当方東方ネタはあんまわからんのだけど, わかる人はよさそう.

www65.atwiki.jp

Linuxにおける変愚蛮怒

変愚蛮怒Linuxでも動いてとてもサイコー. ただコードが昔に書かれたものであるため, 現状とあわない点が様々ある

  • 内部コードがEUC (これは簡単に文字幅とか計算するのにはしかたなかったり)
    • 表示もこの前までEUCでしようとしていたがpatchが当たった: チケット #25917: UTF-8サポート - 変愚蛮怒 - OSDN
      • これもまだ, UTF-8読んでEUCにして, またUTF-8に戻して表示しててなんかアレなんだけどまあ既存ルーチンいじらないといけないしな〜
      • あと, UTF-8のCファイルをgcc-wrapというスクリプトで, EUCにconvertしてからcompileしていてまたむずかしい. atomicではないからCtrl-cするとめっちゃdiffが出たりする
  • TrueTypeフォントがうまくあつかえない
    • フォント指定には昔ながらの, XLFD (-*-*-medium-r-normal--24-*-*-*-*-*-iso8859-1ってやつ)を使う
      • めちゃ複雑わからん
    • mkfontdir, mkfontscaleなど使うとTTFも使えるらしい?んだけどうまく動かんわ
      • 昔動いてたんだけど…もうきえた?
    • bitmapフォントは…見た目やっぱアレ

現在のX11用描画コード

ということで, 変愚蛮怒をXftというTrueTypeフォントを扱えるライブラリで描画するように書き変えよう, という話.

今回は現在の描画コードについて整理する.

変愚蛮怒WindowsやらX11やらCursesやら様々な環境に対応できるように, 描画対象をTermと抽象化している. 各環境では以下のhookを実装して, 描画を行う.

いろいろhookはあるけれど, 主に仕事をするのはtext_hookになる. X11での実装は以下の通り:

(x, y)の位置に, 属性番号aで, nバイト長の文字列sを描画します, ということですね. Infoclr_set(clr[a]);で描画色を設定して, Infofnt_text_std(x, y, s, n);でテキスト描画している感じ. ここの(x, y)は文字単位での座標. ピクセル単位での座標へはInfofnt_text_std()内で変換する.

Infofnt_text_std()は以下のあたりから. ifdefとかが多いので抜粋する.

https://github.com/naota/hengband/blob/master/src/main-x11.c#L1605

static errr Infofnt_text_std(int x, int y, cptr str, int len)
{
    /*** Decide where to place the string, vertically ***/

    /* Ignore Vertical Justifications */
    y = (y * Infofnt->hgt) + Infofnt->asc + Infowin->oy;

    /*** Decide where to place the string, horizontally ***/

    /* Line up with x at left edge of column 'x' */
    x = (x * Infofnt->wid) + Infowin->ox;

    /*** Actually draw 'str' onto the infowin ***/
...
    /*** Handle the fake mono we can enforce on fonts ***/

    /* Monotize the font */
    if (Infofnt->mono)
...
    /* Assume monoospaced font */
    else
    {
...
        XmbDrawImageString(Metadpy->dpy, Infowin->win, Infofnt->info, Infoclr->gc, x, y, kanji, kp-kanji);
        free(kanji);
    }

    /* Success */
    return (0);
}

まず, 引数の文字単位の座標である(x, y)ピクセル単位に変換して, 描画位置を決める. フォントの高さ, 幅はどのフォントでも一定(monospaceフォント)と仮定しているので, 基本的にはx, yにそれぞれフォントの幅とフォントの高さを掛ければよい. これに描画の開始座標の(Infowin->ox, Infowin->oy)を足す. また, Y座標にはフォントのアセント(フォントのベースラインから上端までの高さ)Infofnt->ascを足す. これで, (x * フォントの幅 + ox, y * フォントの高さ + oy)ピクセルに文字の左上が当たるように描画されることになる.

あとはkanjiUTF-8が入っているので, そいつをXmbDrawImageString()で描画する. XサーバがMetadpy->dpy(XのDISPLAYのやつ)で, 描画先がInfowin->winになる. フォントがInfofnt->infoで指定され, 色がInfoclr->gcで指定される.

Xft化することを考えると, Infowin, Infofnt, Infoclrの初期化とかを見ておくとべんりそう.

(しかし, #define DPY (Metadpy->dpy)とかしてるのに全然使ってなくてあんまりコードの治安はよくない)

ウィンドウの管理: Infowin

struct infowin Infowinは以下のような変数. ウィンドウの管理をする.

struct infowin
{
    Window win;
#ifdef USE_XIM
    XIC xic;
    long xic_mask;
#endif
...
};

InfowinのデータはInfowin_prepare()で初期化されている.

Xft化する時に, ウィンドウ関係でなにか追加でほしい時は, 上の構造体に変数増やして, Infowin_prepare()で初期化したらいいかな, という感じ.

フォントの管理: Infofnt

struct infofnt Infofntは以下のような変数. フォントの管理をする.

struct infofnt
{
    XFontSet info;

    cptr name;

    s16b wid;
    s16b twid;
    s16b hgt;
    s16b asc;
...
};

ここに, フォント, その幅・高さ・アセントなどが保持されている. 初期化はInfofnt_prepare()で行う.

static errr Infofnt_prepare(XFontSet info)
{
    infofnt *ifnt = Infofnt;

    XCharStruct *cs;
    XFontStruct **fontinfo;
    char **fontname;
    int n_fonts;
    int ascent, descent, width;

    /* Assign the struct */
    ifnt->info = info;

    n_fonts = XFontsOfFontSet(info, &fontinfo, &fontname);

    ascent = descent = width = 0;
    while(n_fonts-- > 0){
        cs = &((*fontinfo)->max_bounds);
        if(ascent < (*fontinfo)->ascent) ascent = (*fontinfo)->ascent;
        if(descent < (*fontinfo)->descent) descent = (*fontinfo)->descent;
        if(((*fontinfo)->max_byte1) > 0){
            /* 多バイト文字の場合は幅半分(端数切り上げ)で評価する */
            if(width < (cs->width+1)/2) width = (cs->width+1)/2;
        }else{
            if(width < cs->width) width = cs->width;
        }
        fontinfo++;
        fontname++;
    }
    ifnt->asc = ascent;
    ifnt->hgt = ascent + descent;
    ifnt->wid = width;
...
    /* Success */
    return (0);
}

XFontsOfFontSet()でフォント一覧をとってきて, そのリストをなめて, 最大のアセント・デセントを求める. 幅は多バイト文字なら, フォント幅の半分と計算していて, 多バイト文字の幅が単一バイト文字の幅の2倍と仮定しているとわかる.

Xftでも, このへんでアセントとかを計算したらいいね, ということになる.

色の管理: Infoclr

Infoclrは以下の通り. GCがXlibのグラフィックコンテキストで, 描画色・背景色などを保持してくれる. ついでに, fg, bgでも描画色・背景色を保持しているけど, べつに使っていない.

struct infoclr
{
    GC gc;

    Pixell fg;
    Pixell bg
...
};

これまでの予想に反して, Infoclrのデータの初期化はinit_x11()の中で行われている.

errr init_x11(int argc, char *argv[])
{
...
    /* Prepare normal colors */
    for (i = 0; i < 256; ++i)
    {
        Pixell pixel;

        MAKE(clr[i], infoclr);

        Infoclr_set(clr[i]);

        /* Acquire Angband colors */
        color_table[i][0] = angband_color_table[i][0];
        color_table[i][1] = angband_color_table[i][1];
        color_table[i][2] = angband_color_table[i][2];
        color_table[i][3] = angband_color_table[i][3];

        /* Default to monochrome */
        pixel = ((i == 0) ? Metadpy->bg : Metadpy->fg);

        /* Handle color */
        if (Metadpy->color)
        {
            /* Create pixel */
            pixel = create_pixel(Metadpy->dpy,
                         color_table[i][1],
                         color_table[i][2],
                         color_table[i][3]);
        }

        /* Initialize the color */
        Infoclr_init_ppn(pixel, Metadpy->bg, "cpy", 0);
    }

angband_color_table[]にARGBが入っている. create_pixelPixell(X11の場合, XColorのtypedef)を作り, その色を描画色としてGCを設定する. Infoclr_set(clr[i])Infoclr = clr[i]となって, Infoclr_init_ppn()で色が設定される. 結局,のところ配列clr[]に各色でGCが格納される.

(ここで256個のGC作っているけど, 実際には8色しかない. alpha == 0xFFあたりでbreakしちゃってもいい気もする)

まとめ

Infowin, Infofnt, Infoclrがそれぞれ現在のウィンドウ・フォント・描画色を管理する.

InfowinInfowin_prepare()で, InfofntInfofnt_prepare()で, Infoclr(clr[])はinit_x11()で初期化される.

Term_text_x11()が指定位置にテキストを描画する. Infoclr_set(clr[a]);で色を変えて, Infofnt_text_std(x, y, s, n);でテキスト描画する. 実際に描画しているのは, XmbDrawImageString().

次回はXmbDrawImageString()をXftのテキスト描画関数に置き換える. それにあたって, 描画先・フォント・色の構造体が変わるので必要な処理を初期化関数に追加するなどについて書く.