こんにちは。昨日急にやることに決まった、弊社アドベントカレンダーの2日目の記事です。よろしくお願いいたします。
はじめに
今回は Processing に入門してみたので、そのことについて書きます。
Processing ですが、これがとてもリア充っぽい感じなんです。
たとえば典型的な Processing プログラマは以下のような生活を送っています。
次の場面を想像してください。土曜日の朝、ジョギングから戻っておいしいシリアルを食べたあなたは、温かいカモミール茶を飲みながらコンピュータの前に座っています。今日は古くからの友人の誕生日なので、Processing でグリーティングカードを作ろう考えます。誕生日の紙吹雪はどうでしょう。(Nature Of Code / Chapter.4)
で、朝から粒子系(パーティクルシステム)を設計したりするのです。
どうですか。興味をそそられるでしょう。俺もマジでこんな生活がしたい。
取り上げたコードについて
以下で公開しています。
Mac OS X で Processing がインストール済みであれば、以下のようにして確認することができます。
$ git clone https://github.com/ma2saka/java_netstats/
$ cd java_netstats
$ gem install sinatra
$ ruby app.rb &
$ open java_netstats.pde
What's Processing
プロセッシングと読みます。
「電子アートとビジュアルデザインのためのプログラミング言語であり、統合開発環境」とされています。オープンソースプロジェクトであり、本体はJavaで開発されているようです。
コードは主に Java (をベースにした独自言語)で書きますが、pythonや Javascript、CoffeeScript による記述も追加モジュールでサポートされています。また、ブラウザ上で動作させることのできる Processing.js が開発されており(http://processingjs.org )ブラウザアプリケーションのフロントエンドとしての利用も現実的に可能です。
学び方
いくつか書籍を拾い読みしましたが、この二冊が通読するには面白くてよかったです。
特にNature of Code の方は特定のプログラミング環境のトピックを完全に超えた内容で、普段データベースとクライアントと開発者の間でうろうろ活動している私にとってはすごく刺激的な本でした。この本と出会えただけでも、Processing に取り組んだ価値があったと言えるくらい。
本をわざわざ買わなくても、開発環境を落とすと大量のサンプルコードがついてきますので、まずはそれらを眺めるだけでも十分雰囲気はつかめるのではないかと思います。
とりあえず netstat でも可視化してみることにする
ローカルマシンの tcp コネクションを可視化するアプリを作ってみようと決めました。netstat を発行して結果を表示するシンプルな仕様です。
画面を設計する
これが設計書です。
ゆくゆくは、監視サーバーからの各サーバへの nrpe リクエストがソナーみたいに飛んでいって、返答があってピコーンって光って、ヤバければヤバイBGMが流れてヤバそうに真っ赤になるという予定(目指せ DAEDALUS!!!)なんですが、まずは小さなところから行ってみましょう。
構成は Web API + Processing
全て processing で完結させてもいいんですが、いずれ汎用的に使っていくために、
- 画面表示は processing
- データ取得は Web API
にします。
サーバーの実装を sinatra で書く
何も考えず、 gem install sinatra
して、vi app.rb
ってやって書き始めます。
require 'sinatra'
require 'sinatra/json'
get '/' do
# netstat で ESTABLISHED なコネクションを取得
lines = (`netstat -an -p tcp | grep ESTABLISHED`).split("\n")
# ヘッダを削除
lines.shift(2)
# ポート番号以下を削除
lines = lines.collect{|x| x.split(/\s+/) }
# ローカルホスト同士の通信を削除(適当)
list = lines.collect{|x| x[4].sub(/\.\d+$/,'') }.select{|x| x != "127.0.0.1" }
# 重複を取り除き、重複数をカウント
data = list.inject({}){|c,x| c[x] ||=0 ; c[x]+=1; c }
# json で返す
json(data)
end
できた。今の気持ち的にこれで十分。
ruby app.rb
して起動しておきましょう。
画面の実装を Processing で書く
試行錯誤しながら電車で書いてました。画面に円を描けて喜ぶとか久しぶりの感覚です。
通信処理
loadJSONObject
がそのままURLを処理できるので簡単です。戻り値の JSONObject は少し不便だけど、Java だしなぁと考えると腹が立たないこの不思議。
定期的に Web APIへリクエストします。ワイルドだから、エラー処理は気にしません。
JSONObject json = loadJSONObject("http://localhost:4567");
for (Object key : json.keys ()) {
int count = json.getInt((String)key);
...
}
背景をサイバーに
サイバーな感じの格子模様で許してもらいましょう。
// overwrite original function.
void background() {
fill(255, 60);
stroke(180, 50);
rect(width/2, height/2, width, height);
for (int i = 10; i< width; i+=20) {
line(i, 0, i, height);
}
for (int j =10; j< height; j+=20) {
line(0, j, width, j);
}
}
基本的に前回書いたキャンパスを上書きで変更していくため、rect(width/2, height/2, width, height);
で全体を背景で塗りつぶします。
今回は rectMode(CENTER);
で進めるので、起点は (0,0)
ではなく、(width/2, height/2)
としています。
ふわふわ浮く要素の親クラスをつくる
class FloatingItem {
PVector _location;
PVector _velocity;
float _color;
float _noise_count;
FloatingItem() {
_location = new PVector(random(width-50) + 25, random(height - 50) + 25);
_velocity = new PVector(0.0, 0.0);
_noise_count = random(10000);
_color = int(random(150))+50;
}
void update() {
_velocity.y = noise(_noise_count)-0.5;
_location.add(_velocity);
_noise_count += 0.01;
}
}
ふわふわ浮く要素の子クラスとして、HostとLocalHostを作成します。
display()
の実装がワイルドです。
class Host extends FloatingItem {
String _name;
Host(String name) {
super();
_name = name;
}
void update() {
super.update();
}
void display() {
fill(_color);
strokeWeight(1);
rect(_location.x, _location.y, 10, 10);
textSize(10);
textAlign(LEFT);
String text = String.format("[%s] ", _name);
text(text, _location.x - 10, _location.y - 10);
}
}
class LocalHost extends Host {
LocalHost(String name) {
super(name);
_location = new PVector(width/2, height/2);
}
void display() {
fill(_color);
strokeWeight(2);
stroke(100);
ellipse(_location.x, _location.y, 40, 40);
textSize(20);
textAlign(CENTER);
fill(0);
text("- localhost - ", _location.x, _location.y - 20);
}
}
パーリンノイズを実装した noise()
関数、最初はランダム値の親戚かなと思って使ってたのでハマりました。こいつはむしろ三角関数の親戚として扱うべきなんですね。
コネクションを表すビームを作る
最初、パケットを模した何かが飛んでいく感じでイメージしていましたが、途中で計算が面倒になって線の太さだけで表すことにしました。
作成された後、update()
を呼び出されるごとに細くなっていき、0を過ぎると isdead()
が true
を返すようになります。
ワイルドに接続元と接続先の内部変数に直接アクセスしています。
class Connection {
Host _dest;
Host _src;
int _life;
boolean _isfirst;
Connection(Host dest, Host src, int life) {
_dest = dest;
_src = src;
_life = life + 2;
_isfirst = true;
}
void update() {
_life -= 0.01;
}
void display() {
if (_isfirst) {
strokeWeight(10);
stroke(_dest._color, 70);
line(_src._location.x, _src._location.y, _dest._location.x, _dest._location.y);
_isfirst = false;
}
strokeWeight(min(_life, 4));
stroke(_dest._color);
line(_src._location.x, _src._location.y, _dest._location.x, _dest._location.y);
}
boolean isdead() {
return _life <= 0;
}
}
定期的にAPI通信してコネクション状態を受け取る処理を作る
上の通信処理を含む update()
を作ります。
こんなコードを社内のレビュアーに見せると切られるので見せません。
HashMap<Object, Host> hosts;
ArrayList<Connection> connections;
Host localhost;
int countdown = 0;
void update() {
if (--countdown > 0) {
return;
}
countdown = 20;
JSONObject json = loadJSONObject("http://localhost:4567");
for (Object key : json.keys ()) {
Host h = null;
if (hosts.containsKey(key)) {
h = hosts.get(key);
}
if (h == null) {
h = new Host((String)key);
hosts.put(key, h);
}
int count = json.getInt((String)key);
connections.add(new Connection(localhost, h, count));
}
}
なお、hosts
は永遠に増え続けるので動かし続けるといつか死にます。
メイン処理では、背景、ふわふわ、前景で書く
processing はレイヤーの概念がない(PGraphics を利用することでオフスクリーンバッファを実現できる)ため、書き出す順番大事です。奥から書きましょう。
毎回同じ内容を描画する場合はCPU時間がもったいないと思いました。調べてみると一度画面に出力したものを PImage に読み込んで使い回すとか、PGraphics に書き出して再利用するといった技があるみたいだけど、そんなに凝ったことするわけではないので全て1フレーム毎に書いていきます。
ということで、毎フレーム呼び出される draw()
は以下のように。
void draw() {
update();
// == background layer ==
background();
// == contents ==
localhost.update();
localhost.display();
for (Object key : hosts.keySet ()) {
Host h = hosts.get(key);
h.update();
h.display();
}
for (int i = connections.size () -1; i >= 0; i--) {
Connection c = connections.get(i);
c.update();
if (c.isdead()) {
connections.remove(i);
continue;
}
c.display();
}
// == forground layer ==
forground();
}
できたー!
できた、できたけど、ええと…?
真ん中の localhost の黒いのに目玉を書き足す誘惑に負けるところでした。妖怪バックベアード的なやつ。たまにふわふわ浮いていって画面外に行っちゃうところが愛らしい。
そんなわけで一晩いじり倒してみて、社内ツール化にはもうちょっと修行が必要そうだけど、面白いなーと思いました。
まとめ
いくつか学んだことをまとめますと、
- コンピュータは意外に高速だから、最初は draw() でじゃんじゃんループを回して良い。そんな難しいことを考えず、まずは動かすべき。
- ベタ書きを恐れない。
- 最初 python で書いていたが、意外にコード補完なしのJavaでも書ける。覚えなければいけないことはそんなに多くない。
- 日本語には弱い。エラーメッセージが化けるのはかなり辛い。(環境設定からフォント変更すればいけた! ということに気がつくまでに時間を使うのだった)
- 休日においしいカモミール茶を飲みながら古い友人の誕生日にグリーティングカードを作ろうと思うかどうかはけっこう微妙だけど、いい線行ってる気がする。
- 正しく作るより、作りたいものを作るという感覚は久しぶりで超楽しい。
と、いうわけで Processing 面白いからみんなやろうぜ! というお話でした。
さて、「じゃあ明日 @ma2saka さんお願いします」と突然振られたアドベントカレンダーですが、なんとか埋めることができました。よかったよかった。
3日目の @ftakao2007 さん、後はよろしくお願いいたします。