中機能タイマー
uu.create.timer()は、window.setInterval() と window.setTimeout() の代替手段となるものです。
さほど高機能でもないので、中機能ってことで。
特徴:
- 低負荷
- 多数のタイマー(10本〜)を同時に使用する状況下で、CPU負荷がsetInterval(),setTimeout()よりも20〜30%程軽い。
- サスペンド(一時停止)とレジューム(再開)が可能。
- ループ回数を指定可能。無限ループも可能。
- ループ回数を1にすれば、setTimeout(), 無限にすればsetInterval()互換の動作。
- delay時間をプログラマブルに変更可能。
- アニメーションGIFのような処理がとても簡単。
- メモリコンパクションで使い古しのリソースをスッキリ削れる。
使いどころ:
- もっさりなサイト(エフェクトを多用しているサイト)なら導入するだけで効果があるかも。
- ゲームなどの多数のオブジェクトを同時多発的に動かすシーンで活躍するかも。オブジェクトが突然増えてもガクガクしづらいと思う。
弱点:
- setInterval() や setTimeout() で事足りるならそっちがベスト。KISS大事。
- 正確な時間を計ろうとするならこれは使えない。
- 273785年以上は連続稼動できない。10万年に一度は再起動が必要。
仕組みとか雑記:
- 「ワーカースレッドに一本化し、ループ処理することで、スレッドコンテキスト(オーバーヘッド)を減らす。」といった定石を、JavaScriptの世界に持ち込んでみた。
- オブジェクトが増えても遅くなりにくい
- 主要ループ内での、変数のムーブや演算を極力減らす。
- 現在時刻とタイムアウト予定時間を比較する if (d.next && t >= d.next) {} がヘビーローテーションなので、ここの演算量を減らすための努力とか。
- 関数呼出し前に次回のタイムアウト予定時間を算出しているのは、10msで処理が終わらない関数が出てきた場合(その時点でまずいのだが)でも、suspendやMemory Compactionに与える影響を減らすため。
- ヘビーローテーション内に try / catch 書いちゃうと遅くなる。
- 安全にsuspendとかMemory Compactionする仕組みで軽い方法はないもんか?
- JavaScritpでスレッド間の同期(アトミックな動作)ってできたっけ?
- 無理なら無理で安全時間を多めに取るとか、スレッド止めちゃうしかないんじゃない → 結局 Memory Compaction 作業中は、スレッドを止めることに。
- 安全にsuspendとかMemory Compactionする仕組みで軽い方法はないもんか?
- regist()で与えられたdelay時間を、基準クロック(baseClock=10ms)で割っていないため、より自然に動作する。
- スピードも十分。
- this.tick は現在時間を保持する変数ではなく、何回呼ばれたかをぼんやり覚えておく変数なので、実時間と大幅にずれても問題はなし。
- 正確に現在時間がほしければ、適時 (new Date().getTime()) すべき。
uupaa.jsに組み込まれた状態のソース。今回は単体で動作するHTMLソースは無し(uupaa.jsが必要)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>JavaScript::timer test</title> <!--[if IE]><script type="text/javascript" src="../../lib/xpath.js"></script><![endif]--> <script type="text/javascript" src="../../uupaa.js"></script> <style type="text/css">body { background-color: black; color: white; }</style> </head> <body> <script type="text/javascript"> window.onload = function() { // 一定時間(0ms, 100ms, 200ms, 300ms)で、ステータスバーに、"Hello"を追加 var fn1 = function() { window.status += "Hello"; }; var delay = function(n) { return n * 100; }; // create delay value uu.tm10.regist(fn1, delay, 4); // 10ms毎にステータスバーに"."を追加 var fn2 = function() { window.status += "."; }; uu.tm10.regist(fn2, 10, 100); // 100回ドットを打ったら停止 } </script> </body> </html>
---- uupaa.js から抜粋 ---- // --- timer extend --- uu.create.timer = uu.create.basicClass(); uu.create.timer.prototype = { construct: function(baseClock /* = 10ms */) { this.baseClock = baseClock || 10; this.tick = 0; this.itID = 0; // interval timer id (window.setInterval result) this.data = []; this.lock = false; // true: under memory-compaction execution }, destruct: function() { var me = this; uu.registWindowEvent("onunload", function() { me.lock = true; me.suspend(); me.data = null; }); }, /** regist delay function * * 遅延評価関数を登録します。 * suspend状態ならresumeします。 * * @param function fn 遅延評価関数を指定します。文字列も指定可能です。 * @param number/function delay 遅延時間をms単位で指定します。 * 遅延時間を返す関数を指定することもできます。 * 呼び出し回数を引数に関数を呼び出します。呼び出し回数は0以上の整数です。 * @param number loop 評価回数を指定します。0なら無限, 1なら1回, 2なら2回だけ評価します。 * @return number unregist()で使用するIDを返します。 */ regist: function(fn, delay, loop /* = 0 */) { var dfn = uu.isF(delay) ? delay : undefined; var id = this.data.length; this.resume(); this.data[id] = { id: id, next: this.tick + (dfn ? dfn(0) : delay), count: 0, dfn: dfn, fn: (uu.isF(fn)) ? fn : new Function(fn), delay: delay, loop: loop || 0 }; return id; }, /** unregist delay function * * 登録済みの遅延評価関数を抹消します。 * * @param number id regist()が返すIDを指定します。 */ unregist: function(id) { this.data.some(function(v, i) { return (v.id === id) ? (v.next = 0, true) : false; }); }, /** 一時停止 */ suspend: function() { this.itID && uuw.clearInterval(this.itID); this.itID = 0; }, /** 再開 * * @return boolean 再開成功でtrueを返します。失敗でfalseを返します。 */ resume: function() { if (!this.itID && !this.lock) { this.tick = (new Date()).getTime(); this.itID = this.run(); } return !!this.itID; }, /** Memory Compaction */ mc: function() { var me = this; me.lock = true; me.suspend(); uuw.setTimeout(function() { var i = 0, sz = me.data.length, tmp = []; for (; i < sz; ++i) { me.data[i].next && tmp.push(me.data[i]); } me.data = tmp; // swap me.lock = false; if (me.data.length) { me.resume(); } }, Math.min(me.baseClock * 4, 100)); // 100ms wait(根拠なし) }, /** loop * * @private */ run: function() { var me = this; return uuw.setInterval(function() { var i = 0, sz = me.data.length, t = me.tick += me.baseClock, d; for (; i < sz && !me.lock; ++i) { d = me.data[i]; if (d.next && t >= d.next) { d.next = (d.loop && !(--d.loop)) ? 0 : t + (d.dfn ? d.dfn(++d.count) : d.delay); d.fn(); } } }, me.baseClock); } }; uu.tm10 = new uu.create.timer(10);
使い方
- 登録
- 抹消
- uu.tm10.unregist(id); で登録してある遅延評価関数を抹消します。
- 一時停止と再開
- uu.tm10.suspend(); で一時停止。 uu.tm10.resume()で再開します。
- スッキリ
- uu.tm10.mc(); でMemory Compactionします。mc中はタイマーを止めます(suspend→resume)。
そのうちに、http://pigs.sourceforge.jp/wiki/index.php?uupaa.js でリリースする予定です。
Google Code Archive - Long-term storage for Google Code Project Hosting.で、より改善された最新版をリリースしています。