もぐてっく

人は1つ歳をとるたび、1ビットづつ大きくなれると信じてた。

mikutter:おっさんだってサインコサインを学びたい!(学んだやん)

f:id:moguno:20150830205041p:plain

はじめに

なんか巷では三角関数が空前の大ブームと言うことで、デスマから解放されて無駄にテンションの高い僕もノッてみることにしました!!

冷静に考えると、もぐのはおっさんであり社会に出てから三角関数なんてとんと使ってない勢であるわけで、テメェが手を動かしたところで元ネタに擦りもしてませんね。アレ。

「くくく・・・計画ミス」

取り敢えず三角関数で作れるプログラムと言ったらアナログ時計ですよね!
(異論はrescueして揉み消します。)

アナログ時計の針の動きは円軌道なので、極座標(原点からの距離と偏角で平面上の点を特定するモデル。偏角を変化させていくと円が描けるよ。)との親和性が高いです。その極座標をcairoの座標系である直交座標に変換する際に、件の三角関数を用います。

距離r、偏角θの点を直行座標x, yに変換するには、

x = r * cos(θ)
y = r * sin(θ)

ですね。

てな訳でアナログ時計画像を作るプログラムが誕生したので、現在時刻を吐き出すmikutter-datasource-clockのアイコン画像にして終わりにしようとしたのですが、ここで問題が。

mikutter内での画像の指定方法はファイル名またはURLのみで、cairoで作成したPixbufを指定することはできません。どうしてもと言うならPixbufをテンポラリディレクトリにpngとかで保存すればいいですが、まぁ格好悪いなと。

ならばモンキーパッチとなるのですが、アイコン画像を差し替えるだけじゃつまんないなと思って、前からやってみたかった大技にチャレンジすることにしました。

ミクぺたをペタペタする

mikutterのTLの描画はGdk::MiraclePainter(以降「ミクぺた」)と言うクラスで行われており、Messageごとにそのインスタンスを保持しています。

と言うことは、オブジェクト志向的発想でミクぺたのサブクラスを作ってやれば、TLに独自レイアウトの描画が出来るはずです。

出来ました。割と簡単。

まずはGdk::MiraclePainterを継承してrender_to_context()をオーバーライドします。
引数のcairocontextにあなたのパトスをぶつけましょう。

次にMessageにミクぺたのインスタンスをアサインしているGtk::CellRendererMessageのcreate_miracle_painter()をモンキーパッチして、適宜さっきのサブクラスのインスタンスを返せばOKです。

んで、出来たのがこれ

github.com

でっかいみくったーちゃん時計がホームTLの上部に居座ります。割と邪魔です。

しかしながら、mikutterは時計すら無いヤバい環境で動かされることもしばしばなので、一周回って案外便利かもしれません。

mikutterパッケージマネージャ"Packaged"でもインストールが可能です。こちらもよろしくです。moguno.hatenablog.jp

mikutterにおけるスレッドと並行処理機構のまとめ

mikutterの薄い本Vol.8に掲載したmikutter-datasource-aclogを入れてから、なんかたまにGUIが重たくなる気がして来ました。

原因を調べたところ、mikutterイベントon_period内のhttp通信処理がたまに重くなるのせいで、メインスレッドのGUIの動作を阻害していたためでした。

てっきり別スレッドで動いてると思ってたよ。あらあらうふふ

いい機会なので、mikutterに於けるスレッドの取り扱いと、mikutterフレームワークの並列機構との関係を整理してみようと思いました。

mikutterに於けるスレッド

mikutterのスレッドは「メインスレッド」と「サブスレッド」に大別されます。
特徴はそれぞれこんな感じです。

メインスレッド

Rubyがデフォルトで持っているスレッドです。
mikutterにおけるメインスレッドは、初期化処理の後にGUIを司るGTKのメインループに支配されます。

mikutterのコアやプラグインは、後述のDelayerやGTKのシグナル処理を使ってメインスレッドで処理を行います。
メインスレッドの注意点は、インターネット通信などの「重い処理」を行うと、その分GUI処理が滞って「もっさり」や「プチフリ」を引き起こすことです。

サブスレッド

