OSDN Git Service

d2dbd8f7d90341edc6f662ca7e1c67a873d1802b
[pettanr/clientJs.git] / 0.6.x / js / 01_core / 14_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         X_Timer_SET_TIMEOUT   = window.setTimeout,\r
48         X_Timer_CLEAR_TIMEOUT = window.clearTimeout,\r
49 \r
50         // http://uupaa.hatenablog.com/entry/2012/02/01/083607\r
51         // Firefox 4 partial (request only), Mobile Firefox5 ready (request only), Firefox 11 ready (cancel impl)       \r
52         X_Timer_REQ_ANIME_FRAME =\r
53                 window.requestAnimationFrame ||\r
54                 window.webkitRequestAnimationFrame ||\r
55                 window.mozRequestAnimationFrame ||\r
56                 window.oRequestAnimationFrame ||\r
57                 window.msRequestAnimationFrame ||\r
58                 false,\r
59 \r
60         X_Timer_CANCEL_ANIME_FRAME =\r
61                 window.cancelRequestAnimationFrame ||\r
62                 window.webkitCancelAnimationFrame ||\r
63                 window.webkitCancelRequestAnimationFrame ||\r
64                 window.mozCancelRequestAnimationFrame ||\r
65                 window.oCancelRequestAnimationFrame ||\r
66                 window.msCancelRequestAnimationFrame ||\r
67                 false,\r
68                 \r
69         X_Timer_INTERVAL_TIME  = 16,\r
70         X_Timer_TICKET_LIST    = [],\r
71         X_Timer_removal        = null,\r
72         X_Timer_skipUpdate     = false,\r
73         X_Timer_uid            = 0,\r
74         X_Timer_timerId        = 0,\r
75         X_Timer_busyTimeout    = false,\r
76         X_Timer_timeStamp      = 0, // setTimeout に登録した時間\r
77         X_Timer_waitTime       = 0,     // 待ち時間\r
78         X_Timer_currentUID     = 0, // 現在発火中の uid\r
79         \r
80         X_Timer_REQ_FRAME_LIST = [],\r
81         X_Timer_requestID      = 0,\r
82         X_Timer_busyOnFrame    = false;\r
83 \r
84 // ------------------------------------------------------------------------- //\r
85 // --- interface ----------------------------------------------------------- //\r
86 // ------------------------------------------------------------------------- //\r
87 \r
88 /**\r
89  * <p>setTimeout をラップします。複数のタイマーを登録しても Web ブラウザにはひとつのタイマーを登録します。\r
90  * <p>参考:<a href="http://d.hatena.ne.jp/amachang/20060924/1159084608" target="_blank">複雑で重くなった JavaScript を超高速化する方法3</a>,\r
91  * <a href="http://d.hatena.ne.jp/sawat/20070329" target="_blank">[JavaScript]setIntervalを実験する</a>\r
92  * <p>指定時間の経過したタイマーは、より過去のものから順番にコールバックされます。\r
93  * <p>setTimeout のコールバックに文字列しか指定できないブラウザがあり対策しています。\r
94  * <p>requestAnimationFrame をラップします。ベンダープレフィックス付の requestAnimationFrame もない場合、setTimeout にフォールバックします。\r
95  * \r
96  * @example X.Timer.add( 1000, 5, thisContext, onTimer );\r
97  * \r
98  * @namespace X.Timer\r
99  * @alias X.Timer\r
100  */ \r
101 X.Timer = {\r
102 \r
103         now : X_Timer_now,\r
104         \r
105         /**\r
106          * タイマーをセットします。\r
107          * @param {number} time ミリ秒\r
108          * @param {number} opt_count 回数。省略可能。指定回数で自動でタイマーを破棄します。0 を指定した場合無限にタイマーが呼ばれます。省略した場合 0 と同じです。\r
109          * @param {*} args1 コールバックのための最大で 3 つの引数を指定します。参考:__CallbackHash__\r
110          * @param {*} args2\r
111          * @param {*} args3\r
112          * @return {number} タイマーID。1 以上の数値。タイマーの解除に使用。\r
113          */\r
114         add : function( time, opt_count, args1, args2, args3 ){\r
115                 var list = X_Timer_TICKET_LIST,\r
116                         hash, obj;\r
117                 time = time < X_Timer_INTERVAL_TIME ? 1 : time / X_Timer_INTERVAL_TIME | 0; // 正の数で使える「Math.floor(x)」を「(x | 0)」に;\r
118                 \r
119                 if( !X_Type_isNumber( opt_count ) ){\r
120                         args3 = args2;\r
121                         args2 = args1;\r
122                         args1 = opt_count;\r
123                         opt_count = 0;\r
124                 };\r
125                 \r
126                 hash = X_Callback_classifyCallbackArgs( args1, args2, args3 );\r
127                 if( !hash ) return -1; // dev only\r
128                 \r
129                 if( !hash.k ) hash = { f : hash };\r
130                 hash.time  = time;\r
131                 hash.last  = time;\r
132                 hash.count = opt_count;\r
133                 hash.uid   = ++X_Timer_uid;\r
134                 list[ list.length ] = hash;\r
135                 \r
136             !X_Timer_busyTimeout && X_Timer_update();\r
137             return X_Timer_uid;\r
138         },\r
139         \r
140         /**\r
141          * 1 回呼ばれたら解除されるタイマーをセットします。\r
142          * @param {number} time ミリ秒\r
143          * @param {*} args1 コールバックのための最大で 3 つの引数を指定します。参考:__CallbackHash__\r
144          * @param {*} args2\r
145          * @param {*} args3\r
146          * @return {number} タイマーID。1 以上の数値。タイマーの解除に使用。\r
147          */\r
148         once : function( time, args1, args2, args3 ){\r
149                 return X.Timer.add( time, 1, args1, args2, args3 );\r
150         },\r
151         \r
152         /**\r
153          * タイマーを解除します。登録時に受け取ったタイマーIDを使用します。\r
154          * @param {number} タイマーID\r
155          */\r
156         remove : function( uid ){\r
157                 var list = X_Timer_TICKET_LIST,\r
158                         i    = list.length,\r
159                         l    = i,\r
160                         f, q, eventDispatcher, lazy, listeners;\r
161                 // fire 中の cancel\r
162                 if( X_Timer_busyTimeout ){\r
163                         if( !X_Timer_removal ) X_Timer_removal = {};\r
164                         X_Timer_removal[ uid ] = true;\r
165                 } else {\r
166                         for( ; i; ){\r
167                                 if( ( q = list[ --i ] ).uid === uid ){\r
168                                         list.splice( i, 1 );\r
169                                         \r
170                                         /*\r
171                                          * lazyDispatch 中の EventDispatcher の有無を調べる\r
172                                          */\r
173                                         if( X_EventDispatcher_LAZY_TIMERS[ uid ] ){\r
174                                                 // eventDispatcher = X_EventDispatcher_LAZY_TIMERS[ uid ];\r
175                                                 delete X_EventDispatcher_LAZY_TIMERS[ uid ];\r
176                                                 /*\r
177                                                 listeners = eventDispatcher[ '_listeners' ];\r
178                                                 if( listeners && !listeners[ X_Listeners_.DISPATCHING ] && listeners[ X_Listeners_.KILL_RESERVED ] ){\r
179                                                         for( uid in X_EventDispatcher_LAZY_TIMERS ){\r
180                                                                 if( X_EventDispatcher_LAZY_TIMERS[ uid ] === eventDispatcher ){\r
181                                                                         lazy = true;\r
182                                                                         break;\r
183                                                                 };\r
184                                                         };                                              \r
185                                                         !lazy && eventDispatcher.kill();\r
186                                                 }; */\r
187                                         };\r
188                                         \r
189                                         !X_Timer_skipUpdate && ( q.last <= X_Timer_waitTime || l === 1 ) && X_Timer_update();\r
190                                         break;\r
191                                 };\r
192                         };                              \r
193                 };\r
194         },\r
195         \r
196         /**\r
197          * requestAnimationFrame をセットします。\r
198          * @function\r
199          * @param {*} args1 コールバックのための最大で 3 つの引数を指定します。参考:__CallbackHash__\r
200          * @param {*} args2\r
201          * @param {*} args3\r
202          * @return {number} タイマーID。1 以上の数値。タイマーの解除に使用。\r
203          */\r
204         requestFrame : X_Timer_REQ_ANIME_FRAME ?\r
205                 (function( args1, args2, args3 ){\r
206                         var i = X_Timer_REQ_FRAME_LIST.length,\r
207                                 f;\r
208                         i === 0 && ( X_Timer_requestID = X_Timer_REQ_ANIME_FRAME( X_Timer_onEnterFrame ) );\r
209                         f = X_Timer_REQ_FRAME_LIST[ i ] = X_Callback_classifyCallbackArgs( args1, args2, args3 );\r
210                         return f.uid = ++X_Timer_uid;\r
211                 }) :\r
212                 (function( args1, args2, args3 ){\r
213                         var i = X_Timer_REQ_FRAME_LIST.length,\r
214                                 f;\r
215                         i === 0 && ( X_Timer_requestID = X.Timer.add( 0, 1, X_Timer_onEnterFrame ) );\r
216                         f = X_Timer_REQ_FRAME_LIST[ i ] = X_Callback_classifyCallbackArgs( args1, args2, args3 );\r
217                         return f.uid = ++X_Timer_uid;\r
218                 }),\r
219 \r
220         /**\r
221          * requestAnimationFrame を解除します。登録時に受け取ったタイマーIDを使用します。\r
222          * @function\r
223          * @param {number} タイマーID\r
224          */\r
225         cancelFrame : X_Timer_CANCEL_ANIME_FRAME ?\r
226                 (function( uid ){\r
227                         var list = X_Timer_REQ_FRAME_LIST,\r
228                                 l    = list.length,\r
229                                 i    = l,\r
230                                 f;\r
231                         // fire 中の cancel\r
232                         if( X_Timer_busyOnFrame ){\r
233                                 if( !X_Timer_removal ) X_Timer_removal = {};\r
234                                 X_Timer_removal[ uid ] = true;\r
235                         } else {\r
236                                 for( ; i; ){\r
237                                         if( ( f = list[ --i ] ).uid < uid ) break;\r
238                                         if( f.uid === uid ){\r
239                                                 list.splice( i, 1 );\r
240                                                 // gecko では無い場合がある\r
241                                                 l === 1 && X_Timer_CANCEL_ANIME_FRAME && X_Timer_CANCEL_ANIME_FRAME( X_Timer_requestID );\r
242                                                 break;\r
243                                         };\r
244                                 };                              \r
245                         };\r
246                 }) :\r
247                 (function( uid ){\r
248                         var list = X_Timer_REQ_FRAME_LIST,\r
249                                 l    = list.length,\r
250                                 i    = l,\r
251                                 f;\r
252                         // fire 中の cancel\r
253                         if( X_Timer_busyOnFrame ){\r
254                                 if( !X_Timer_removal ) X_Timer_removal = {};\r
255                                 X_Timer_removal[ uid ] = true;\r
256                         } else {\r
257                                 for( ; i; ){\r
258                                         if( ( f = list[ --i ] ).uid < uid ) break;\r
259                                         if( f.uid === uid ){\r
260                                                 list.splice( i, 1 );\r
261                                                 l === 1 && X.Timer.remove( X_Timer_requestID );\r
262                                                 break;\r
263                                         };\r
264                                 };\r
265                         };\r
266                 })\r
267         \r
268 };\r
269 \r
270 // ------------------------------------------------------------------------- //\r
271 // --- implements ---------------------------------------------------------- //\r
272 // ------------------------------------------------------------------------- //\r
273 \r
274 if( X_UA[ 'IE4' ] || X_UA[ 'MacIE' ] ){\r
275         X.Timer[ '_' ] = X_Timer_onTimeout;\r
276         X_Timer_onTimeout = 'X.Timer._()';\r
277 };\r
278 \r
279 function X_Timer_onTimeout(){\r
280         var now   = X_Timer_now(),\r
281                 minus = ( ( now - X_Timer_timeStamp ) / X_Timer_INTERVAL_TIME | 0 ) || 1,\r
282                 list  = X_Timer_TICKET_LIST,\r
283                 i     = 0,\r
284                 l     = list.length,\r
285                 limit = now + X_Timer_INTERVAL_TIME / 2,\r
286                 heavy,\r
287                 q, f, c, r, uid;\r
288         \r
289         //console.log( '予定時間と発火時間の差:' + ( now - X_Timer_timeStamp - X_Timer_waitTime * X_Timer_INTERVAL_TIME ) + ' -:' + minus + ' next:' + X_Timer_waitTime );\r
290         \r
291         if( X_Timer_busyTimeout ){\r
292                 alert( 'X_Timer_busyTimeout フラグが立ったまま!エラーの可能性' );\r
293         };\r
294         \r
295         X_Timer_busyTimeout = true;\r
296         \r
297     for( ; i < l; ++i ){\r
298         q = list[ i ];\r
299         if(\r
300                 ( X_Timer_removal && X_Timer_removal[ q.uid ] ) || // timerId は remove 登録されている\r
301                         0 < ( q.last -= minus ) || // 時間が経過していない\r
302                         heavy && ( q.last = 1 ) // 時間は経過したが、ヘビーフラグが立っている\r
303                 ){\r
304                         continue;\r
305                 };\r
306                 c = q.count;\r
307                 \r
308                 X_Timer_currentUID = q.uid;\r
309                 \r
310                 if( q.k ){\r
311                         r = X_Callback_proxyCallback( q, [] );\r
312                 } else {\r
313                         r = q.f();\r
314                 };\r
315                 \r
316                 //console.log( 'fire....' );\r
317                 \r
318                 if( limit <= X_Timer_now() ){\r
319                         //console.log( '******* heavy!' );\r
320                         // 関数の実行に時間がかかる場合、次のタイミングに\r
321                         heavy = true;\r
322                 };\r
323                 \r
324                 if( r & X_Callback_UN_LISTEN || c === 1 ){\r
325                         list.splice( i, 1 );\r
326                         --i;\r
327                         --l;\r
328                         continue;\r
329                 } else\r
330                 if( 1 < c ) --q.count;\r
331                 q.last = q.time;\r
332     };\r
333     X_Timer_timerId     = X_Timer_currentUID = 0;\r
334     X_Timer_busyTimeout = false;\r
335     \r
336     if( X_Timer_removal ){\r
337         X_Timer_skipUpdate = true;\r
338         for( uid in X_Timer_removal ){\r
339                 //if( X_EMPTY_OBJECT[ uid ] ) continue;\r
340                 X.Timer.remove( uid );\r
341         };\r
342         X_Timer_skipUpdate = false;\r
343         X_Timer_removal = null;\r
344     };\r
345     X_Timer_update();\r
346 };\r
347 \r
348 function X_Timer_update(){\r
349         var list = X_Timer_TICKET_LIST,\r
350                 i    = list.length,\r
351                 n;\r
352         if( i === 0 ){\r
353                 X_Timer_timerId && X_Timer_CLEAR_TIMEOUT( X_Timer_timerId );\r
354                 X_Timer_timerId = 0;\r
355                 return;\r
356         };\r
357         \r
358         1 < i && list.sort( X_Timer_compareQueue );\r
359         \r
360     n = list[ i - 1 ].last;\r
361     \r
362     if( n < X_Timer_waitTime || X_Timer_timerId === 0 ){\r
363         if( X_Timer_timerId ){\r
364                 X_Timer_CLEAR_TIMEOUT( X_Timer_timerId );\r
365                 n -= ( X_Timer_now() - X_Timer_timeStamp ) / X_Timer_INTERVAL_TIME | 0;\r
366                 0 <= n || ( n = 0 ); // 負の数は 0 に\r
367         };\r
368         X_Timer_timeStamp = X_Timer_now();\r
369         X_Timer_timerId   = X_Timer_SET_TIMEOUT( X_Timer_onTimeout, X_Timer_INTERVAL_TIME * n );\r
370         X_Timer_waitTime  = n;\r
371     };\r
372 };\r
373 \r
374 // 大きい -> 小さい\r
375 function X_Timer_compareQueue( a, b ){\r
376         return a.last < b.last ? 1 : a.last === b.last ? 0 : -1;\r
377 };\r
378 \r
379 // http://havelog.ayumusato.com/develop/javascript/e528-ios6_scrolling_timer_notcall.html\r
380 // iOS6 スクロール中のタイマー発火絡みのバグ備忘\r
381 if( X_UA[ 'iOS' ] ){\r
382         window.addEventListener( 'scroll', function(){\r
383                 var last, now;\r
384                 if( X_Timer_timerId ){\r
385                         X_Timer_CLEAR_TIMEOUT( X_Timer_timerId );\r
386                         now               = X_Timer_now();\r
387                         last              = X_Timer_timeStamp + X_Timer_INTERVAL_TIME * X_Timer_waitTime - now;\r
388                         X_Timer_timerId   = X_Timer_SET_TIMEOUT( X_Timer_onTimeout, 0 < last ? last : 0 );\r
389                         // 更新\r
390                         X_Timer_timeStamp = now;\r
391                         X_Timer_waitTime  = last / X_Timer_INTERVAL_TIME | 0;\r
392                 };\r
393         });\r
394 };\r
395 \r
396 \r
397 // ページを読み込んでからの時間\r
398 function X_Timer_onEnterFrame( time ){\r
399         var list = X_Timer_REQ_FRAME_LIST,\r
400                 l    = list.length,\r
401                 i    = 0, q, uid, args;\r
402 \r
403         time = time || X_Timer_now();\r
404         X_Timer_busyOnFrame = true;\r
405         // console.log( X_Timer_now() + ' , ' + time );\r
406     for( ; i < l; ++i ){\r
407         q = list[ i ];\r
408         \r
409         if( X_Timer_removal && X_Timer_removal[ q.uid ] ) continue;\r
410         \r
411                 if( q.k ){\r
412                         X_Callback_proxyCallback( q, args || ( args = [ time ] ) );\r
413                 } else {\r
414                         q( time );\r
415                 };\r
416     };\r
417 \r
418     list.splice( 0, l );\r
419     if( list.length ){\r
420         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
421     };\r
422     \r
423     X_Timer_busyOnFrame = false;\r
424     if( X_Timer_removal ){\r
425         for( uid in X_Timer_removal ){\r
426                 //if( X_EMPTY_OBJECT[ uid ] ) continue;\r
427                 X.Timer.cancelFrame( X_Timer_removal[ uid ] );\r
428         };\r
429         X_Timer_removal = null;\r
430     };\r
431 };\r
432 \r
433 console.log( 'X.Core.Timer' );\r