グラフを作っていると、「ある処理が終わった後、任意の時間を経過して次の処理を呼ぶ」という
ような処理を行いたい場合がよくあります。
例えば、線を書いた後5秒後に四角、さらに5秒後に円を書くというときは、
public function drawAll():Void{ drawLine(); } public function drawLine():Void{ //線描画 this.lineTo(x,y); setInterval(this,"drawRect",5000); } public function drawRect():Void{ //四角描画 this.lineTo(x,y); setInterval(this,"drawPie",5000); } public function drawPie():Void{ //円描画 this.curveTo(x,y); }
という風に書いたりしますが、この記述方法の場合
- 描画順番が変わると、各メソッドの修正が発生する
- setIntervalのid管理が大変
という問題があります。
直感的にわかりやすいのが、おそらく次のような書き方だと思います。
public function drawAll():Void{ drawPie(); wait(5000); drawLine(); wait(5000); drawRect(); wait(5000); drawPie(); } public function drawLine():Void{ //線描画 this.lineTo(x,y); } public function drawRect():Void{ //四角描画 this.lineTo(x,y); } public function drawPie():Void{ //円描画 this.curveTo(x,y); }
これならば、描画順が変わったりしても変更はdrawAllのみに絞られますし、
かなり見通しはいいと思います。
がしかし、残念ながらActionScriptにはwait的な役割を持つ関数がありませんorz
ということで、次のような処理ができるクラスを導入してみました。
public function drawAll():Void{ var tc:TaskChain = new TaskChain(); tc.addTask("drawPie",this); tc.addTask("drawLine",this); tc.addTask("drawRect",this); tc.addTask("drawPie",this); tc.taskToTaskInterval = 5000;//タスクとタスクの間の秒数 tc.execute(); } public function drawLine():Void{ //線描画 this.lineTo(x,y); } public function drawRect():Void{ //四角描画 this.lineTo(x,y); } public function drawPie():Void{ //円描画 this.curveTo(x,y); }
wait()のパターンよりはわかりにくいですが、初めよりは分かりいいのではないでしょうか?
できることもwait()のパターンと同じです。
以下にTaskChainのソースを上げておきます。
TaskChainと、その内部で使うTaskクラスで構成されています。
class net.souko105.as2util.TaskChain { // 最初のメソッドを呼び出す間隔 public var startInterval:Number = 100; //処理と処理の間隔 public var taskToTaskInterval:Number = 100; private var first:Task; private var currentObj:Task; private var taskList:Array; static var chainMap:Object = new Object(); private static var key:Number = 0; public function TaskChain(){ taskList = new Array(); } //新規タスクを追加します。 public function addTask(callMethod:String,target:Object):Void{ var task:Task = new Task(); task.callMethod = callMethod; task.target = target; if(first==null){ first = task; currentObj = task; }else{ var old:Task = currentObj; old.nextExecuteTask = task; currentObj = task; } task.nextInterval = taskToTaskInterval; taskList.push(task); } //タスク実行 public function execute():Void{ if(first==null){ return; } //GC対象にならないように、staticのマップに格納 var k = ++key; var dt = new Date(); var mykey = k+"_"+dt.getTime(); chainMap[mykey] = this; var del:Object = new Object(); del.key = mykey; del.delKey = function(){ //実行終了後に削除 TaskChain.chainMap[this.key] = null; } addTask("delKey",del); first.id = setInterval(this.first,"execute",startInterval); } } class net.souko105.as2util.Task { public var id:Number; public var callMethod:String; public var target:Object; public var nextInterval:Number; public var preExecuteTask:Task; public var nextExecuteTask:Task; public function execute(){ if(this.id !=null){ clearInterval(this.id); } this.target[this.callMethod].call(this.target,this,preExecuteTask); if(this.nextExecuteTask != null){ this.preExecuteTask = this; this.nextExecuteTask.id = setInterval(this.nextExecuteTask,"execute",this.nextInterval); } } }
適当にパッケージ名を付けてパスに通せば動くと思います。
仕組みとしては、
- addTaskが呼ばれると、新規でタスクを作る
- 一番最初のタスクは、firstという変数に入れておく
- 既にタスクが登録されている場合、直前のタスクに次のタスクを呼ぶ処理を追加する
という感じです。
キモはすべてのタスクをstaticのマップに格納しているところで、これを行わないと2番目以降の
タスクが実行されません。staticのマップに引っ掛けないと、1番目のタスクが終了した時点で
残りのタスクがGC対象になるようで、消えてしまいます(ちなみにデバックモードだと動きます)
前の処理の戻り値を次の処理の引数に出来たりすると、もう少し便利かもしれないですね。