ここでは便宜上メインスレッドでないスレッドを「サブスレッド」と呼称します。
mikutterフレームワークではスレッドの使用に制限はなく、プラグインでサブスレッドを適宜生成して使用可能です。
サブスレッドはGUI処理を阻害せず「重い処理」を行える一方、サブスレッドからGTKGUI部品を操作することは出来ません。

mikutterフレームワークの並行処理機構

さて、各スレッドの得手不得手が整理できた所で、次はmikutterで使える並行処理について確認していきましょう。

Delayer

与えたブロック(処理)をメインスレッドで実行します。
DelayerはGTKのメインループと協調を取っており、GUI処理がひと段落するタイミングまで実行が保留される所がちょっとだけ注意です。
(/core/plugin/gtk/delayer.rbのGtk::idle_add_priority()で暇になった時にDelayerを実行する処理を設定しています。)

この性質を利用して、擬似的にサブスレッドからGUIの操作が行えるようになります。

Thread.new {
  # サブスレッドでなんか重い処理

  Delayer.new {
    # メインスレッドでGUI操作
  }
}

Reserver

Reserverは引数に指定した時間待った後にブロックを実行する機構です。
Reserverは専用のスレッドで時間待ちをしているため、Reserver処理もサブスレッドでの実行されます。
つまり、GUIをいじる場合はDelayerを使用する必要が有ります。

# 30秒後に処理を実行する
Reserver.new(30) {
  # ここはサブスレッドで実行される
}

イベント

mikutterイベントは内部でDelayerが噛むので、必ずメインスレッドで実行されます。

データを定期的に取得するのに便利なon_period(1分周期で発生)も例外ではなく、そこに冒頭のhttp処理なんかを書くと「もっさり」の原因となります。
(冒頭のやらかしですね。)

サブスレッドを作ってさっさと明け渡しましょう。

# 1分周期イベント
on_period { |service|
  # ここはメインスレッドなので、速やかに明け渡しましょう。
}

フィルタ

イベントと似た構文を持つフィルタは呼び出し元に値を返す必要があるため、処理が実行されるスレッドは呼び出し元と同じ(メイン、サブ特定できない)になります。

まぁ、フィルタ処理でスレッドやDelayerによる並行処理は原理上無意味なので、困ることはないと思います。

しかしながら、フィルタ内で重い処理を行うと後続の何らかの処理が遅れるはずなので、重い処理を伴うデータはReserverやon_periodなどで予め非同期に作っておくのが良いと思います。

on_period { |service|
  Thread {
    # 重い処理は予め実行しておく
    @data = heavy_proc()
  }
}

filter_hoge { |args|
  # フィルタでは@dataを使って答えを返すだけ(排他とかちゃんとしてね)
 [args]
}

GTKシグナル処理

signal_connect()やssc()で登録するGTKのシグナル処理はメインスレッドで動作します。
通常、プラグインからGTKシグナルを直接触ることがないため気にすることは無いですが、頭の配線から色々リークしているプラグイン作者は注意しましょう。

おまけ:Twitter APIは重い処理だよね?

Twitter APIも比較的低速な回線での通信が絡むので、重い処理と言えます。
しかし、mikutter独自のTwitter APIライブラリ「mikutwitter」では原則API呼び出しごとにサブスレッドを生成する仕様なので、「もっさり」が発生しない様になっています。

# APIをコールすると新たにサブスレッドが作られる
Service.primary.search(params).next{ |res|
  # Deferredによるスレッド実行後処理(next())もサブスレッドで動きます
}

mikutterメモ:プラグインスラッグの注意点

.mikutter.ymlで指定した:slugと、Plugin.create()の引数で指定したスラッグが違うと、Plugin::specがnilになります。

これは、Plugin::specの初期化は.mikutter.ymlで定義されたスラッグのインスタンスで行われ、プラグイン自身はPlugin.create()で指定したスラッグのインスタンス(前者とは別のインスタンス)で行われるためです。

結果、Plugin::specを参照する機能を使うとポコっと落ちます。
具体的にはアイコンセットです。ぐふぅ。

当たり前ですが、両者のスラッグは統一したほうがいいですね。