OSDN Git Service

Version 0.6.132, fix X.Node._flags & fix X.EventDispatcher._listeners & start to...
[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 X_Timer_now = Date.now || function(){ return +new Date; },\r
38 \r
39         X_Timer_SET_TIMEOUT   = window.setTimeout,\r
40         X_Timer_CLEAR_TIMEOUT = window.clearTimeout,\r
41 \r
42         // http://uupaa.hatenablog.com/entry/2012/02/01/083607\r
43         // Firefox 4 partial (request only), Mobile Firefox5 ready (request only), Firefox 11 ready (cancel impl)       \r
44         X_Timer_REQ_ANIME_FRAME =\r
45                 window.requestAnimationFrame ||\r
46                 window.webkitRequestAnimationFrame ||\r
47                 window.mozRequestAnimationFrame ||\r
48                 window.oRequestAnimationFrame ||\r
49                 window.msRequestAnimationFrame ||\r
50                 false,\r
51 \r
52         X_Timer_CANCEL_ANIME_FRAME =\r
53                 window.cancelRequestAnimationFrame ||\r
54                 window.webkitCancelAnimationFrame ||\r
55                 window.webkitCancelRequestAnimationFrame ||\r
56                 window.mozCancelRequestAnimationFrame ||\r
57                 window.oCancelRequestAnimationFrame ||\r
58                 window.msCancelRequestAnimationFrame ||\r
59                 false,\r
60                 \r
61         X_Timer_INTERVAL_TIME  = 16,\r
62         X_Timer_TICKET_LIST    = [],\r
63         X_Timer_removal        = null,\r
64         X_Timer_skipUpdate     = false,\r
65         X_Timer_uid            = 0,\r
66         X_Timer_timerId        = 0,\r
67         X_Timer_busyTimeout    = false,\r
68         X_Timer_timeStamp      = 0, // setTimeout に登録した時間\r
69         X_Timer_waitTime       = 0,     // 待ち時間\r
70         X_Timer_currentUID     = 0, // 現在発火中の uid\r
71         \r
72         X_Timer_REQ_FRAME_LIST = [],\r
73         X_Timer_requestID      = 0,\r
74         X_Timer_busyOnFrame    = false;\r
75 \r
76 // ------------------------------------------------------------------------- //\r
77 // --- interface ----------------------------------------------------------- //\r
78 // ------------------------------------------------------------------------- //\r
79 X.Timer = {\r
80         \r
81         now : X_Timer_now,\r
82         \r
83         add : function( time, opt_count, args1, args2, args3 ){\r
84                 var list = X_Timer_TICKET_LIST,\r
85                         hash, obj;\r
86                 time = time < X_Timer_INTERVAL_TIME ? 1 : time / X_Timer_INTERVAL_TIME | 0; // 正の数で使える「Math.floor(x)」を「(x | 0)」に;\r
87                 \r
88                 if( !X.Type.isNumber( opt_count ) ){\r
89                         args3 = args2;\r
90                         args2 = args1;\r
91                         args1 = opt_count;\r
92                         opt_count = 0;\r
93                 };\r
94                 \r
95                 hash = X_Callback_classifyCallbackArgs( args1, args2, args3 );\r
96                 if( !hash ) return -1; // dev only\r
97                 \r
98                 if( !hash.k ) hash = { f : hash };\r
99                 hash.time  = time;\r
100                 hash.last  = time;\r
101                 hash.count = opt_count;\r
102                 hash.uid   = ++X_Timer_uid;\r
103                 list[ list.length ] = hash;\r
104                 \r
105             !X_Timer_busyTimeout && X_Timer_update();\r
106             return X_Timer_uid;\r
107         },\r
108         \r
109         once : function( time, args1, args2, args3 ){\r
110                 return X.Timer.add( time, 1, args1, args2, args3 );\r
111         },\r
112         \r
113         remove : function( uid ){\r
114                 var list = X_Timer_TICKET_LIST,\r
115                         i    = list.length,\r
116                         l    = i,\r
117                         f, q, eventDispatcher, lazy, listeners;\r
118                 // fire 中の cancel\r
119                 if( X_Timer_busyTimeout ){\r
120                         if( !X_Timer_removal ) X_Timer_removal = {};\r
121                         X_Timer_removal[ uid ] = true;\r
122                 } else {\r
123                         for( ; i; ){\r
124                                 if( ( q = list[ --i ] ).uid === uid ){\r
125                                         list.splice( i, 1 );\r
126                                         \r
127                                         /*\r
128                                          * lazyDispatch 中の EventDispatcher の有無を調べる\r
129                                          */\r
130                                         if( X_EventDispatcher_LAZY_TIMERS[ uid ] ){\r
131                                                 eventDispatcher = X_EventDispatcher_LAZY_TIMERS[ uid ];\r
132                                                 delete X_EventDispatcher_LAZY_TIMERS[ uid ];\r
133                                                 /*\r
134                                                 listeners = eventDispatcher[ '_listeners' ];\r
135                                                 if( listeners && !listeners[ X_Listeners_.DISPATCHING ] && listeners[ X_Listeners_.KILL_RESERVED ] ){\r
136                                                         for( uid in X_EventDispatcher_LAZY_TIMERS ){\r
137                                                                 if( X_EventDispatcher_LAZY_TIMERS[ uid ] === eventDispatcher ){\r
138                                                                         lazy = true;\r
139                                                                         break;\r
140                                                                 };\r
141                                                         };                                              \r
142                                                         !lazy && eventDispatcher.kill();\r
143                                                 }; */\r
144                                         };\r
145                                         \r
146                                         !X_Timer_skipUpdate && ( q.last <= X_Timer_waitTime || l === 1 ) && X_Timer_update();\r
147                                         break;\r
148                                 };\r
149                         };                              \r
150                 };\r
151         },\r
152         \r
153         requestFrame : X_Timer_REQ_ANIME_FRAME ?\r
154                 (function( args1, args2, args3 ){\r
155                         var i = X_Timer_REQ_FRAME_LIST.length,\r
156                                 f;\r
157                         i === 0 && ( X_Timer_requestID = X_Timer_REQ_ANIME_FRAME( X_Timer_onEnterFrame ) );\r
158                         f = X_Timer_REQ_FRAME_LIST[ i ] = X_Callback_classifyCallbackArgs( args1, args2, args3 );\r
159                         return f.uid = ++X_Timer_uid;\r
160                 }) :\r
161                 (function( args1, args2, args3 ){\r
162                         var i = X_Timer_REQ_FRAME_LIST.length,\r
163                                 f;\r
164                         i === 0 && ( X_Timer_requestID = X.Timer.add( 0, 1, X_Timer_onEnterFrame ) );\r
165                         f = X_Timer_REQ_FRAME_LIST[ i ] = X_Callback_classifyCallbackArgs( args1, args2, args3 );\r
166                         return f.uid = ++X_Timer_uid;\r
167                 }),\r
168         \r
169         cancelFrame : X_Timer_CANCEL_ANIME_FRAME ?\r
170                 (function( uid ){\r
171                         var list = X_Timer_REQ_FRAME_LIST,\r
172                                 l    = list.length,\r
173                                 i    = l,\r
174                                 f;\r
175                         // fire 中の cancel\r
176                         if( X_Timer_busyOnFrame ){\r
177                                 if( !X_Timer_removal ) X_Timer_removal = {};\r
178                                 X_Timer_removal[ uid ] = true;\r
179                         } else {\r
180                                 for( ; i; ){\r
181                                         if( ( f = list[ --i ] ).uid < uid ) break;\r
182                                         if( f.uid === uid ){\r
183                                                 list.splice( i, 1 );\r
184                                                 // gecko では無い場合がある\r
185                                                 l === 1 && X_Timer_CANCEL_ANIME_FRAME && X_Timer_CANCEL_ANIME_FRAME( X_Timer_requestID );\r
186                                                 break;\r
187                                         };\r
188                                 };                              \r
189                         };\r
190                 }) :\r
191                 (function( uid ){\r
192                         var list = X_Timer_REQ_FRAME_LIST,\r
193                                 l    = list.length,\r
194                                 i    = l,\r
195                                 f;\r
196                         // fire 中の cancel\r
197                         if( X_Timer_busyOnFrame ){\r
198                                 if( !X_Timer_removal ) X_Timer_removal = {};\r
199                                 X_Timer_removal[ uid ] = true;\r
200                         } else {\r
201                                 for( ; i; ){\r
202                                         if( ( f = list[ --i ] ).uid < uid ) break;\r
203                                         if( f.uid === uid ){\r
204                                                 list.splice( i, 1 );\r
205                                                 l === 1 && X.Timer.remove( X_Timer_requestID );\r
206                                                 break;\r
207                                         };\r
208                                 };\r
209                         };\r
210                 })\r
211         \r
212 };\r
213 \r
214 // ------------------------------------------------------------------------- //\r
215 // --- implements ---------------------------------------------------------- //\r
216 // ------------------------------------------------------------------------- //\r
217 \r
218 if( X_UA.IE4 || X_UA.MacIE ){\r
219         X.Timer[ '_' ] = X_Timer_onTimeout;\r
220         X_Timer_onTimeout = 'X.Timer._()';\r
221 };\r
222 \r
223 function X_Timer_onTimeout(){\r
224         var now   = X_Timer_now(),\r
225                 minus = ( ( now - X_Timer_timeStamp ) / X_Timer_INTERVAL_TIME | 0 ) || 1,\r
226                 list  = X_Timer_TICKET_LIST,\r
227                 i     = 0,\r
228                 l     = list.length,\r
229                 limit = now + X_Timer_INTERVAL_TIME / 2,\r
230                 heavy,\r
231                 q, f, c, r, uid;\r
232         \r
233         //console.log( '予定時間と発火時間の差:' + ( now - X_Timer_timeStamp - X_Timer_waitTime * X_Timer_INTERVAL_TIME ) + ' -:' + minus + ' next:' + X_Timer_waitTime );\r
234         \r
235         if( X_Timer_busyTimeout ){\r
236                 alert( 'X_Timer_busyTimeout フラグが立ったまま!エラーの可能性' );\r
237         };\r
238         \r
239         X_Timer_busyTimeout = true;\r
240         \r
241     for( ; i < l; ++i ){\r
242         q = list[ i ];\r
243         if(\r
244                 ( X_Timer_removal && X_Timer_removal[ q.uid ] ) || // timerId は remove 登録されている\r
245                         0 < ( q.last -= minus ) || // 時間が経過していない\r
246                         heavy && ( q.last = 1 ) // 時間は経過したが、ヘビーフラグが立っている\r
247                 ){\r
248                         continue;\r
249                 };\r
250                 c = q.count;\r
251                 \r
252                 X_Timer_currentUID = q.uid;\r
253                 \r
254                 if( q.k ){\r
255                         r = X_Callback_proxyCallback( q, [] );\r
256                 } else {\r
257                         r = q.f();\r
258                 };\r
259                 \r
260                 //console.log( 'fire....' );\r
261                 \r
262                 if( limit <= X_Timer_now() ){\r
263                         //console.log( '******* heavy!' );\r
264                         // 関数の実行に時間がかかる場合、次のタイミングに\r
265                         heavy = true;\r
266                 };\r
267                 \r
268                 if( r & X_Callback_UN_LISTEN || c === 1 ){\r
269                         list.splice( i, 1 );\r
270                         --i;\r
271                         --l;\r
272                         continue;\r
273                 } else\r
274                 if( 1 < c ) --q.count;\r
275                 q.last = q.time;\r
276     };\r
277     X_Timer_timerId     = X_Timer_currentUID = 0;\r
278     X_Timer_busyTimeout = false;\r
279     \r
280     if( X_Timer_removal ){\r
281         X_Timer_skipUpdate = true;\r
282         for( uid in X_Timer_removal ){\r
283                 //if( X_EMPTY_OBJECT[ uid ] ) continue;\r
284                 X.Timer.remove( uid );\r
285         };\r
286         X_Timer_skipUpdate = false;\r
287         X_Timer_removal = null;\r
288     };\r
289     X_Timer_update();\r
290 };\r
291 \r
292 function X_Timer_update(){\r
293         var list = X_Timer_TICKET_LIST,\r
294                 i    = list.length,\r
295                 n;\r
296         if( i === 0 ){\r
297                 X_Timer_timerId && X_Timer_CLEAR_TIMEOUT( X_Timer_timerId );\r
298                 X_Timer_timerId = 0;\r
299                 return;\r
300         };\r
301         \r
302         1 < i && list.sort( X_Timer_compareQueue );\r
303         \r
304     n = list[ i - 1 ].last;\r
305     \r
306     if( n < X_Timer_waitTime || X_Timer_timerId === 0 ){\r
307         if( X_Timer_timerId ){\r
308                 X_Timer_CLEAR_TIMEOUT( X_Timer_timerId );\r
309                 n -= ( X_Timer_now() - X_Timer_timeStamp ) / X_Timer_INTERVAL_TIME | 0;\r
310                 0 <= n || ( n = 0 ); // 負の数は 0 に\r
311         };\r
312         X_Timer_timeStamp = X_Timer_now();\r
313         X_Timer_timerId   = X_Timer_SET_TIMEOUT( X_Timer_onTimeout, X_Timer_INTERVAL_TIME * n );\r
314         X_Timer_waitTime  = n;\r
315     };\r
316 };\r
317 \r
318 // 大きい -> 小さい\r
319 function X_Timer_compareQueue( a, b ){\r
320         return a.last < b.last ? 1 : a.last === b.last ? 0 : -1;\r
321 };\r
322 \r
323 // http://havelog.ayumusato.com/develop/javascript/e528-ios6_scrolling_timer_notcall.html\r
324 // iOS6 スクロール中のタイマー発火絡みのバグ備忘\r
325 if( X_UA.iOS ){\r
326         window.addEventListener( 'scroll', function(){\r
327                 var last, now;\r
328                 if( X_Timer_timerId ){\r
329                         X_Timer_CLEAR_TIMEOUT( X_Timer_timerId );\r
330                         now               = X_Timer_now();\r
331                         last              = X_Timer_timeStamp + X_Timer_INTERVAL_TIME * X_Timer_waitTime - now;\r
332                         X_Timer_timerId   = X_Timer_SET_TIMEOUT( X_Timer_onTimeout, 0 < last ? last : 0 );\r
333                         // 更新\r
334                         X_Timer_timeStamp = now;\r
335                         X_Timer_waitTime  = last / X_Timer_INTERVAL_TIME | 0;\r
336                 };\r
337         });\r
338 };\r
339 \r
340 \r
341 // ページを読み込んでからの時間\r
342 function X_Timer_onEnterFrame( time ){\r
343         var list = X_Timer_REQ_FRAME_LIST,\r
344                 l    = list.length,\r
345                 i    = 0, q, uid, args;\r
346 \r
347         time = time || X_Timer_now();\r
348         X_Timer_busyOnFrame = true;\r
349         // console.log( X_Timer_now() + ' , ' + time );\r
350     for( ; i < l; ++i ){\r
351         q = list[ i ];\r
352         \r
353         if( X_Timer_removal && X_Timer_removal[ q.uid ] ) continue;\r
354         \r
355                 if( q.k ){\r
356                         X_Callback_proxyCallback( q, args || ( args = [ time ] ) );\r
357                 } else {\r
358                         q( time );\r
359                 };\r
360     };\r
361 \r
362     list.splice( 0, l );\r
363     if( list.length ){\r
364         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
365     };\r
366     \r
367     X_Timer_busyOnFrame = false;\r
368     if( X_Timer_removal ){\r
369         for( uid in X_Timer_removal ){\r
370                 //if( X_EMPTY_OBJECT[ uid ] ) continue;\r
371                 X.Timer.cancelFrame( X_Timer_removal[ uid ] );\r
372         };\r
373         X_Timer_removal = null;\r
374     };\r
375 };\r
376 \r
377 console.log( 'X.Core.Timer' );\r