(日本語) Raspberry PiでビルドしたPygameでハマった話

Sorry, this entry is only available in Japanese. For the sake of viewer convenience, the content is shown below in the alternative language. You may click the link to switch the active language.

あくあたん在室モニターの話の続き.

なんとかSSLのhandshakeでエラーを出さなくなったので,次に進みます。
Python 3.7.3をソースからインストールしたので,pygameをインストールしないといけないです。

$ sudo apt install libsdl-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libsmpeg-dev libportmidi-dev libavformat-dev libswscale-dev
(省略)
$ sudo pip3 install pygame
(省略)
Successfully installed pygame-1.9.6

インストールは正常に終了.
あくあたん在室モニターをおもむろに起動します.

Fatal Python error: (pygame parachute) Segmentation Fault

Thread 0x71523470 (most recent call first):
  File "monitor.py", line 444 in run
  File "/usr/local/lib/python3.6/threading.py", line 916 in _bootstrap_inner
  File "/usr/local/lib/python3.6/threading.py", line 884 in _bootstrap

Current thread 0x76ccd000 (most recent call first):
  File "monitor.py", line 1151 in draw_character
  File "monitor.py", line 1391 in draw
  File "monitor.py", line 192 in main
  File "monitor.py", line 1503 in <module>
Aborted

なんで落ちるの….しかもSegmentation Faultとか、およそPythonのエラーとは思えない。
1151行目はこれ.画面に文字を1文字表示するルーチン.
なお,あくあたん在室モニターでは画面上にメッセージがでるときに,昔のRPG風に1文字ずつ表示しています。ここはその部分。

surf,rect = self.myfont.render(ch,self.color)

いやいや,他のところでちゃんと表示してるやん.なんでこの場所だけ落ちるの.
この文を含むメソッド(draw_character)の呼び出し元をチェックします。

self.msg_engine.draw_character(self.surface, (dx,dy), ch)

至って普通で特に問題が見受けられません。しかも,どうも途中までは表示してるぽいのです。ならば,何を表示したのか見てみましょう。

print("draw {},{} {} ({})".format(dx,dy,str(ch),hex(ord(ch))))
self.msg_engine.draw_character(self.surface, (dx,dy), ch)

これで実行したら、どこの文字で落ちたか分かります。

draw 6,6 あ (0x3042)
draw 22,6 く (0x304f)
draw 38,6 あ (0x3042)
draw 54,6 た (0x305f)
draw 70,6 ん (0x3093)
draw 86,6 が (0x304c)
draw 102,6 8 (0x38)
draw 118,6 - (0x2d)
draw 134,6 3 (0x33)
draw 150,6 2 (0x32)
draw 166,6 0 (0x30)
draw 182,6 へ (0x3078)
draw 198,6   (0x3000)
Fatal Python error: (pygame parachute) Segmentation Fault

落ちた!まさかの全角スペース.なぜなんだ….その後,半角スペースでもダメなことが判明しました。空白文字全般がダメなのです。ただし、可視文字と一緒にスペースを混ぜた場合はきちんと動きます。あくまで、文字列全体で何も描画するものが無いものをrenderしようとすると落ちるのです。
症状を分かりやすくするために簡単に対話式でチェックしてみます。

>>> import pygame
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
>>> import pygame.freetype
>>> pygame.freetype.init()
>>> from pygame.locals import *
>>> myfont = pygame.freetype.Font("./font/rounded-mgenplus-1cp-bold.ttf",18)
>>> myfont.render("a",Color(255,255,255,255))
(<Surface(9x11x32 SW)>, <rect(0, 10, 9, 11)>)
>>> myfont.render(" ",Color(255,255,255,255))
Fatal Python error: (pygame parachute) Segmentation Fault

Current thread 0x76d49000 (most recent call first):
  File "<stdin>", line 1 in <module>
Aborted

これで再現しました。半角でも全角でも画面には表示されないスペースのみで構成される文字列をrenderしようとしたら,Segmentation Faultで落ちます。なお,これはRaspberry Pi上のPython 3.7.3 + pygame 1.9.6で発現したのですが,macOS上のPython 3.7.3 + pygame1.9.6では発現しません.どういう機種依存やねん、と悪態をつきつつ対策を考えます。

これ以上,なぜかを考えていても埒が明かないので,work aroundで対処します。chが空白文字のみだったらrenderをスキップしちゃえば良いのです。(dx,dyは事前に計算して位置決めしているので,これでも画面表示はくずれないようになっています。)

if ch != " " and ch != " ":
    self.msg_engine.draw_character(self.surface, (dx,dy), ch)

これで表示可能になりました。
めでたしめでたし。