GUI のイベントスクリプティング
http://subtech.g.hatena.ne.jp/secondlife/20070813/1186999047 より
まずイベントドリブンなプログラミングに慣れてないのが一つで。Flex のイベントや自前イベントやをただ単に投げまくってると、とりあえずは動くけど後からメンテし辛いスパゲッティコードができあがる。このスパゲッティコードは goto 文が乱立するコードよりも酷く、goto だったら割と行き先は把握できるけど、イベントを投げまくってるだけだと、どこでどのオブジェクトがこのイベントを受け取るかが解らない。解りづらい。いちいちソースコード grep ですね、おめでたいですね。あのイベントが発生してから、そのイベントが終了したら発生するイベントが終了したらウィンドウ閉じて、その間は別のイベントはブロックして/発生しないようにして、とかもうわけわかんない。これも GUI プログラミングをしたこと無いからのような気もしなくもないけど。
たしかに、そこらへんが GUI プログラミングは難しい。
イベントハンドラの中でネットワーク待ちとか時間がかかる処理を書くと、UI 全体が固まってしまうから、処理を細切れにして非同期で呼び出してつなげていかないといけないところとか。
ここらへんは、C# + WindowsForms でも、Mac OS X + Cocoa でも、Flex でも一緒。
A,B,C のデータ三つのロードが終わったら処理をしたい場合のパターンとか。もちろん安易に直列でやれば楽なんだろうけど、直列も並列もどちらでも同じように書いて動かしたいときや、統一された書き方で書きたいときのパターンが解らない。
自分もそういうことやりたいときあるなぁと思ったので、ruby で作ってみた。
http://limechat.net/sample/eventscript.rb
自前でイベントループ回してるので、イベントループの部分を捨てて ActionScript で書きなおせば Flex でも使えるパターンになってると思う。
直列に実行したい場合には、
scenario = sequence(self) << [ get('http://www.google.com/'), get('http://www.yahoo.com/'), get('http://www.apple.com/'), get('http://www.mozilla.org/'), ] scenario.start
という感じで、並行実行したいなら、
scenario = concurrent(self) << [ get('http://www.google.com/'), get('http://www.yahoo.com/'), get('http://www.apple.com/'), get('http://www.mozilla.org/'), ] scenario.start
こんな感じに書ける。
実行結果を見ると、
scenario start url get start: http://www.google.com/ url get success: http://www.google.com/ url get start: http://www.yahoo.com/ url get success: http://www.yahoo.com/ url get start: http://www.apple.com/ url get success: http://www.apple.com/ url get start: http://www.mozilla.org/ url get success: http://www.mozilla.org/ scenario end
concurrent scenario start url get start: http://www.google.com/ url get start: http://www.yahoo.com/ url get start: http://www.apple.com/ url get start: http://www.mozilla.org/ url get success: http://www.google.com/ url get success: http://www.apple.com/ url get success: http://www.yahoo.com/ url get success: http://www.mozilla.org/ scenario end
concurrent のほうは並行実行できてることがわかる。
シナリオを入れ子にすることもできる。
scenario = sequence(self) << [ lambda { puts '* start' }, concurrent << [ get('http://www.google.com/'), get('http://www.yahoo.com/'), get('http://www.apple.com/'), get('http://www.mozilla.org/'), ], sequence << [ get('http://www.cnn.com/'), get('http://del.icio.us/'), lambda { puts '* waiting for 2 seconds' }, wait(2), concurrent << [ get('http://tumblr.com/'), get('http://flickr.com/'), ], ], lambda { puts '* end' }, ] scenario.start
実行結果は、以下のような感じ。(見やすいように整形済み)
scenario start * start concurrent scenario start url get start: http://www.google.com/ url get start: http://www.yahoo.com/ url get start: http://www.apple.com/ url get start: http://www.mozilla.org/ url get success: http://www.google.com/ url get success: http://www.apple.com/ url get success: http://www.yahoo.com/ url get success: http://www.mozilla.org/ scenario end scenario start url get start: http://www.cnn.com/ url get success: http://www.cnn.com/ url get start: http://del.icio.us/ url get success: http://del.icio.us/ * waiting for 2 seconds concurrent scenario start url get start: http://tumblr.com/ url get start: http://flickr.com/ url get success: http://tumblr.com/ url get success: http://flickr.com/ scenario end scenario end * end scenario end
全部非同期で、内部ではスレッドを起こして実行してるので、
scenario = sequence(self) << [ lambda { sleep 100000 }, ] scenario.start
とか書いても、イベントループは回ったままなので、UI は固まったりしない。
あと、直列で実行するときには、前の処理の結果を使いたい場合が多いと思うので、
scenario = sequence(self) << [ lambda { 2 + 3 }, lambda {|v| puts v }, get('http://tumblr.com/'), lambda {|v| puts v }, ] scenario.start
みたいに書けるようにしておいた。
scenario start 5 url get start: http://tumblr.com/ url get success: http://tumblr.com/ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> ... scenario end
(追記: 8.18)
flashrod さんが AS3 版を作られた。
http://d.hatena.ne.jp/flashrod/20070815#1187191083