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