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         cc.Assert(action != null, "no action");
 96         cc.Assert(target != null, "");
 97         //check if the action target already exists
 98         var element = this._searchElementByTarget(this._targets, target);
 99         //if doesnt exists, create a hashelement and push in mpTargets
100         if (!element) {
101             element = new cc.HashElement();
102             element.paused = paused;
103             element.target = target;
104             this._targets.push(element);
105         }
106         //creates a array for that eleemnt to hold the actions
107         this._actionAllocWithHashElement(element);
108 
109         element.actions.push(action);
110         action.startWithTarget(target);
111     },
112 
113     /**
114      * Removes all actions from all the targets.
115      */
116     removeAllActions:function () {
117         var locTargets = this._targets;
118         for (var i = 0; i < locTargets.length; i++) {
119             var element = locTargets[i];
120             if (element)
121                 this.removeAllActionsFromTarget(element.target, true);
122         }
123     },
124     /** Removes all actions from a certain target. <br/>
125      * All the actions that belongs to the target will be removed.
126      * @param {object} target
127      * @param {boolean} forceDelete
128      */
129     removeAllActionsFromTarget:function (target, forceDelete) {
130         // explicit null handling
131         if (target == null)
132             return;
133         var element = this._searchElementByTarget(this._targets, target);
134 
135         //var element = (target in this._targets)? this._targets[ptarget]: null;
136         if (element) {
137             if (element.actions.indexOf(element.currentAction) !== -1 && !(element.currentActionSalvaged))
138                 element.currentActionSalvaged = true;
139 
140             element.actions = [];
141             if (this._currentTarget == element && !forceDelete) {
142                 this._currentTargetSalvaged = true;
143             } else {
144                 this._deleteHashElement(element);
145             }
146         } //else {
147             //cc.log("cocos2d: removeAllActionsFromTarget: Target not found");
148         //}
149     },
150     /** Removes an action given an action reference.
151      * @param {cc.Action} action
152      */
153     removeAction:function (action) {
154         // explicit null handling
155         if (action == null)
156             return;
157         var target = action.getOriginalTarget();
158         var element = this._searchElementByTarget(this._targets, target);
159 
160         if (element) {
161             for (var i = 0; i < element.actions.length; i++) {
162                 if (element.actions[i] == action) {
163                     element.actions.splice(i, 1);
164                     break;
165                 }
166             }
167         } else {
168             cc.log("cocos2d: removeAction: Target not found");
169         }
170     },
171 
172     /** Removes an action given its tag and the target
173      * @param {Number} tag
174      * @param {object} target
175      */
176     removeActionByTag:function (tag, target) {
177         cc.Assert(tag != cc.ACTION_TAG_INVALID, "");
178         cc.Assert(target != null, "");
179 
180         var element = this._searchElementByTarget(this._targets, target);
181 
182         if (element) {
183             var limit = element.actions.length;
184             for (var i = 0; i < limit; ++i) {
185                 var action = element.actions[i];
186                 if (action) {
187                     if (action.getTag() === tag && action.getOriginalTarget() == target) {
188                         this._removeActionAtIndex(i, element);
189                         break;
190                     }
191                 }
192             }
193         }
194     },
195 
196     /** Gets an action given its tag an a target
197      * @param {Number} tag
198      * @param {object} target
199      * @return {cc.Action|Null}  return the Action with the given tag on success
200      */
201     getActionByTag:function (tag, target) {
202         cc.Assert(tag != cc.ACTION_TAG_INVALID, "");
203         var element = this._searchElementByTarget(this._targets, target);
204         if (element) {
205             if (element.actions != null) {
206                 for (var i = 0; i < element.actions.length; ++i) {
207                     var action = element.actions[i];
208                     if (action && action.getTag() === tag)
209                         return action;
210                 }
211             }
212             cc.log("cocos2d : getActionByTag(tag =" + tag + "): Action not found");
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         return 0;
232     },
233     /** Pauses the target: all running actions and newly added actions will be paused.
234      * @param {object} target
235      */
236     pauseTarget:function (target) {
237         var element = this._searchElementByTarget(this._targets, target);
238         if (element)
239             element.paused = true;
240     },
241     /** Resumes the target. All queued actions will be resumed.
242      * @param {object} target
243      */
244     resumeTarget:function (target) {
245         var element = this._searchElementByTarget(this._targets, target);
246         if (element)
247             element.paused = false;
248     },
249 
250     /**
251      * Pauses all running actions, returning a list of targets whose actions were paused.
252      */
253     pauseAllRunningActions:function(){
254         var idsWithActions = [];
255         var locTargets = this._targets;
256         for(var i = 0; i< locTargets.length; i++){
257             var element = locTargets[i];
258             if(element && !element.paused){
259                 element.paused = true;
260                 idsWithActions.push(element.target);
261             }
262         }
263         return idsWithActions;
264     },
265 
266     /**
267      * Resume a set of targets (convenience function to reverse a pauseAllRunningActions call)
268      * @param {Array} targetsToResume
269      */
270     resumeTargets:function(targetsToResume){
271         if(!targetsToResume)
272             return;
273 
274         for(var i = 0 ; i< targetsToResume.length; i++){
275             if(targetsToResume[i])
276                 this.resumeTarget(targetsToResume[i]);
277         }
278     },
279 
280     /** purges the shared action manager. It releases the retained instance. <br/>
281      * because it uses this, so it can not be static
282      */
283     purgeSharedManager:function () {
284         cc.Director.getInstance().getScheduler().unscheduleUpdateForTarget(this);
285     },
286 
287     //protected
288     _removeActionAtIndex:function (index, element) {
289         var action = element.actions[index];
290 
291         if ((action == element.currentAction) && (!element.currentActionSalvaged))
292             element.currentActionSalvaged = true;
293 
294         cc.ArrayRemoveObjectAtIndex(element.actions,index);
295 
296         // update actionIndex in case we are in tick. looping over the actions
297         if (element.actionIndex >= index)
298             element.actionIndex--;
299 
300         if (element.actions.length == 0) {
301             if (this._currentTarget == element) {
302                 this._currentTargetSalvaged = true;
303             } else {
304                 this._deleteHashElement(element);
305             }
306         }
307     },
308 
309     _deleteHashElement:function (element) {
310         cc.ArrayRemoveObject(this._targets, element);
311         if (element) {
312             element.actions = null;
313             element.target = null;
314         }
315     },
316 
317     _actionAllocWithHashElement:function (element) {
318         // 4 actions per Node by default
319         if (element.actions == null) {
320             element.actions = [];
321         }
322     },
323 
324     /**
325      * @param {Number} dt delta time in seconds
326      */
327     update:function (dt) {
328         var locTargets = this._targets , locCurrTarget;
329         for (var elt = 0; elt < locTargets.length; elt++) {
330             this._currentTarget = locTargets[elt];
331             locCurrTarget = this._currentTarget;
332             //this._currentTargetSalvaged = false;
333             if (!locCurrTarget.paused) {
334                 // The 'actions' CCMutableArray may change while inside this loop.
335                 for (locCurrTarget.actionIndex = 0; locCurrTarget.actionIndex < locCurrTarget.actions.length;
336                      locCurrTarget.actionIndex++) {
337                     locCurrTarget.currentAction = locCurrTarget.actions[locCurrTarget.actionIndex];
338                     if (!locCurrTarget.currentAction)
339                         continue;
340 
341                     locCurrTarget.currentActionSalvaged = false;
342                     locCurrTarget.currentAction.step(dt);
343                     if (locCurrTarget.currentActionSalvaged) {
344                         // The currentAction told the node to remove it. To prevent the action from
345                         // accidentally deallocating itself before finishing its step, we retained
346                         // it. Now that step is done, it's safe to release it.
347                         locCurrTarget.currentAction = null;//release
348                     } else if (locCurrTarget.currentAction.isDone()) {
349                         locCurrTarget.currentAction.stop();
350                         var action = locCurrTarget.currentAction;
351                         // Make currentAction nil to prevent removeAction from salvaging it.
352                         locCurrTarget.currentAction = null;
353                         this.removeAction(action);
354                     }
355 
356                     locCurrTarget.currentAction = null;
357                 }
358             }
359 
360             // elt, at this moment, is still valid
361             // so it is safe to ask this here (issue #490)
362 
363             // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
364             if (this._currentTargetSalvaged && locCurrTarget.actions.length === 0) {
365                 this._deleteHashElement(locCurrTarget);
366             }
367         }
368     }
369 });
370