7 * http://please-sleep.cou929.nu/script-yielding-with-setimmediate.html
\r
8 * setImmediate での script yielding
\r
10 * http://ie.microsoft.com/testdrive/Performance/setImmediateSorting/Default.html
\r
13 * if( timer < 4ms ) useSetImmediate
\r
15 * if (window.msSetImmediate)
\r
17 this.timer = msSetImmediate(function () { t.stepper(); });
\r
19 else if (window.MozSetImmediate)
\r
21 this.timer = MozSetImmediate(function () { t.stepper(); });
\r
23 else if (window.WebkitSetImmediate) {
\r
24 this.timer = WebkitSetImmediate(function () { t.stepper(); });
\r
26 else if (window.OSetImmediate)
\r
28 this.timer = OSetImmediate(function () { t.stepper(); });
\r
33 // ------------------------------------------------------------------------- //
\r
34 // ------------ local variables -------------------------------------------- //
\r
35 // ------------------------------------------------------------------------- //
\r
40 * 現在時の ms を返します。 new Date().getTime() の値です。
\r
41 * @alias X.Timer.now
\r
43 * @return {number} ミリ秒
\r
45 X_Timer_now = Date.now || ( function(){ return +new Date; } ),
\r
47 // TODO X.AF.request, X.AF.cancel
\r
48 // http://uupaa.hatenablog.com/entry/2012/02/01/083607
\r
49 // Firefox 4 partial (request only), Mobile Firefox5 ready (request only), Firefox 11 ready (cancel impl)
\r
50 X_Timer_REQ_ANIME_FRAME =
\r
51 window.requestAnimationFrame ||
\r
52 window.webkitRequestAnimationFrame ||
\r
53 window.mozRequestAnimationFrame ||
\r
54 window.oRequestAnimationFrame ||
\r
55 window.msRequestAnimationFrame ||
\r
58 X_Timer_CANCEL_ANIME_FRAME =
\r
59 window.cancelAnimationFrame ||
\r
60 window.cancelRequestAnimationFrame ||
\r
61 window.webkitCancelAnimationFrame ||
\r
62 window.webkitCancelRequestAnimationFrame ||
\r
63 window.mozCancelRequestAnimationFrame ||
\r
64 window.oCancelRequestAnimationFrame ||
\r
65 window.msCancelRequestAnimationFrame ||
\r
68 X_Timer_INTERVAL_TIME = 16,
\r
69 X_Timer_TICKET_LIST = [],
\r
70 X_Timer_removal = null,
\r
71 X_Timer_skipUpdate = false,
\r
73 X_Timer_timerId = 0,
\r
74 X_Timer_busyTimeout = false,
\r
75 X_Timer_timeStamp = 0, // setTimeout に登録した時間
\r
76 X_Timer_waitTime = 0, // 待ち時間
\r
77 X_Timer_currentUID = 0, // 現在発火中の uid
\r
79 X_Timer_REQ_FRAME_LIST = [],
\r
80 X_Timer_requestID = 0,
\r
81 X_Timer_busyOnFrame = false,
\r
84 * requestAnimationFrame をセットします。
\r
85 * @alias X.Timer.requestFrame
\r
87 * @param {*} args1 コールバックのための最大で 3 つの引数を指定します。参考:__CallbackHash__
\r
90 * @return {number} タイマーID。1 以上の数値。タイマーの解除に使用。
\r
92 X_Timer_requestFrame = X_Timer_REQ_ANIME_FRAME ?
\r
93 (function( args1, args2, args3 ){
\r
94 var i = X_Timer_REQ_FRAME_LIST.length,
\r
96 i === 0 && ( X_Timer_requestID = X_Timer_REQ_ANIME_FRAME( X_Timer_onEnterFrame ) );
\r
97 f = X_Closure_classifyCallbackArgs( args1, args2, args3 );
\r
98 if( !f.cbKind ) f = { func : f };
\r
99 X_Timer_REQ_FRAME_LIST[ i ] = f;
\r
100 return f._uid = ++X_Timer_uid;
\r
102 (function( args1, args2, args3 ){
\r
103 var i = X_Timer_REQ_FRAME_LIST.length,
\r
105 i === 0 && ( X_Timer_requestID = X_Timer_add( 0, 1, X_Timer_onEnterFrame ) );
\r
106 f = X_Closure_classifyCallbackArgs( args1, args2, args3 );
\r
107 if( !f.cbKind ) f = { func : f };
\r
108 X_Timer_REQ_FRAME_LIST[ i ] = f;
\r
109 return f._uid = ++X_Timer_uid;
\r
113 * requestAnimationFrame を解除します。登録時に受け取ったタイマーIDを使用します。
\r
114 * @alias X.Timer.cancelFrame
\r
116 * @param {number|string} タイマーID, 数字文字の場合もある!
\r
117 * @return {number} 0 が返る
\r
118 * @example if( timerID ) timerID = X.Timer.cancelFrame( timerID );
\r
120 X_Timer_cancelFrame = X_Timer_CANCEL_ANIME_FRAME ?
\r
122 var list = X_Timer_REQ_FRAME_LIST,
\r
127 if( X_Timer_busyOnFrame ){
\r
129 if( !X_Timer_removal ) X_Timer_removal = {};
\r
130 X_Timer_removal[ uid ] = true;
\r
133 if( ( f = list[ --i ] )._uid < uid ) break;
\r
134 if( f._uid == uid ){
\r
135 list.splice( i, 1 );
\r
136 // gecko では cancelRequestAnimationFrame が無い場合がある
\r
137 l === 1 && X_Timer_CANCEL_ANIME_FRAME && X_Timer_CANCEL_ANIME_FRAME( X_Timer_requestID );
\r
145 var list = X_Timer_REQ_FRAME_LIST,
\r
150 if( X_Timer_busyOnFrame ){
\r
152 if( !X_Timer_removal ) X_Timer_removal = {};
\r
153 X_Timer_removal[ uid ] = true;
\r
156 if( ( f = list[ --i ] )._uid < uid ) break;
\r
157 if( f._uid == uid ){
\r
158 list.splice( i, 1 );
\r
159 l === 1 && X_Timer_remove( X_Timer_requestID );
\r
168 // ------------------------------------------------------------------------- //
\r
169 // --- interface ----------------------------------------------------------- //
\r
170 // ------------------------------------------------------------------------- //
\r
173 * <p>setTimeout をラップします。複数のタイマーを登録しても Web ブラウザにはひとつのタイマーを登録します。
\r
174 * <p>参考:<a href="http://d.hatena.ne.jp/amachang/20060924/1159084608" target="_blank">複雑で重くなった JavaScript を超高速化する方法3</a>,
\r
175 * <a href="http://d.hatena.ne.jp/sawat/20070329" target="_blank">[JavaScript]setIntervalを実験する</a>
\r
176 * <p>指定時間の経過したタイマーは、より過去のものから順番にコールバックされます。
\r
177 * <p>setTimeout のコールバックに文字列しか指定できないブラウザがあり対策しています。
\r
178 * <p>requestAnimationFrame をラップします。ベンダープレフィックス付の requestAnimationFrame もない場合、setTimeout にフォールバックします。
\r
180 * @namespace X.Timer
\r
184 // TODO IE4 の resolution は 64ms
\r
185 'RESOLUTION' : X_Timer_INTERVAL_TIME,
\r
187 'now' : X_Timer_now,
\r
189 'add' : X_Timer_add,
\r
191 'once' : X_Timer_once,
\r
193 'remove' : X_Timer_remove,
\r
195 'requestFrame' : X_Timer_requestFrame,
\r
197 'cancelFrame' : X_Timer_cancelFrame
\r
201 // ------------------------------------------------------------------------- //
\r
202 // --- implements ---------------------------------------------------------- //
\r
203 // ------------------------------------------------------------------------- //
\r
207 * @alias X.Timer.add
\r
208 * @param {number} time ミリ秒
\r
209 * @param {number} opt_count 回数。省略可能。指定回数で自動でタイマーを破棄します。0 を指定した場合無限にタイマーが呼ばれます。省略した場合 0 と同じです。
\r
210 * @param {*} args1 コールバックのための最大で 3 つの引数を指定します。参考:__CallbackHash__
\r
213 * @return {number} タイマーID。1 以上の数値。タイマーの解除に使用。
\r
214 * @example timerID = X.Timer.add( 1000, 5, thisContext, onTimer );
\r
216 function X_Timer_add( time, opt_count, args1, args2, args3 ){
\r
217 var list = X_Timer_TICKET_LIST,
\r
220 time = time < X_Timer_INTERVAL_TIME ? 1 : time / X_Timer_INTERVAL_TIME | 0; // 正の数で使える「Math.floor(x)」を「(x | 0)」に;
\r
222 if( !X_Type_isNumber( opt_count ) ){
\r
229 hash = X_Closure_classifyCallbackArgs( args1, args2, args3 );
\r
230 if( !hash ) return -1; // dev only
\r
232 if( !hash.cbKind ) hash = { func : hash };
\r
235 hash._count = opt_count;
\r
236 hash._uid = ++X_Timer_uid;
\r
237 list[ list.length ] = hash;
\r
239 !X_Timer_busyTimeout && X_Timer_update();
\r
240 return X_Timer_uid;
\r
244 * 1 回呼ばれたら解除されるタイマーをセットします。
\r
245 * @alias X.Timer.once
\r
246 * @param {number} time ミリ秒
\r
247 * @param {*} args1 コールバックのための最大で 3 つの引数を指定します。参考:__CallbackHash__
\r
250 * @return {number} タイマーID。1 以上の数値。タイマーの解除に使用。
\r
252 function X_Timer_once( time, args1, args2, args3 ){
\r
253 return X_Timer_add( time, 1, args1, args2, args3 );
\r
257 * タイマーを解除します。登録時に受け取ったタイマーIDを使用します。
\r
258 * @alias X.Timer.remove
\r
259 * @param {number} タイマーID
\r
260 * @return {number} 0 が返る
\r
261 * @example if( timerID ) timerID = X.Timer.remove( timerID );
\r
263 function X_Timer_remove( uid ){
\r
264 var list = X_Timer_TICKET_LIST,
\r
267 f, q, eventDispatcher, lazy, listeners;
\r
269 if( X_Timer_busyTimeout ){
\r
271 if( !X_Timer_removal ) X_Timer_removal = {};
\r
272 X_Timer_removal[ uid ] = true;
\r
275 if( ( q = list[ --i ] )._uid == uid ){ // 数字の場合と文字の場合がある
\r
276 list.splice( i, 1 );
\r
279 * lazyDispatch 中の EventDispatcher の有無を調べる
\r
281 if( X_EventDispatcher_LAZY_TIMERS[ uid ] ){
\r
282 delete X_EventDispatcher_LAZY_TIMERS[ uid ];
\r
285 !X_Timer_skipUpdate && ( q.last <= X_Timer_waitTime || l === 1 ) && X_Timer_update();
\r
293 if( X_UA[ 'IE4' ] || X_UA[ 'MacIE' ] ){
\r
294 X[ 'Timer' ][ '_' ] = X_Timer_onTimeout;
\r
295 X_Timer_onTimeout = 'X.Timer._()';
\r
298 function X_Timer_onTimeout(){
\r
299 var now = X_Timer_now(),
\r
300 minus = ( ( now - X_Timer_timeStamp ) / X_Timer_INTERVAL_TIME | 0 ) || 1,
\r
301 list = X_Timer_TICKET_LIST,
\r
304 limit = now + X_Timer_INTERVAL_TIME / 2,
\r
308 //console.log( '予定時間と発火時間の差:' + ( now - X_Timer_timeStamp - X_Timer_waitTime * X_Timer_INTERVAL_TIME ) + ' -:' + minus + ' next:' + X_Timer_waitTime );
\r
310 if( X_Timer_busyTimeout ){
\r
311 alert( 'X_Timer_busyTimeout フラグが立ったまま!エラーの可能性' );
\r
314 X_Timer_busyTimeout = true;
\r
316 for( ; i < l; ++i ){
\r
319 ( X_Timer_removal && X_Timer_removal[ q._uid ] ) || // timerId は remove 登録されている
\r
320 0 < ( q.last -= minus ) || // 時間が経過していない
\r
321 heavy && ( q.last = 1 ) // 時間は経過したが、ヘビーフラグが立っている
\r
327 X_Timer_currentUID = q._uid;
\r
330 r = X_Closure_proxyCallback( q, [] );
\r
335 //console.log( 'fire....' );
\r
337 if( limit <= X_Timer_now() ){
\r
338 //console.log( '******* heavy!' );
\r
339 // 関数の実行に時間がかかる場合、次のタイミングに
\r
343 if( r & X_CALLBACK_UN_LISTEN || c === 1 ){
\r
344 list.splice( i, 1 );
\r
349 if( 1 < c ) --q._count;
\r
352 X_Timer_timerId = X_Timer_currentUID = 0;
\r
353 X_Timer_busyTimeout = false;
\r
355 if( X_Timer_removal ){
\r
356 X_Timer_skipUpdate = true;
\r
357 for( uid in X_Timer_removal ){
\r
358 //if( X_EMPTY_OBJECT[ uid ] ) continue;
\r
359 X_Timer_remove( uid );
\r
361 X_Timer_skipUpdate = false;
\r
362 X_Timer_removal = null;
\r
366 ExecuteAtEnd_onEnd();
\r
369 function X_Timer_update(){
\r
370 var list = X_Timer_TICKET_LIST,
\r
374 X_Timer_timerId && clearTimeout( X_Timer_timerId );
\r
375 X_Timer_timerId = 0;
\r
379 1 < i && list.sort( X_Timer_compareQueue );
\r
381 n = list[ 0 ].last;
\r
383 if( n < X_Timer_waitTime || X_Timer_timerId === 0 ){
\r
384 if( X_Timer_timerId ){
\r
385 clearTimeout( X_Timer_timerId );
\r
386 n -= ( X_Timer_now() - X_Timer_timeStamp ) / X_Timer_INTERVAL_TIME;
\r
387 0 <= n || ( n = 0 ); // 負の数は 0 に
\r
389 X_Timer_timeStamp = X_Timer_now();
\r
390 X_Timer_timerId = setTimeout( X_Timer_onTimeout, X_Timer_INTERVAL_TIME * n | 0 );
\r
391 X_Timer_waitTime = n;
\r
395 // 小さい -> 大きい、 同値の場合 uid の小さいものが先
\r
396 // http://jsfiddle.net/warby_/X8YUZ/ Chrome で return が 0 の場合の挙動が他のブラウザと異なる
\r
397 function X_Timer_compareQueue( a, b ){
\r
398 if( a.last === b.last ){ // Chrome のみ
\r
399 return a._uid - b._uid;
\r
401 return a.last - b.last;
\r
402 // return a.last <= b.last ? -1 : 1; //a.last === b.last ? 0 : 1;
\r
405 // http://havelog.ayumusato.com/develop/javascript/e528-ios6_scrolling_timer_notcall.html
\r
406 // iOS6 スクロール中のタイマー発火絡みのバグ備忘
\r
407 if( X_UA[ 'iOS' ] ){
\r
408 window.addEventListener( 'scroll', function(){
\r
410 if( X_Timer_timerId ){
\r
411 clearTimeout( X_Timer_timerId );
\r
412 now = X_Timer_now();
\r
413 last = X_Timer_timeStamp + X_Timer_INTERVAL_TIME * X_Timer_waitTime - now;
\r
414 X_Timer_timerId = setTimeout( X_Timer_onTimeout, 0 < last ? last : 0 );
\r
416 X_Timer_timeStamp = now;
\r
417 X_Timer_waitTime = last / X_Timer_INTERVAL_TIME | 0;
\r
419 X[ 'ViewPort' ][ 'getScrollPosition' ](); // X_DomEvent のための X_ViewPort_scrollX & Y の更新、
\r
425 function X_Timer_onEnterFrame( time ){
\r
426 var list = X_Timer_REQ_FRAME_LIST,
\r
428 i = 0, q, uid, args;
\r
430 time = time || X_Timer_now();
\r
431 X_Timer_busyOnFrame = true;
\r
432 // console.log( X_Timer_now() + ' , ' + time );
\r
433 for( ; i < l; ++i ){
\r
436 if( X_Timer_removal && X_Timer_removal[ q._uid ] ) continue;
\r
439 X_Closure_proxyCallback( q, args || ( args = [ time ] ) );
\r
445 list.splice( 0, l );
\r
447 X_Timer_requestID = X_Timer_REQ_ANIME_FRAME ? X_Timer_REQ_ANIME_FRAME( X_Timer_onEnterFrame ) : X_Timer_add( 0, 1, X_Timer_onEnterFrame );
\r
450 X_Timer_busyOnFrame = false;
\r
451 if( X_Timer_removal ){
\r
452 for( uid in X_Timer_removal ){
\r
453 //if( X_EMPTY_OBJECT[ uid ] ) continue;
\r
454 X_Timer_cancelFrame( X_Timer_removal[ uid ] );
\r
456 X_Timer_removal = null;
\r
459 ExecuteAtEnd_onEnd();
\r
462 console.log( 'X.Core.Timer' );
\r