OSDN Git Service

Fix the bug of X.NodeAnime.
[pettanr/clientJs.git] / 0.6.x / js / 01_core / 16_XTimer.js
1 /*\r
2  * use X.Callback\r
3  */\r
4 \r
5 /*\r
6  * \r
7  * http://please-sleep.cou929.nu/script-yielding-with-setimmediate.html\r
8  * setImmediate での script yielding\r
9  * \r
10  * http://ie.microsoft.com/testdrive/Performance/setImmediateSorting/Default.html\r
11  * setImmediate API\r
12  * \r
13  * if( timer < 4ms ) useSetImmediate\r
14  * \r
15  *         if (window.msSetImmediate)\r
16         {\r
17             this.timer = msSetImmediate(function () { t.stepper(); });        \r
18         }\r
19         else if (window.MozSetImmediate)\r
20         {\r
21             this.timer = MozSetImmediate(function () { t.stepper(); });        \r
22         }\r
23         else if (window.WebkitSetImmediate) {\r
24             this.timer = WebkitSetImmediate(function () { t.stepper(); });\r
25         }\r
26         else if (window.OSetImmediate)\r
27         {\r
28             this.timer = OSetImmediate(function () { t.stepper(); });        \r
29         }\r
30  */\r
31 \r
32 \r
33 // ------------------------------------------------------------------------- //\r
34 // ------------ local variables -------------------------------------------- //\r
35 // ------------------------------------------------------------------------- //\r
36 \r
37 var\r
38 \r
39         /**\r
40          * 現在時の ms を返します。 new Date().getTime() の値です。\r
41          * @alias X.Timer.now\r
42          * @function\r
43          * @return {number} ミリ秒\r
44          */\r
45         X_Timer_now = Date.now || ( function(){ return +new Date; } ),\r
46 \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
56                 false,\r
57 \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
66                 false,\r
67                 \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
72         X_Timer_uid            = 0,\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
78         \r
79         X_Timer_REQ_FRAME_LIST = [],\r
80         X_Timer_requestID      = 0,\r
81         X_Timer_busyOnFrame    = false,\r
82 \r
83         /**\r
84          * requestAnimationFrame をセットします。\r
85          * @alias X.Timer.requestFrame\r
86          * @function\r
87          * @param {*} args1 コールバックのための最大で 3 つの引数を指定します。参考:__CallbackHash__\r
88          * @param {*} args2\r
89          * @param {*} args3\r
90          * @return {number} タイマーID。1 以上の数値。タイマーの解除に使用。\r
91          */\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
95                                 f;\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
101                 }) :\r
102                 (function( args1, args2, args3 ){\r
103                         var i = X_Timer_REQ_FRAME_LIST.length,\r
104                                 f;\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
110                 }),\r
111 \r
112         /**\r
113          * requestAnimationFrame を解除します。登録時に受け取ったタイマーIDを使用します。\r
114          * @alias X.Timer.cancelFrame\r
115          * @function\r
116          * @param {number|string} タイマーID, 数字文字の場合もある!\r
117          * @return {number} 0 が返る\r
118          * @example if( timerID ) timerID = X.Timer.cancelFrame( timerID );\r
119          */\r
120         X_Timer_cancelFrame = X_Timer_CANCEL_ANIME_FRAME ?\r
121                 (function( uid ){\r
122                         var list = X_Timer_REQ_FRAME_LIST,\r
123                                 l    = list.length,\r
124                                 i    = l,\r
125                                 f;\r
126 \r
127                         if( X_Timer_busyOnFrame ){\r
128                                 // fire 中の cancel\r
129                                 if( !X_Timer_removal ) X_Timer_removal = {};\r
130                                 X_Timer_removal[ uid ] = true;\r
131                         } else {\r
132                                 for( ; i; ){\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
138                                                 break;\r
139                                         };\r
140                                 };                              \r
141                         };\r
142                         return 0;\r
143                 }) :\r
144                 (function( uid ){\r
145                         var list = X_Timer_REQ_FRAME_LIST,\r
146                                 l    = list.length,\r
147                                 i    = l,\r
148                                 f;\r
149 \r
150                         if( X_Timer_busyOnFrame ){\r
151                                 // fire 中の cancel\r
152                                 if( !X_Timer_removal ) X_Timer_removal = {};\r
153                                 X_Timer_removal[ uid ] = true;\r
154                         } else {\r
155                                 for( ; i; ){\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
160                                                 break;\r
161                                         };\r
162                                 };\r
163                         };\r
164                         return 0;\r
165                 });\r
166 \r
167 \r
168 // ------------------------------------------------------------------------- //\r
169 // --- interface ----------------------------------------------------------- //\r
170 // ------------------------------------------------------------------------- //\r
171 \r
172 /**\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
179  * \r
180  * @namespace X.Timer\r
181  * @alias X.Timer\r
182  */ \r
183 X[ 'Timer' ] = {\r
184         // TODO IE4 の resolution は 64ms\r
185         'RESOLUTION'   : X_Timer_INTERVAL_TIME,\r
186 \r
187         'now'          : X_Timer_now,\r
188         \r
189         'add'          : X_Timer_add,\r
190         \r
191         'once'         : X_Timer_once,\r
192         \r
193         'remove'       : X_Timer_remove,\r
194         \r
195         'requestFrame' : X_Timer_requestFrame,\r
196         \r
197         'cancelFrame'  : X_Timer_cancelFrame\r
198         \r
199 };\r
200 \r
201 // ------------------------------------------------------------------------- //\r
202 // --- implements ---------------------------------------------------------- //\r
203 // ------------------------------------------------------------------------- //\r
204 \r
205         /**\r
206          * タイマーをセットします。\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
211          * @param {*} args2\r
212          * @param {*} args3\r
213          * @return {number} タイマーID。1 以上の数値。タイマーの解除に使用。\r
214          * @example timerID = X.Timer.add( 1000, 5, thisContext, onTimer );\r
215          */\r
216         function X_Timer_add( time, opt_count, args1, args2, args3 ){\r
217                 var list = X_Timer_TICKET_LIST,\r
218                         hash, obj;\r
219 \r
220                 time = time < X_Timer_INTERVAL_TIME ? 1 : time / X_Timer_INTERVAL_TIME | 0; // 正の数で使える「Math.floor(x)」を「(x | 0)」に;\r
221                 \r
222                 if( !X_Type_isNumber( opt_count ) ){\r
223                         args3 = args2;\r
224                         args2 = args1;\r
225                         args1 = opt_count;\r
226                         opt_count = 0;\r
227                 };\r
228                 \r
229                 hash = X_Closure_classifyCallbackArgs( args1, args2, args3 );\r
230                 if( !hash ) return -1; // dev only\r
231                 \r
232                 if( !hash.cbKind ) hash = { func : hash };\r
233                 hash._time  = time;\r
234                 hash.last   = time;\r
235                 hash._count = opt_count;\r
236                 hash._uid   = ++X_Timer_uid;\r
237                 list[ list.length ] = hash;\r
238                 \r
239             !X_Timer_busyTimeout && X_Timer_update();\r
240             return X_Timer_uid;\r
241         };\r
242         \r
243         /**\r
244          * 1 回呼ばれたら解除されるタイマーをセットします。\r
245          * @alias X.Timer.once\r
246          * @param {number} time ミリ秒\r
247          * @param {*} args1 コールバックのための最大で 3 つの引数を指定します。参考:__CallbackHash__\r
248          * @param {*} args2\r
249          * @param {*} args3\r
250          * @return {number} タイマーID。1 以上の数値。タイマーの解除に使用。\r
251          */\r
252         function X_Timer_once( time, args1, args2, args3 ){\r
253                 return X_Timer_add( time, 1, args1, args2, args3 );\r
254         };\r
255         \r
256         /**\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
262          */\r
263         function X_Timer_remove( uid ){\r
264                 var list = X_Timer_TICKET_LIST,\r
265                         i    = list.length,\r
266                         l    = i,\r
267                         f, q, eventDispatcher, lazy, listeners;\r
268                 \r
269                 if( X_Timer_busyTimeout ){\r
270                         // fire 中の cancel\r
271                         if( !X_Timer_removal ) X_Timer_removal = {};\r
272                         X_Timer_removal[ uid ] = true;\r
273                 } else {\r
274                         for( ; i; ){\r
275                                 if( ( q = list[ --i ] )._uid == uid ){ // 数字の場合と文字の場合がある\r
276                                         list.splice( i, 1 );\r
277                                         \r
278                                         /*\r
279                                          * lazyDispatch 中の EventDispatcher の有無を調べる\r
280                                          */\r
281                                         if( X_EventDispatcher_LAZY_TIMERS[ uid ] ){\r
282                                                 delete X_EventDispatcher_LAZY_TIMERS[ uid ];\r
283                                         };\r
284                                         \r
285                                         !X_Timer_skipUpdate && ( q.last <= X_Timer_waitTime || l === 1 ) && X_Timer_update();\r
286                                         break;\r
287                                 };\r
288                         };                              \r
289                 };\r
290                 return 0;\r
291         };\r
292 \r
293 if( X_UA[ 'IE4' ] || X_UA[ 'MacIE' ] ){\r
294         X[ 'Timer' ][ '_' ] = X_Timer_onTimeout;\r
295         X_Timer_onTimeout = 'X.Timer._()';\r
296 };\r
297 \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
302                 i     = 0,\r
303                 l     = list.length,\r
304                 limit = now + X_Timer_INTERVAL_TIME / 2,\r
305                 heavy,\r
306                 q, f, c, r, uid;\r
307         \r
308         //console.log( '予定時間と発火時間の差:' + ( now - X_Timer_timeStamp - X_Timer_waitTime * X_Timer_INTERVAL_TIME ) + ' -:' + minus + ' next:' + X_Timer_waitTime );\r
309         \r
310         if( X_Timer_busyTimeout ){\r
311                 alert( 'X_Timer_busyTimeout フラグが立ったまま!エラーの可能性' );\r
312         };\r
313         \r
314         X_Timer_busyTimeout = true;\r
315         \r
316     for( ; i < l; ++i ){\r
317         q = list[ i ];\r
318         if(\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
322                 ){\r
323                         continue;\r
324                 };\r
325                 c = q._count;\r
326                 \r
327                 X_Timer_currentUID = q._uid;\r
328                 \r
329                 if( q.cbKind ){\r
330                         r = X_Closure_proxyCallback( q, [] );\r
331                 } else {\r
332                         r = q.func();\r
333                 };\r
334                 \r
335                 //console.log( 'fire....' );\r
336                 \r
337                 if( limit <= X_Timer_now() ){\r
338                         //console.log( '******* heavy!' );\r
339                         // 関数の実行に時間がかかる場合、次のタイミングに\r
340                         heavy = true;\r
341                 };\r
342                 \r
343                 if( r & X_CALLBACK_UN_LISTEN || c === 1 ){\r
344                         list.splice( i, 1 );\r
345                         --i;\r
346                         --l;\r
347                         continue;\r
348                 } else\r
349                 if( 1 < c ) --q._count;\r
350                 q.last = q._time;\r
351     };\r
352     X_Timer_timerId     = X_Timer_currentUID = 0;\r
353     X_Timer_busyTimeout = false;\r
354     \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
360         };\r
361         X_Timer_skipUpdate = false;\r
362         X_Timer_removal = null;\r
363     };\r
364     X_Timer_update();\r
365     \r
366     ExecuteAtEnd_onEnd();\r
367 };\r
368 \r
369 function X_Timer_update(){\r
370         var list = X_Timer_TICKET_LIST,\r
371                 i    = list.length,\r
372                 n;\r
373         if( i === 0 ){\r
374                 X_Timer_timerId && clearTimeout( X_Timer_timerId );\r
375                 X_Timer_timerId = 0;\r
376                 return;\r
377         };\r
378         \r
379         1 < i && list.sort( X_Timer_compareQueue );\r
380         \r
381     n = list[ 0 ].last;\r
382     \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
388         };\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
392     };\r
393 };\r
394 \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
400     };\r
401     return a.last - b.last;\r
402         // return a.last <= b.last ? -1 : 1; //a.last === b.last ? 0 : 1;\r
403 };\r
404 \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
409                 var last, now;\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
415                         // 更新\r
416                         X_Timer_timeStamp = now;\r
417                         X_Timer_waitTime  = last / X_Timer_INTERVAL_TIME | 0;\r
418                 };\r
419                 X[ 'ViewPort' ][ 'getScrollPosition' ](); // X_DomEvent のための X_ViewPort_scrollX & Y の更新、\r
420         });\r
421 };\r
422 \r
423 \r
424 // ページを読み込んでからの時間\r
425 function X_Timer_onEnterFrame( time ){\r
426         var list = X_Timer_REQ_FRAME_LIST,\r
427                 l    = list.length,\r
428                 i    = 0, q, uid, args;\r
429 \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
434         q = list[ i ];\r
435         \r
436         if( X_Timer_removal && X_Timer_removal[ q._uid ] ) continue;\r
437         \r
438                 if( q.cbKind ){\r
439                         X_Closure_proxyCallback( q, args || ( args = [ time ] ) );\r
440                 } else {\r
441                         q.func( time );\r
442                 };\r
443     };\r
444 \r
445     list.splice( 0, l );\r
446     if( list.length ){\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
448     };\r
449     \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
455         };\r
456         X_Timer_removal = null;\r
457     };\r
458     \r
459     ExecuteAtEnd_onEnd();\r
460 };\r
461 \r
462 console.log( 'X.Core.Timer' );\r