OSDN Git Service

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