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  * @class
 29  * @extends cc.Class
 30  */
 31 cc.HashElement = cc.Class.extend(/** @lends cc.HashElement# */{
 32     actions:null,
 33     target:null, //ccobject
 34     actionIndex:0,
 35     currentAction:null, //CCAction
 36     currentActionSalvaged:false,
 37     paused:false,
 38     hh:null, //ut hash handle
 39     /**
 40      * Constructor
 41      */
 42     ctor:function () {
 43         this.actions = [];
 44         this._target = null;
 45         this.actionIndex = 0;
 46         this.currentAction = null; //CCAction
 47         this.currentActionSalvaged = false;
 48         this.paused = false;
 49         this.hh = null; //ut hash handle
 50     }
 51 });
 52 
 53 /**
 54  * cc.ActionManager is a singleton that manages all the actions.<br/>
 55  * Normally you won't need to use this singleton directly. 99% of the cases you will use the CCNode interface,
 56  * which uses this singleton.
 57  * But there are some cases where you might need to use this singleton. <br/>
 58  * Examples:<br/>
 59  * - When you want to run an action where the target is different from a CCNode.<br/>
 60  * - When you want to pause / resume the actions<br/>
 61  * @class
 62  * @extends cc.Class
 63  */
 64 cc.ActionManager = cc.Class.extend({
 65     _targets:null,
 66     _currentTarget:null,
 67     _currentTargetSalvaged:false,
 68 
 69     _searchElementByTarget:function (arr, target) {
 70         for (var k = 0; k < arr.length; k++) {
 71             if (target == arr[k].target)
 72                 return arr[k];
 73         }
 74         return null;
 75     },
 76 
 77     /**
 78      * Constructor
 79      */
 80     ctor:function () {
 81         this._targets = [];
 82         this._currentTarget = null;
 83         this._currentTargetSalvaged = false;
 84     },
 85 
 86     /** Adds an action with a target.
 87      * If the target is already present, then the action will be added to the existing target.
 88      * If the target is not present, a new instance of this target will be created either paused or not, and the action will be added to the newly created target.
 89      * When the target is paused, the queued actions won't be 'ticked'.
 90      * @param {cc.Action} action
 91      * @param {cc.Node} target
 92      * @param {Boolean} paused
 93      */
 94     addAction:function (action, target, paused) {
 95         if(!action)
 96             throw "cc.ActionManager.addAction(): action must be non-null";
 97         if(!target)
 98             throw "cc.ActionManager.addAction(): action must be non-null";
 99 
100         //check if the action target already exists
101         var element = this._searchElementByTarget(this._targets, target);
102         //if doesnt exists, create a hashelement and push in mpTargets
103         if (!element) {
104             element = new cc.HashElement();
105             element.paused = paused;
106             element.target = target;
107             this._targets.push(element);
108         }
109         //creates a array for that eleemnt to hold the actions
110         this._actionAllocWithHashElement(element);
111 
112         element.actions.push(action);
113         action.startWithTarget(target);
114     },
115 
116     /**
117      * Removes all actions from all the targets.
118      */
119     removeAllActions:function () {
120         var locTargets = this._targets;
121         for (var i = 0; i < locTargets.length; i++) {
122             var element = locTargets[i];
123             if (element)
124                 this.removeAllActionsFromTarget(element.target, true);
125         }
126     },
127     /** Removes all actions from a certain target. <br/>
128      * All the actions that belongs to the target will be removed.
129      * @param {object} target
130      * @param {boolean} forceDelete
131      */
132     removeAllActionsFromTarget:function (target, forceDelete) {
133         // explicit null handling
134         if (target == null)
135             return;
136         var element = this._searchElementByTarget(this._targets, target);
137 
138         //var element = (target in this._targets)? this._targets[ptarget]: null;
139         if (element) {
140             if (element.actions.indexOf(element.currentAction) !== -1 && !(element.currentActionSalvaged))
141                 element.currentActionSalvaged = true;
142 
143             element.actions = [];
144             if (this._currentTarget == element && !forceDelete) {
145                 this._currentTargetSalvaged = true;
146             } else {
147                 this._deleteHashElement(element);
148             }
149         } //else {
150             //cc.log("cocos2d: removeAllActionsFromTarget: Target not found");
151         //}
152     },
153     /** Removes an action given an action reference.
154      * @param {cc.Action} action
155      */
156     removeAction:function (action) {
157         // explicit null handling
158         if (action == null)
159             return;
160         var target = action.getOriginalTarget();
161         var element = this._searchElementByTarget(this._targets, target);
162 
163         if (element) {
164             for (var i = 0; i < element.actions.length; i++) {
165                 if (element.actions[i] == action) {
166                     element.actions.splice(i, 1);
167                     break;
168                 }
169             }
170         } else {
171             cc.log("cocos2d: removeAction: Target not found");
172         }
173     },
174 
175     /** Removes an action given its tag and the target
176      * @param {Number} tag
177      * @param {object} target
178      */
179     removeActionByTag:function (tag, target) {
180         if(tag == cc.ACTION_TAG_INVALID)
181             cc.log("cc.ActionManager.removeActionByTag(): an invalid tag");
182         if(!target)
183             throw "cc.ActionManager.removeActionByTag(): target must be non-null";
184 
185         var element = this._searchElementByTarget(this._targets, target);
186 
187         if (element) {
188             var limit = element.actions.length;
189             for (var i = 0; i < limit; ++i) {
190                 var action = element.actions[i];
191                 if (action) {
192                     if (action.getTag() === tag && action.getOriginalTarget() == target) {
193                         this._removeActionAtIndex(i, element);
194                         break;
195                     }
196                 }
197             }
198         }
199     },
200 
201     /** Gets an action given its tag an a target
202      * @param {Number} tag
203      * @param {object} target
204      * @return {cc.Action|Null}  return the Action with the given tag on success
205      */
206     getActionByTag:function (tag, target) {
207         if(tag == cc.ACTION_TAG_INVALID)
208             cc.log("cc.ActionManager.getActionByTag(): an invalid tag");
209 
210         var element = this._searchElementByTarget(this._targets, target);
211         if (element) {
212             if (element.actions != null) {
213                 for (var i = 0; i < element.actions.length; ++i) {
214                     var action = element.actions[i];
215                     if (action && action.getTag() === tag)
216                         return action;
217                 }
218             }
219             cc.log("cocos2d : getActionByTag(tag =" + tag + "): Action not found");
220         }
221         return null;
222     },
223 
224 
225     /** Returns the numbers of actions that are running in a certain target. <br/>
226      * Composable actions are counted as 1 action. <br/>
227      * Example: <br/>
228      * - If you are running 1 Sequence of 7 actions, it will return 1. <br/>
229      * - If you are running 7 Sequences of 2 actions, it will return 7.
230      * @param {object} target
231      * @return {Number}
232      */
233     numberOfRunningActionsInTarget:function (target) {
234         var element = this._searchElementByTarget(this._targets, target);
235         if (element)
236             return (element.actions) ? element.actions.length : 0;
237 
238         return 0;
239     },
240     /** Pauses the target: all running actions and newly added actions will be paused.
241      * @param {object} target
242      */
243     pauseTarget:function (target) {
244         var element = this._searchElementByTarget(this._targets, target);
245         if (element)
246             element.paused = true;
247     },
248     /** Resumes the target. All queued actions will be resumed.
249      * @param {object} target
250      */
251     resumeTarget:function (target) {
252         var element = this._searchElementByTarget(this._targets, target);
253         if (element)
254             element.paused = false;
255     },
256 
257     /**
258      * Pauses all running actions, returning a list of targets whose actions were paused.
259      */
260     pauseAllRunningActions:function(){
261         var idsWithActions = [];
262         var locTargets = this._targets;
263         for(var i = 0; i< locTargets.length; i++){
264             var element = locTargets[i];
265             if(element && !element.paused){
266                 element.paused = true;
267                 idsWithActions.push(element.target);
268             }
269         }
270         return idsWithActions;
271     },
272 
273     /**
274      * Resume a set of targets (convenience function to reverse a pauseAllRunningActions call)
275      * @param {Array} targetsToResume
276      */
277     resumeTargets:function(targetsToResume){
278         if(!targetsToResume)
279             return;
280 
281         for(var i = 0 ; i< targetsToResume.length; i++){
282             if(targetsToResume[i])
283                 this.resumeTarget(targetsToResume[i]);
284         }
285     },
286 
287     /** purges the shared action manager. It releases the retained instance. <br/>
288      * because it uses this, so it can not be static
289      */
290     purgeSharedManager:function () {
291         cc.Director.getInstance().getScheduler().unscheduleUpdateForTarget(this);
292     },
293 
294     //protected
295     _removeActionAtIndex:function (index, element) {
296         var action = element.actions[index];
297 
298         if ((action == element.currentAction) && (!element.currentActionSalvaged))
299             element.currentActionSalvaged = true;
300 
301         cc.ArrayRemoveObjectAtIndex(element.actions,index);
302 
303         // update actionIndex in case we are in tick. looping over the actions
304         if (element.actionIndex >= index)
305             element.actionIndex--;
306 
307         if (element.actions.length == 0) {
308             if (this._currentTarget == element) {
309                 this._currentTargetSalvaged = true;
310             } else {
311                 this._deleteHashElement(element);
312             }
313         }
314     },
315 
316     _deleteHashElement:function (element) {
317         cc.ArrayRemoveObject(this._targets, element);
318         if (element) {
319             element.actions = null;
320             element.target = null;
321         }
322     },
323 
324     _actionAllocWithHashElement:function (element) {
325         // 4 actions per Node by default
326         if (element.actions == null) {
327             element.actions = [];
328         }
329     },
330 
331     /**
332      * @param {Number} dt delta time in seconds
333      */
334     update:function (dt) {
335         var locTargets = this._targets , locCurrTarget;
336         for (var elt = 0; elt < locTargets.length; elt++) {
337             this._currentTarget = locTargets[elt];
338             locCurrTarget = this._currentTarget;
339             //this._currentTargetSalvaged = false;
340             if (!locCurrTarget.paused) {
341                 // The 'actions' CCMutableArray may change while inside this loop.
342                 for (locCurrTarget.actionIndex = 0; locCurrTarget.actionIndex < locCurrTarget.actions.length;
343                      locCurrTarget.actionIndex++) {
344                     locCurrTarget.currentAction = locCurrTarget.actions[locCurrTarget.actionIndex];
345                     if (!locCurrTarget.currentAction)
346                         continue;
347 
348                     locCurrTarget.currentActionSalvaged = false;
349                     locCurrTarget.currentAction.step(dt);
350                     if (locCurrTarget.currentActionSalvaged) {
351                         // The currentAction told the node to remove it. To prevent the action from
352                         // accidentally deallocating itself before finishing its step, we retained
353                         // it. Now that step is done, it's safe to release it.
354                         locCurrTarget.currentAction = null;//release
355                     } else if (locCurrTarget.currentAction.isDone()) {
356                         locCurrTarget.currentAction.stop();
357                         var action = locCurrTarget.currentAction;
358                         // Make currentAction nil to prevent removeAction from salvaging it.
359                         locCurrTarget.currentAction = null;
360                         this.removeAction(action);
361                     }
362 
363                     locCurrTarget.currentAction = null;
364                 }
365             }
366 
367             // elt, at this moment, is still valid
368             // so it is safe to ask this here (issue #490)
369 
370             // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
371             if (this._currentTargetSalvaged && locCurrTarget.actions.length === 0) {
372                 this._deleteHashElement(locCurrTarget);
373             }
374         }
375     }
376 });
377