1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3  Copyright (c) 2008-2010 Ricardo Quesada
  4  Copyright (c) 2011      Zynga Inc.
  5 
  6  http://www.cocos2d-x.org
  7 
  8  Permission is hereby granted, free of charge, to any person obtaining a copy
  9  of this software and associated documentation files (the "Software"), to deal
 10  in the Software without restriction, including without limitation the rights
 11  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  copies of the Software, and to permit persons to whom the Software is
 13  furnished to do so, subject to the following conditions:
 14 
 15  The above copyright notice and this permission notice shall be included in
 16  all copies or substantial portions of the Software.
 17 
 18  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  THE SOFTWARE.
 25  ****************************************************************************/
 26 
 27 /**
 28  * Priority level reserved for system services.
 29  * @constant
 30  * @type Number
 31  */
 32 cc.PRIORITY_SYSTEM = (-2147483647 - 1);
 33 
 34 /**
 35  * Minimum priority level for user scheduling.
 36  * @constant
 37  * @type Number
 38  */
 39 cc.PRIORITY_NON_SYSTEM = cc.PRIORITY_SYSTEM + 1;
 40 
 41 /**
 42  * Verify Array's Type
 43  * @param {Array} arr
 44  * @param {function} type
 45  * @return {Boolean}
 46  * @function
 47  */
 48 cc.ArrayVerifyType = function (arr, type) {
 49     if (arr && arr.length > 0) {
 50         for (var i = 0; i < arr.length; i++) {
 51             if (!(arr[i] instanceof  type)) {
 52                 cc.log("element type is wrong!");
 53                 return false;
 54             }
 55         }
 56     }
 57     return true;
 58 };
 59 
 60 /**
 61  * Removes object at specified index and pushes back all subsequent objects.Behaviour undefined if index outside [0, num-1].
 62  * @function
 63  * @param {Array} arr Source Array
 64  * @param {Number} index index of remove object
 65  */
 66 cc.ArrayRemoveObjectAtIndex = function (arr, index) {
 67     arr.splice(index, 1);
 68 };
 69 
 70 /**
 71  * Searches for the first occurance of object and removes it. If object is not found the function has no effect.
 72  * @function
 73  * @param {Array} arr Source Array
 74  * @param {*} delObj  remove object
 75  */
 76 cc.ArrayRemoveObject = function (arr, delObj) {
 77     for (var i = 0, l = arr.length; i < l; i++) {
 78         if (arr[i] == delObj) {
 79             arr.splice(i, 1);
 80             break;
 81         }
 82     }
 83 };
 84 
 85 /**
 86  * Removes from arr all values in minusArr. For each Value in minusArr, the first matching instance in arr will be removed.
 87  * @function
 88  * @param {Array} arr Source Array
 89  * @param {Array} minusArr minus Array
 90  */
 91 cc.ArrayRemoveArray = function (arr, minusArr) {
 92     for (var i = 0, l = minusArr.length; i < l; i++) {
 93         cc.ArrayRemoveObject(arr, minusArr[i]);
 94     }
 95 };
 96 
 97 /**
 98  * Returns index of first occurence of value, -1 if value not found.
 99  * @function
100  * @param {Array} arr Source Array
101  * @param {*} value find value
102  * @return {Number} index of first occurence of value
103  */
104 cc.ArrayGetIndexOfValue = function (arr, value) {
105     return arr.indexOf(value);
106 };
107 
108 /**
109  * append an object to array
110  * @function
111  * @param {Array} arr
112  * @param {*} addObj
113  */
114 cc.ArrayAppendObject = function (arr, addObj) {
115     arr.push(addObj);
116 };
117 
118 /**
119  * Inserts an object at index
120  * @function
121  * @param {Array} arr
122  * @param {*} addObj
123  * @param {Number} index
124  * @return {Array}
125  */
126 cc.ArrayAppendObjectToIndex = function (arr, addObj, index) {
127     arr.splice(index, 0, addObj);
128     return arr;
129 };
130 
131 /**
132  * Inserts some objects at index
133  * @function
134  * @param {Array} arr
135  * @param {Array} addObjs
136  * @param {Number} index
137  * @return {Array}
138  */
139 cc.ArrayAppendObjectsToIndex = function(arr, addObjs,index){
140     arr.splice.apply(arr, [index, 0].concat(addObjs));
141     return arr;
142 };
143 
144 /**
145  * Returns index of first occurence of object, -1 if value not found.
146  * @function
147  * @param {Array} arr Source Array
148  * @param {*} findObj find object
149  * @return {Number} index of first occurence of value
150  */
151 cc.ArrayGetIndexOfObject = function (arr, findObj) {
152     for (var i = 0, l = arr.length; i < l; i++) {
153         if (arr[i] == findObj)
154             return i;
155     }
156     return -1;
157 };
158 
159 /**
160  * Returns a Boolean value that indicates whether value is present in the array.
161  * @function
162  * @param {Array} arr
163  * @param {*} findObj
164  * @return {Boolean}
165  */
166 cc.ArrayContainsObject = function (arr, findObj) {
167     return arr.indexOf(findObj) != -1;
168 };
169 
170 /**
171  * find object from array by target
172  * @param {Array} arr source array
173  * @param {cc.ListEntry|cc.HashUpdateEntry} findInt find target
174  * @return {cc.ListEntry|cc.HashUpdateEntry}
175  */
176 cc.HASH_FIND_INT = function (arr, findInt) {
177     if (arr == null) {
178         return null;
179     }
180     for (var i = 0; i < arr.length; i++) {
181         if (arr[i].target === findInt) {
182             return arr[i];
183         }
184     }
185     return null;
186 };
187 
188 //data structures
189 /**
190  * A list double-linked list used for "updates with priority"
191  * @Class
192  * @Construct
193  * @param {cc.ListEntry} prev
194  * @param {cc.ListEntry} next
195  * @param {cc.Class} target not retained (retained by hashUpdateEntry)
196  * @param {Number} priority
197  * @param {Boolean} paused
198  * @param {Boolean} markedForDeletion selector will no longer be called and entry will be removed at end of the next tick
199  */
200 cc.ListEntry = function (prev, next, target, priority, paused, markedForDeletion) {
201     this.prev = prev;
202     this.next = next;
203     this.target = target;
204     this.priority = priority;
205     this.paused = paused;
206     this.markedForDeletion = markedForDeletion;
207 };
208 
209 /**
210  *  a update entry list
211  * @Class
212  * @Construct
213  * @param {cc.ListEntry} list Which list does it belong to ?
214  * @param {cc.ListEntry} entry entry in the list
215  * @param {cc.Class} target hash key (retained)
216  * @param {Array} hh
217  */
218 cc.HashUpdateEntry = function (list, entry, target, hh) {
219     this.list = list;
220     this.entry = entry;
221     this.target = target;
222     this.hh = hh;
223 };
224 
225 //
226 /**
227  * Hash Element used for "selectors with interval"
228  * @Class
229  * @Construct
230  * @param {Array} timers
231  * @param {cc.Class} target  hash key (retained)
232  * @param {Number} timerIndex
233  * @param {cc.Timer} currentTimer
234  * @param {Boolean} currentTimerSalvaged
235  * @param {Boolean} paused
236  * @param {Array} hh
237  */
238 cc.HashTimerEntry = function (timers, target, timerIndex, currentTimer, currentTimerSalvaged, paused, hh) {
239     this.timers = timers;
240     this.target = target;
241     this.timerIndex = timerIndex;
242     this.currentTimer = currentTimer;
243     this.currentTimerSalvaged = currentTimerSalvaged;
244     this.paused = paused;
245     this.hh = hh;
246 };
247 
248 /**
249  * Light weight timer
250  * @class
251  * @extends cc.Class
252  */
253 cc.Timer = cc.Class.extend(/** @lends cc.Timer# */{
254     _interval:0.0,
255     _selector:null,
256 
257     _target:null,
258     _elapsed:0.0,
259 
260     _runForever:false,
261     _useDelay:false,
262     _timesExecuted:0,
263     _repeat:0, //0 = once, 1 is 2 x executed
264     _delay:0,
265 
266     /**
267      * cc.Timer's Constructor
268      * Constructor
269      */
270     ctor:function () {
271     },
272 
273     /**
274      * returns interval of timer
275      * @return {Number}
276      */
277     getInterval:function () {
278         return this._interval;
279     },
280 
281     /**
282      * set interval in seconds
283      * @param {Number} interval
284      */
285     setInterval:function(interval){
286 
287     },
288 
289     /**
290      * returns selector
291      * @return {String|function}
292      */
293     getSelector:function(){
294        return this._selector;
295     },
296 
297     /**
298      * Initializes a timer with a target, a selector and an interval in seconds.
299      * @param {cc.Class} target target
300      * @param {String|function} selector Selector
301      * @param {Number} [seconds=0] second
302      * @param {Number} [repeat=cc.REPEAT_FOREVER] repeat times
303      * @param {Number} [delay=0] delay
304      * @return {Boolean} <tt>true</tt> if initialized
305      * * */
306     initWithTarget: function (target, selector, seconds, repeat, delay) {
307         this._target = target;
308         this._selector = selector;
309         this._elapsed = -1;
310         this._interval = seconds || 0;
311         this._delay = delay || 0;
312         this._useDelay = this._delay > 0;
313         this._repeat = (repeat == null) ? cc.REPEAT_FOREVER : repeat;
314         this._runForever = (this._repeat == cc.REPEAT_FOREVER);
315         return true;
316     },
317 
318     _callSelector:function(){
319         if (typeof(this._selector) == "string")
320             this._target[this._selector](this._elapsed);
321          else // if (typeof(this._selector) == "function") {
322             this._selector.call(this._target, this._elapsed);
323     },
324 
325     /**
326      * triggers the timer
327      * @param {Number} dt delta time
328      */
329     update:function (dt) {
330         if (this._elapsed == -1) {
331             this._elapsed = 0;
332             this._timesExecuted = 0;
333         } else {
334             var locTarget = this._target, locSelector = this._selector;
335             if (this._runForever && !this._useDelay) {
336                 //standard timer usage
337                 this._elapsed += dt;
338 
339                 if (this._elapsed >= this._interval) {
340                     if (locTarget && locSelector)
341                        this._callSelector();
342                     this._elapsed = 0;
343                 }
344             } else {
345                 //advanced usage
346                 this._elapsed += dt;
347                 if (this._useDelay) {
348                     if (this._elapsed >= this._delay) {
349                         if (locTarget && locSelector)
350                             this._callSelector();
351 
352                         this._elapsed = this._elapsed - this._delay;
353                         this._timesExecuted += 1;
354                         this._useDelay = false;
355                     }
356                 } else {
357                     if (this._elapsed >= this._interval) {
358                         if (locTarget && locSelector)
359                             this._callSelector();
360 
361                         this._elapsed = 0;
362                         this._timesExecuted += 1;
363                     }
364                 }
365 
366                 if (this._timesExecuted > this._repeat)
367                     cc.Director.getInstance().getScheduler().unscheduleCallbackForTarget(locTarget, locSelector);
368             }
369         }
370     }
371 });
372 
373 /**
374  * Allocates a timer with a target, a selector and an interval in seconds.
375  * @function
376  * @param {cc.Class} target
377  * @param {String|function} selector Selector
378  * @param {Number} seconds
379  * @return {cc.Timer} a cc.Timer instance
380  * */
381 cc.Timer.timerWithTarget = function (target, selector, seconds) {
382     if (arguments.length < 2){
383         throw new Error("timerWithTarget'argument can't is null");
384     }
385 
386     var timer = new cc.Timer();
387     seconds = seconds||0;
388     timer.initWithTarget(target, selector, seconds, cc.REPEAT_FOREVER, 0);
389     return timer;
390 };
391 
392 cc._sharedScheduler = null;
393 /**
394  * <p>
395  *    Scheduler is responsible of triggering the scheduled callbacks.<br/>
396  *    You should not use NSTimer. Instead use this class.<br/>
397  *    <br/>
398  *    There are 2 different types of callbacks (selectors):<br/>
399  *       - update selector: the 'update' selector will be called every frame. You can customize the priority.<br/>
400  *       - custom selector: A custom selector will be called every frame, or with a custom interval of time<br/>
401  *       <br/>
402  *    The 'custom selectors' should be avoided when possible. It is faster, and consumes less memory to use the 'update selector'. *
403  * </p>
404  * @class
405  * @extends cc.Class
406  *
407  * @example
408  * //register a schedule to scheduler
409  * cc.Director.getInstance().getScheduler().scheduleSelector(selector, this, interval, !this._isRunning);
410  */
411 cc.Scheduler = cc.Class.extend(/** @lends cc.Scheduler# */{
412     _timeScale:1.0,
413     _updatesNegList:null, // list of priority < 0
414     _updates0List:null, // list priority == 0
415     _updatesPosList:null, // list priority > 0
416     _hashForUpdates:null, // hash used to fetch quickly the list entries for pause,delete,etc
417     _arrayForUpdates:null,
418 
419     _hashForTimers:null, //Used for "selectors with interval"
420     _arrayForTimes:null,
421 
422     _currentTarget:null,
423     _currentTargetSalvaged:false,
424     _updateHashLocked:false, //If true unschedule will not remove anything from a hash. Elements will only be marked for deletion.
425 
426     /**
427      * Constructor
428      */
429     ctor:function () {
430         this._timeScale = 1.0;
431 
432         this._updatesNegList = [];
433         this._updates0List = [];
434         this._updatesPosList = [];
435 
436         this._hashForUpdates = {};
437         this._arrayForUpdates = [];
438 
439         this._hashForTimers = {};
440         this._arrayForTimers = [];
441 
442         this._currentTarget = null;
443         this._currentTargetSalvaged = false;
444         this._updateHashLocked = false;
445     },
446 
447     //-----------------------private method----------------------
448     _removeHashElement:function (element) {
449         delete this._hashForTimers[element.target.__instanceId];
450         cc.ArrayRemoveObject(this._arrayForTimers, element);
451         element.Timer = null;
452         element.target = null;
453         element = null;
454     },
455 
456     /**
457      * find Object from Array
458      * @private
459      * @param {Array} array Array
460      * @param {cc.Class} target object
461      * @return {cc.ListEntry} object if found, or return null
462      */
463     _findElementFromArray:function (array, target) {
464         for (var i = 0; i < array.length; i++) {
465             if (array[i].target == target) {
466                 return array[i];
467             }
468         }
469         return null;
470     },
471 
472     _removeUpdateFromHash:function (entry) {
473 //        var element = this._findElementFromArray(this._hashForUpdates, entry.target);
474         var element = this._hashForUpdates[entry.target.__instanceId];
475         if (element) {
476             //list entry
477             cc.ArrayRemoveObject(element.list, element.entry);
478 
479             delete this._hashForUpdates[element.target.__instanceId];
480             cc.ArrayRemoveObject(this._arrayForUpdates, element);
481             element.entry = null;
482 
483             //hash entry
484             element.target = null;
485 //            cc.ArrayRemoveObject(this._hashForUpdates, element);
486         }
487     },
488 
489     _priorityIn:function (ppList, target, priority, paused) {
490         var listElement = new cc.ListEntry(null, null, target, priority, paused, false);
491 
492         // empey list ?
493         if (!ppList) {
494             ppList = [];
495             ppList.push(listElement);
496         } else {
497             var added = false;
498             for (var i = 0; i < ppList.length; i++) {
499                 if (priority < ppList[i].priority) {
500                     ppList = cc.ArrayAppendObjectToIndex(ppList, listElement, i);
501                     added = true;
502                     break;
503                 }
504             }
505 
506             // Not added? priority has the higher value. Append it.
507             if (!added) {
508                 ppList.push(listElement);
509             }
510         }
511 
512         //update hash entry for quick access
513         var hashElement = new cc.HashUpdateEntry(ppList, listElement, target, null);
514         this._arrayForUpdates.push(hashElement);
515         this._hashForUpdates[target.__instanceId] = hashElement;
516 //        this._hashForUpdates.push(hashElement);
517 
518         return ppList;
519     },
520 
521     _appendIn:function (ppList, target, paused) {
522         var listElement = new cc.ListEntry(null, null, target, 0, paused, false);
523         ppList.push(listElement);
524 
525         //update hash entry for quicker access
526         var hashElement = new cc.HashUpdateEntry(ppList, listElement, target, null);
527         this._arrayForUpdates.push(hashElement);
528         this._hashForUpdates[target.__instanceId] = hashElement;
529 //        this._hashForUpdates.push(hashElement);
530     },
531 
532     //-----------------------public method-------------------------
533     /**
534      * <p>
535      *    Modifies the time of all scheduled callbacks.<br/>
536      *    You can use this property to create a 'slow motion' or 'fast forward' effect.<br/>
537      *    Default is 1.0. To create a 'slow motion' effect, use values below 1.0.<br/>
538      *    To create a 'fast forward' effect, use values higher than 1.0.<br/>
539      *    @warning It will affect EVERY scheduled selector / action.
540      * </p>
541      * @param {Number} timeScale
542      */
543     setTimeScale:function (timeScale) {
544         this._timeScale = timeScale;
545     },
546 
547     /**
548      * returns time scale of scheduler
549      * @return {Number}
550      */
551     getTimeScale:function () {
552         return this._timeScale;
553     },
554 
555     /**
556      * 'update' the scheduler. (You should NEVER call this method, unless you know what you are doing.)
557      * @param {Number} dt delta time
558      */
559     update:function (dt) {
560         this._updateHashLocked = true;
561 
562         if (this._timeScale != 1.0) {
563             dt *= this._timeScale;
564         }
565 
566         //Iterate all over the Updates selectors
567         var tmpEntry;
568         var i;
569         for (i = 0; i < this._updatesNegList.length; i++) {
570             tmpEntry = this._updatesNegList[i];
571             if ((!tmpEntry.paused) && (!tmpEntry.markedForDeletion)) {
572                 tmpEntry.target.update(dt);
573             }
574         }
575 
576         // updates with priority == 0
577         for (i = 0; i < this._updates0List.length; i++) {
578             tmpEntry = this._updates0List[i];
579             if ((!tmpEntry.paused) && (!tmpEntry.markedForDeletion)) {
580                 tmpEntry.target.update(dt);
581             }
582         }
583 
584         // updates with priority > 0
585         for (i = 0; i < this._updatesPosList.length; i++) {
586             tmpEntry = this._updatesPosList[i];
587             if ((!tmpEntry.paused) && (!tmpEntry.markedForDeletion)) {
588                 tmpEntry.target.update(dt);
589             }
590         }
591 
592         //Interate all over the custom selectors
593         var elt;
594         for (i = 0; i < this._arrayForTimers.length; i++) {
595             this._currentTarget = this._arrayForTimers[i];
596             elt = this._currentTarget;
597             this._currentTargetSalvaged = false;
598 
599             if (!this._currentTarget.paused) {
600                 // The 'timers' array may change while inside this loop
601                 for (elt.timerIndex = 0; elt.timerIndex < elt.timers.length; elt.timerIndex++) {
602                     elt.currentTimer = elt.timers[elt.timerIndex];
603                     elt.currentTimerSalvaged = false;
604 
605                     elt.currentTimer.update(dt);
606                     elt.currentTimer = null;
607                 }
608             }
609 
610             if ((this._currentTargetSalvaged) && (this._currentTarget.timers.length == 0)) {
611                 this._removeHashElement(this._currentTarget);
612             }
613         }
614 
615         //delete all updates that are marked for deletion
616         // updates with priority < 0
617         for (i = 0; i < this._updatesNegList.length; i++) {
618             if (this._updatesNegList[i].markedForDeletion) {
619                 this._removeUpdateFromHash(this._updatesNegList[i]);
620             }
621         }
622 
623         // updates with priority == 0
624         for (i = 0; i < this._updates0List.length; i++) {
625             if (this._updates0List[i].markedForDeletion) {
626                 this._removeUpdateFromHash(this._updates0List[i]);
627             }
628         }
629 
630         // updates with priority > 0
631         for (i = 0; i < this._updatesPosList.length; i++) {
632             if (this._updatesPosList[i].markedForDeletion) {
633                 this._removeUpdateFromHash(this._updatesPosList[i]);
634             }
635         }
636 
637         this._updateHashLocked = false;
638         this._currentTarget = null;
639     },
640 
641     /**
642      * <p>
643      *   The scheduled method will be called every 'interval' seconds.</br>
644      *   If paused is YES, then it won't be called until it is resumed.<br/>
645      *   If 'interval' is 0, it will be called every frame, but if so, it recommended to use 'scheduleUpdateForTarget:' instead.<br/>
646      *   If the callback function is already scheduled, then only the interval parameter will be updated without re-scheduling it again.<br/>
647      *   repeat let the action be repeated repeat + 1 times, use cc.REPEAT_FOREVER to let the action run continuously<br/>
648      *   delay is the amount of time the action will wait before it'll start<br/>
649      * </p>
650      * @param {cc.Class} target
651      * @param {function} callback_fn
652      * @param {Number} interval
653      * @param {Number} repeat
654      * @param {Number} delay
655      * @param {Boolean} paused
656      * @example
657      * //register a schedule to scheduler
658      * cc.Director.getInstance().getScheduler().scheduleCallbackForTarget(this, function, interval, repeat, delay, !this._isRunning );
659      */
660     scheduleCallbackForTarget:function (target, callback_fn, interval, repeat, delay, paused) {
661         if(!callback_fn)
662             throw "cc.scheduler.scheduleCallbackForTarget(): callback_fn should be non-null.";
663 
664         if(!target)
665             throw "cc.scheduler.scheduleCallbackForTarget(): target should be non-null.";
666 
667         // default arguments
668         interval = interval || 0;
669         repeat = (repeat == null) ? cc.REPEAT_FOREVER : repeat;
670         delay = delay || 0;
671         paused = paused || false;
672 
673         var element = this._hashForTimers[target.__instanceId];
674 
675         if (!element) {
676             // Is this the 1st element ? Then set the pause level to all the callback_fns of this target
677             element = new cc.HashTimerEntry(null, target, 0, null, null, paused, null);
678             this._arrayForTimers.push(element);
679             this._hashForTimers[target.__instanceId] = element;
680         }
681 
682         var timer;
683         if (element.timers == null) {
684             element.timers = [];
685         } else {
686             for (var i = 0; i < element.timers.length; i++) {
687                 timer = element.timers[i];
688                 if (callback_fn == timer._selector) {
689                     cc.log("CCSheduler#scheduleCallback. Callback already scheduled. Updating interval from:"
690                         + timer.getInterval().toFixed(4) + " to " + interval.toFixed(4));
691                     timer._interval = interval;
692                     return;
693                 }
694             }
695         }
696 
697         timer = new cc.Timer();
698         timer.initWithTarget(target, callback_fn, interval, repeat, delay);
699         element.timers.push(timer);
700     },
701 
702     /**
703      * <p>
704      *    Schedules the 'update' callback_fn for a given target with a given priority.<br/>
705      *    The 'update' callback_fn will be called every frame.<br/>
706      *    The lower the priority, the earlier it is called.
707      * </p>
708      * @param {cc.Class} target
709      * @param {Number} priority
710      * @param {Boolean} paused
711      * @example
712      * //register this object to scheduler
713      * cc.Director.getInstance().getScheduler().scheduleUpdateForTarget(this, priority, !this._isRunning );
714      */
715     scheduleUpdateForTarget:function (target, priority, paused) {
716         var hashElement = this._hashForUpdates[target.__instanceId];
717 
718         if (hashElement) {
719             // TODO: check if priority has changed!
720             hashElement.entry.markedForDeletion = false;
721             return;
722         }
723 
724         // most of the updates are going to be 0, that's way there
725         // is an special list for updates with priority 0
726         if (priority == 0) {
727             this._appendIn(this._updates0List, target, paused);
728         } else if (priority < 0) {
729             this._updatesNegList = this._priorityIn(this._updatesNegList, target, priority, paused);
730         } else {
731             // priority > 0
732             this._updatesPosList = this._priorityIn(this._updatesPosList, target, priority, paused);
733         }
734     },
735 
736     /**
737      * <p>
738      *   Unschedule a callback function for a given target.<br/>
739      *   If you want to unschedule the "update", use unscheudleUpdateForTarget.
740      * </p>
741      * @param {cc.Class} target
742      * @param {function} callback_fn
743      * @example
744      * //unschedule a selector of target
745      * cc.Director.getInstance().getScheduler().unscheduleCallbackForTarget(function, this);
746      */
747     unscheduleCallbackForTarget:function (target, callback_fn) {
748         // explicity handle nil arguments when removing an object
749         if ((target == null) || (callback_fn == null)) {
750             return;
751         }
752 
753         var element = this._hashForTimers[target.__instanceId];
754         if (element != null) {
755             for (var i = 0; i < element.timers.length; i++) {
756                 var timer = element.timers[i];
757                 if (callback_fn == timer._selector) {
758                     if ((timer == element.currentTimer) && (!element.currentTimerSalvaged)) {
759                         element.currentTimerSalvaged = true;
760                     }
761                     cc.ArrayRemoveObjectAtIndex(element.timers, i);
762                     //update timerIndex in case we are in tick;, looping over the actions
763                     if (element.timerIndex >= i) {
764                         element.timerIndex--;
765                     }
766 
767                     if (element.timers.length == 0) {
768                         if (this._currentTarget == element) {
769                             this._currentTargetSalvaged = true;
770                         } else {
771 
772                             this._removeHashElement(element);
773                         }
774                     }
775                     return;
776                 }
777             }
778         }
779     },
780 
781     /**
782      * Unschedules the update callback function for a given target
783      * @param {cc.Class} target
784      * @example
785      * //unschedules the "update" method.
786      * cc.Director.getInstance().getScheduler().unscheduleUpdateForTarget(this);
787      */
788     unscheduleUpdateForTarget:function (target) {
789         if (target == null) {
790             return;
791         }
792 
793         var element = this._hashForUpdates[target.__instanceId];
794         if (element != null) {
795             if (this._updateHashLocked) {
796                 element.entry.markedForDeletion = true;
797             } else {
798                 this._removeUpdateFromHash(element.entry);
799             }
800         }
801     },
802 
803     /**
804      * Unschedules all function callbacks for a given target. This also includes the "update" callback function.
805      * @param {cc.Class} target
806      */
807     unscheduleAllCallbacksForTarget:function (target) {
808         //explicit NULL handling
809         if (target == null) {
810             return;
811         }
812 
813         var element = this._hashForTimers[target.__instanceId];
814         if (element) {
815             if ((!element.currentTimerSalvaged) && (cc.ArrayContainsObject(element.timers, element.currentTimer))) {
816                 element.currentTimerSalvaged = true;
817             }
818             element.timers.length = 0;
819 
820             if (this._currentTarget == element) {
821                 this._currentTargetSalvaged = true;
822             } else {
823                 this._removeHashElement(element);
824             }
825         }
826         // update selector
827         this.unscheduleUpdateForTarget(target);
828     },
829 
830     /**
831      *  <p>
832      *      Unschedules all function callbacks from all targets. <br/>
833      *      You should NEVER call this method, unless you know what you are doing.
834      *  </p>
835      */
836     unscheduleAllCallbacks:function () {
837         this.unscheduleAllCallbacksWithMinPriority(cc.PRIORITY_SYSTEM);
838     },
839 
840     /**
841      * <p>
842      *    Unschedules all function callbacks from all targets with a minimum priority.<br/>
843      *    You should only call this with kCCPriorityNonSystemMin or higher.
844      * </p>
845      * @param {Number} minPriority
846      */
847     unscheduleAllCallbacksWithMinPriority:function (minPriority) {
848         // Custom Selectors
849         var i;
850         for (i = 0; i < this._arrayForTimers.length; i++) {
851             // element may be removed in unscheduleAllCallbacksForTarget
852             this.unscheduleAllCallbacksForTarget(this._arrayForTimers[i].target);
853         }
854 
855         //updates selectors
856         if (minPriority < 0) {
857             for (i = 0; i < this._updatesNegList.length; i++) {
858                 this.unscheduleUpdateForTarget(this._updatesNegList[i].target);
859             }
860         }
861 
862         if (minPriority <= 0) {
863             for (i = 0; i < this._updates0List.length; i++) {
864                 this.unscheduleUpdateForTarget(this._updates0List[i].target);
865             }
866         }
867 
868         for (i = 0; i < this._updatesPosList.length; i++) {
869             if (this._updatesPosList[i].priority >= minPriority) {
870                 this.unscheduleUpdateForTarget(this._updatesPosList[i].target);
871             }
872         }
873     },
874 
875     /**
876      * <p>
877      *  Pause all selectors from all targets.<br/>
878      *  You should NEVER call this method, unless you know what you are doing.
879      * </p>
880      */
881     pauseAllTargets:function () {
882         return this.pauseAllTargetsWithMinPriority(cc.PRIORITY_SYSTEM);
883     },
884 
885     /**
886      * Pause all selectors from all targets with a minimum priority. <br/>
887      * You should only call this with kCCPriorityNonSystemMin or higher.
888      * @param minPriority
889      */
890     pauseAllTargetsWithMinPriority:function (minPriority) {
891         var idsWithSelectors = [];
892 
893         var i, element;
894         // Custom Selectors
895         for (i = 0; i < this._arrayForTimers.length; i++) {
896             element = this._arrayForTimers[i];
897             if (element) {
898                 element.paused = true;
899                 idsWithSelectors.push(element.target);
900             }
901         }
902 
903         // Updates selectors
904         if (minPriority < 0) {
905             for (i = 0; i < this._updatesNegList.length; i++) {
906                 element = this._updatesNegList[i];
907                 if (element) {
908                     element.paused = true;
909                     idsWithSelectors.push(element.target);
910                 }
911             }
912         }
913 
914         if (minPriority <= 0) {
915             for (i = 0; i < this._updates0List.length; i++) {
916                 element = this._updates0List[i];
917                 if (element) {
918                     element.paused = true;
919                     idsWithSelectors.push(element.target);
920                 }
921             }
922         }
923 
924         for (i = 0; i < this._updatesPosList.length; i++) {
925             element = this._updatesPosList[i];
926             if (element) {
927                 element.paused = true;
928                 idsWithSelectors.push(element.target);
929             }
930         }
931 
932         return idsWithSelectors;
933     },
934 
935     /**
936      * Resume selectors on a set of targets.<br/>
937      * This can be useful for undoing a call to pauseAllCallbacks.
938      * @param targetsToResume
939      */
940     resumeTargets:function (targetsToResume) {
941         if (!targetsToResume)
942             return;
943 
944         for (var i = 0; i < targetsToResume.length; i++) {
945             this.resumeTarget(targetsToResume[i]);
946         }
947     },
948 
949     /**
950      * <p>
951      *    Pauses the target.<br/>
952      *    All scheduled selectors/update for a given target won't be 'ticked' until the target is resumed.<br/>
953      *    If the target is not present, nothing happens.
954      * </p>
955      * @param {cc.Class} target
956      */
957     pauseTarget:function (target) {
958         if(!target)
959             throw "cc.Scheduler.pauseTarget():target should be non-null";
960 
961         //customer selectors
962         var element = this._hashForTimers[target.__instanceId];
963         if (element) {
964             element.paused = true;
965         }
966 
967         //update selector
968         var elementUpdate = this._hashForUpdates[target.__instanceId];
969         if (elementUpdate) {
970             elementUpdate.entry.paused = true;
971         }
972     },
973 
974     /**
975      * Resumes the target.<br/>
976      * The 'target' will be unpaused, so all schedule selectors/update will be 'ticked' again.<br/>
977      * If the target is not present, nothing happens.
978      * @param {cc.Class} target
979      */
980     resumeTarget:function (target) {
981         if(!target)
982             throw "cc.Scheduler.resumeTarget():target should be non-null";
983 
984         // custom selectors
985         var element = this._hashForTimers[target.__instanceId];
986 
987         if (element) {
988             element.paused = false;
989         }
990 
991         //update selector
992         var elementUpdate = this._hashForUpdates[target.__instanceId];
993 
994         if (elementUpdate) {
995             elementUpdate.entry.paused = false;
996         }
997     },
998 
999     /**
1000      * Returns whether or not the target is paused
1001      * @param {cc.Class} target
1002      * @return {Boolean}
1003      */
1004     isTargetPaused:function (target) {
1005         if(!target)
1006             throw "cc.Scheduler.isTargetPaused():target should be non-null";
1007 
1008         // Custom selectors
1009         var element = this._hashForTimers[target.__instanceId];
1010         if (element) {
1011             return element.paused;
1012         }
1013         return false;
1014     }
1015 });
1016 
1017