1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies 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 cc.g_NumberOfDraws = 0;
 28 
 29 //----------------------------------------------------------------------------------------------------------------------
 30 
 31 /**
 32  * <p>
 33  *    ATTENTION: USE cc.director INSTEAD OF cc.Director.<br/>
 34  *    cc.director is a singleton object which manage your game's logic flow.<br/>
 35  *    Since the cc.director is a singleton, you don't need to call any constructor or create functions,<br/>
 36  *    the standard way to use it is by calling:<br/>
 37  *      - cc.director.methodName(); <br/>
 38  *
 39  *    It creates and handle the main Window and manages how and when to execute the Scenes.<br/>
 40  *    <br/>
 41  *    The cc.director is also responsible for:<br/>
 42  *      - initializing the OpenGL context<br/>
 43  *      - setting the OpenGL pixel format (default on is RGB565)<br/>
 44  *      - setting the OpenGL pixel format (default on is RGB565)<br/>
 45  *      - setting the OpenGL buffer depth (default one is 0-bit)<br/>
 46         - setting the color for clear screen (default one is BLACK)<br/>
 47  *      - setting the projection (default one is 3D)<br/>
 48  *      - setting the orientation (default one is Portrait)<br/>
 49  *      <br/>
 50  *    <br/>
 51  *    The cc.director also sets the default OpenGL context:<br/>
 52  *      - GL_TEXTURE_2D is enabled<br/>
 53  *      - GL_VERTEX_ARRAY is enabled<br/>
 54  *      - GL_COLOR_ARRAY is enabled<br/>
 55  *      - GL_TEXTURE_COORD_ARRAY is enabled<br/>
 56  * </p>
 57  * <p>
 58  *   cc.director also synchronizes timers with the refresh rate of the display.<br/>
 59  *   Features and Limitations:<br/>
 60  *      - Scheduled timers & drawing are synchronizes with the refresh rate of the display<br/>
 61  *      - Only supports animation intervals of 1/60 1/30 & 1/15<br/>
 62  * </p>
 63  * @class
 64  * @name cc.Director
 65  */
 66 cc.Director = cc.Class.extend(/** @lends cc.Director# */{
 67     //Variables
 68     _landscape: false,
 69     _nextDeltaTimeZero: false,
 70     _paused: false,
 71     _purgeDirectorInNextLoop: false,
 72     _sendCleanupToScene: false,
 73     _animationInterval: 0.0,
 74     _oldAnimationInterval: 0.0,
 75     _projection: 0,
 76     _contentScaleFactor: 1.0,
 77 
 78     _deltaTime: 0.0,
 79 
 80     _winSizeInPoints: null,
 81 
 82     _lastUpdate: null,
 83     _nextScene: null,
 84     _notificationNode: null,
 85     _openGLView: null,
 86     _scenesStack: null,
 87     _projectionDelegate: null,
 88     _runningScene: null,
 89 
 90     _totalFrames: 0,
 91     _secondsPerFrame: 0,
 92 
 93     _dirtyRegion: null,
 94 
 95     _scheduler: null,
 96     _actionManager: null,
 97     _eventProjectionChanged: null,
 98     _eventAfterUpdate: null,
 99     _eventAfterVisit: null,
100     _eventAfterDraw: null,
101 
102     ctor: function () {
103         var self = this;
104         self._lastUpdate = Date.now();
105         cc.eventManager.addCustomListener(cc.game.EVENT_SHOW, function () {
106             self._lastUpdate = Date.now();
107         });
108     },
109 
110     init: function () {
111         // scenes
112         this._oldAnimationInterval = this._animationInterval = 1.0 / cc.defaultFPS;
113         this._scenesStack = [];
114         // Set default projection (3D)
115         this._projection = cc.Director.PROJECTION_DEFAULT;
116         // projection delegate if "Custom" projection is used
117         this._projectionDelegate = null;
118 
119         // FPS
120         this._totalFrames = 0;
121         this._lastUpdate = Date.now();
122 
123         //Paused?
124         this._paused = false;
125 
126         //purge?
127         this._purgeDirectorInNextLoop = false;
128 
129         this._winSizeInPoints = cc.size(0, 0);
130 
131         this._openGLView = null;
132         this._contentScaleFactor = 1.0;
133 
134         //scheduler
135         this._scheduler = new cc.Scheduler();
136         //action manager
137         if(cc.ActionManager){
138             this._actionManager = new cc.ActionManager();
139             this._scheduler.scheduleUpdate(this._actionManager, cc.Scheduler.PRIORITY_SYSTEM, false);
140         }else{
141             this._actionManager = null;
142         }
143 
144         this._eventAfterUpdate = new cc.EventCustom(cc.Director.EVENT_AFTER_UPDATE);
145         this._eventAfterUpdate.setUserData(this);
146         this._eventAfterVisit = new cc.EventCustom(cc.Director.EVENT_AFTER_VISIT);
147         this._eventAfterVisit.setUserData(this);
148         this._eventAfterDraw = new cc.EventCustom(cc.Director.EVENT_AFTER_DRAW);
149         this._eventAfterDraw.setUserData(this);
150         this._eventProjectionChanged = new cc.EventCustom(cc.Director.EVENT_PROJECTION_CHANGED);
151         this._eventProjectionChanged.setUserData(this);
152 
153         return true;
154     },
155 
156     /**
157      * calculates delta time since last time it was called
158      */
159     calculateDeltaTime: function () {
160         var now = Date.now();
161 
162         // new delta time.
163         if (this._nextDeltaTimeZero) {
164             this._deltaTime = 0;
165             this._nextDeltaTimeZero = false;
166         } else {
167             this._deltaTime = (now - this._lastUpdate) / 1000;
168         }
169 
170         if ((cc.game.config[cc.game.CONFIG_KEY.debugMode] > 0) && (this._deltaTime > 0.2))
171             this._deltaTime = 1 / 60.0;
172 
173         this._lastUpdate = now;
174     },
175 
176     /**
177      * Converts a view coordinate to an WebGL coordinate<br/>
178      * Useful to convert (multi) touches coordinates to the current layout (portrait or landscape)<br/>
179      * Implementation can be found in CCDirectorWebGL
180      * @function
181      * @param {cc.Point} uiPoint
182      * @return {cc.Point}
183      */
184     convertToGL: function (uiPoint) {
185         var docElem = document.documentElement;
186         var view = cc.view;
187         var box = element.getBoundingClientRect();
188         box.left += window.pageXOffset - docElem.clientLeft;
189         box.top += window.pageYOffset - docElem.clientTop;
190         var x = view._devicePixelRatio * (uiPoint.x - box.left);
191         var y = view._devicePixelRatio * (box.top + box.height - uiPoint.y);
192         return view._isRotated ? {x: view._viewPortRect.width - y, y: x} : {x: x, y: y};
193     },
194 
195     /**
196      * Converts an WebGL coordinate to a view coordinate<br/>
197      * Useful to convert node points to window points for calls such as glScissor<br/>
198      * Implementation can be found in CCDirectorWebGL
199      * @function
200      * @param {cc.Point} glPoint
201      * @return {cc.Point}
202      */
203     convertToUI: function (glPoint) {
204         var docElem = document.documentElement;
205         var view = cc.view;
206         var box = element.getBoundingClientRect();
207         box.left += window.pageXOffset - docElem.clientLeft;
208         box.top += window.pageYOffset - docElem.clientTop;
209         var uiPoint = {x: 0, y: 0};
210         if (view._isRotated) {
211             uiPoint.x = box.left + glPoint.y / view._devicePixelRatio;
212             uiPoint.y = box.top + box.height - (view._viewPortRect.width - glPoint.x) / view._devicePixelRatio;
213         }
214         else {
215             uiPoint.x = box.left + glPoint.x / view._devicePixelRatio;
216             uiPoint.y = box.top + box.height - glPoint.y / view._devicePixelRatio;
217         }
218         return uiPoint;
219     },
220 
221     /**
222      *  Draw the scene. This method is called every frame. Don't call it manually.
223      */
224     drawScene: function () {
225         var renderer = cc.renderer;
226 
227         // calculate "global" dt
228         this.calculateDeltaTime();
229 
230         //tick before glClear: issue #533
231         if (!this._paused) {
232             this._scheduler.update(this._deltaTime);
233             cc.eventManager.dispatchEvent(this._eventAfterUpdate);
234         }
235 
236         /* to avoid flickr, nextScene MUST be here: after tick and before draw.
237          XXX: Which bug is this one. It seems that it can't be reproduced with v0.9 */
238         if (this._nextScene) {
239             this.setNextScene();
240         }
241 
242         if (this._beforeVisitScene)
243             this._beforeVisitScene();
244 
245         // draw the scene
246         if (this._runningScene) {
247             if (renderer.childrenOrderDirty) {
248                 cc.renderer.clearRenderCommands();
249                 cc.renderer.assignedZ = 0;
250                 this._runningScene._renderCmd._curLevel = 0;                          //level start from 0;
251                 this._runningScene.visit();
252                 renderer.resetFlag();
253             } 
254             else if (renderer.transformDirty()) {
255                 renderer.transform();
256             }
257         }
258 
259         renderer.clear();
260 
261         // draw the notifications node
262         if (this._notificationNode)
263             this._notificationNode.visit();
264 
265         cc.eventManager.dispatchEvent(this._eventAfterVisit);
266         cc.g_NumberOfDraws = 0;
267 
268         if (this._afterVisitScene)
269             this._afterVisitScene();
270 
271         renderer.rendering(cc._renderContext);
272         this._totalFrames++;
273 
274         cc.eventManager.dispatchEvent(this._eventAfterDraw);
275 
276         this._calculateMPF();
277     },
278 
279     _beforeVisitScene: null,
280     _afterVisitScene: null,
281 
282     /**
283      * End the life of director in the next frame
284      */
285     end: function () {
286         this._purgeDirectorInNextLoop = true;
287     },
288 
289     /**
290      * Returns the size in pixels of the surface. It could be different than the screen size.<br/>
291      * High-res devices might have a higher surface size than the screen size.
292      * @return {Number}
293      */
294     getContentScaleFactor: function () {
295         return this._contentScaleFactor;
296     },
297 
298     /**
299      * This object will be visited after the main scene is visited.<br/>
300      * This object MUST implement the "visit" selector.<br/>
301      * Useful to hook a notification object
302      * @return {cc.Node}
303      */
304     getNotificationNode: function () {
305         return this._notificationNode;
306     },
307 
308     /**
309      * Returns the size of the WebGL view in points.<br/>
310      * It takes into account any possible rotation (device orientation) of the window
311      * @return {cc.Size}
312      */
313     getWinSize: function () {
314         return cc.size(this._winSizeInPoints);
315     },
316 
317     /**
318      * Returns the size of the OpenGL view in pixels.<br/>
319      * It takes into account any possible rotation (device orientation) of the window.<br/>
320      * On Mac winSize and winSizeInPixels return the same value.
321      * @return {cc.Size}
322      */
323     getWinSizeInPixels: function () {
324         return cc.size(this._winSizeInPoints.width * this._contentScaleFactor, this._winSizeInPoints.height * this._contentScaleFactor);
325     },
326 
327     /**
328      * getVisibleSize/getVisibleOrigin move to CCDirectorWebGL/CCDirectorCanvas
329      * getZEye move to CCDirectorWebGL
330      */
331 
332     /**
333      * Returns the visible size of the running scene
334      * @function
335      * @return {cc.Size}
336      */
337     getVisibleSize: null,
338 
339     /**
340      * Returns the visible origin of the running scene
341      * @function
342      * @return {cc.Point}
343      */
344     getVisibleOrigin: null,
345 
346     /**
347      * Returns the z eye, only available in WebGL mode
348      * @function
349      * @return {Number}
350      */
351     getZEye: null,
352 
353     /**
354      * Pause the director's ticker
355      */
356     pause: function () {
357         if (this._paused)
358             return;
359 
360         this._oldAnimationInterval = this._animationInterval;
361         // when paused, don't consume CPU
362         this.setAnimationInterval(1 / 4.0);
363         this._paused = true;
364     },
365 
366     /**
367      * Pops out a scene from the queue.<br/>
368      * This scene will replace the running one.<br/>
369      * The running scene will be deleted. If there are no more scenes in the stack the execution is terminated.<br/>
370      * ONLY call it if there is a running scene.
371      */
372     popScene: function () {
373 
374         cc.assert(this._runningScene, cc._LogInfos.Director_popScene);
375 
376         this._scenesStack.pop();
377         var c = this._scenesStack.length;
378 
379         if (c === 0)
380             this.end();
381         else {
382             this._sendCleanupToScene = true;
383             this._nextScene = this._scenesStack[c - 1];
384         }
385     },
386 
387     /**
388      * Removes cached all cocos2d cached data. It will purge the cc.textureCache, cc.spriteFrameCache, cc.animationCache
389      */
390     purgeCachedData: function () {
391         cc.animationCache._clear();
392         cc.spriteFrameCache._clear();
393         cc.textureCache._clear();
394     },
395 
396     /**
397      * Purge the cc.director itself, including unschedule all schedule, remove all event listeners, clean up and exit the running scene, stops all animations, clear cached data.
398      */
399     purgeDirector: function () {
400         //cleanup scheduler
401         this.getScheduler().unscheduleAll();
402 
403         // Disable event dispatching
404         if (cc.eventManager)
405             cc.eventManager.setEnabled(false);
406 
407         // don't release the event handlers
408         // They are needed in case the director is run again
409 
410         if (this._runningScene) {
411             this._runningScene.onExitTransitionDidStart();
412             this._runningScene.onExit();
413             this._runningScene.cleanup();
414         }
415 
416         this._runningScene = null;
417         this._nextScene = null;
418 
419         // remove all objects, but don't release it.
420         // runScene might be executed after 'end'.
421         this._scenesStack.length = 0;
422 
423         this.stopAnimation();
424 
425         // Clear all caches
426         this.purgeCachedData();
427 
428         cc.checkGLErrorDebug();
429     },
430 
431     /**
432      * Suspends the execution of the running scene, pushing it on the stack of suspended scenes.<br/>
433      * The new scene will be executed.<br/>
434      * Try to avoid big stacks of pushed scenes to reduce memory allocation.<br/>
435      * ONLY call it if there is a running scene.
436      * @param {cc.Scene} scene
437      */
438     pushScene: function (scene) {
439 
440         cc.assert(scene, cc._LogInfos.Director_pushScene);
441 
442         this._sendCleanupToScene = false;
443 
444         this._scenesStack.push(scene);
445         this._nextScene = scene;
446     },
447 
448     /**
449      * Run a scene. Replaces the running scene with a new one or enter the first scene.
450      * @param {cc.Scene} scene
451      */
452     runScene: function (scene) {
453 
454         cc.assert(scene, cc._LogInfos.Director_pushScene);
455 
456         if (!this._runningScene) {
457             //start scene
458             this.pushScene(scene);
459             this.startAnimation();
460         } else {
461             //replace scene
462             var i = this._scenesStack.length;
463             if (i === 0) {
464                 this._sendCleanupToScene = true;
465                 this._scenesStack[i] = scene;
466                 this._nextScene = scene;
467             } else {
468                 this._sendCleanupToScene = true;
469                 this._scenesStack[i - 1] = scene;
470                 this._nextScene = scene;
471             }
472         }
473     },
474 
475     /**
476      * Resume director after pause, if the current scene is not paused, nothing will happen.
477      */
478     resume: function () {
479         if (!this._paused) {
480             return;
481         }
482 
483         this.setAnimationInterval(this._oldAnimationInterval);
484         this._lastUpdate = Date.now();
485         if (!this._lastUpdate) {
486             cc.log(cc._LogInfos.Director_resume);
487         }
488 
489         this._paused = false;
490         this._deltaTime = 0;
491     },
492 
493     /**
494      * The size in pixels of the surface. It could be different than the screen size.<br/>
495      * High-res devices might have a higher surface size than the screen size.
496      * @param {Number} scaleFactor
497      */
498     setContentScaleFactor: function (scaleFactor) {
499         if (scaleFactor !== this._contentScaleFactor) {
500             this._contentScaleFactor = scaleFactor;
501         }
502     },
503 
504     /**
505      * Enables or disables WebGL depth test.<br/>
506      * Implementation can be found in CCDirectorCanvas.js/CCDirectorWebGL.js
507      * @function
508      * @param {Boolean} on
509      */
510     setDepthTest: null,
511 
512     /**
513      * set color for clear screen.<br/>
514      * Implementation can be found in CCDirectorCanvas.js/CCDirectorWebGL.js
515      * @function
516      * @param {cc.Color} clearColor
517      */
518     setClearColor: null,
519     /**
520      * Sets the default values based on the CCConfiguration info
521      */
522     setDefaultValues: function () {
523 
524     },
525 
526     /**
527      * Sets whether next delta time equals to zero
528      * @param {Boolean} nextDeltaTimeZero
529      */
530     setNextDeltaTimeZero: function (nextDeltaTimeZero) {
531         this._nextDeltaTimeZero = nextDeltaTimeZero;
532     },
533 
534     /**
535      * Starts the registered next scene
536      */
537     setNextScene: function () {
538         var runningIsTransition = false, newIsTransition = false;
539         if (cc.TransitionScene) {
540             runningIsTransition = this._runningScene ? this._runningScene instanceof cc.TransitionScene : false;
541             newIsTransition = this._nextScene ? this._nextScene instanceof cc.TransitionScene : false;
542         }
543 
544         // If it is not a transition, call onExit/cleanup
545         if (!newIsTransition) {
546             var locRunningScene = this._runningScene;
547             if (locRunningScene) {
548                 locRunningScene.onExitTransitionDidStart();
549                 locRunningScene.onExit();
550             }
551 
552             // issue #709. the root node (scene) should receive the cleanup message too
553             // otherwise it might be leaked.
554             if (this._sendCleanupToScene && locRunningScene)
555                 locRunningScene.cleanup();
556         }
557 
558         this._runningScene = this._nextScene;
559         cc.renderer.childrenOrderDirty = true;
560 
561         this._nextScene = null;
562         if ((!runningIsTransition) && (this._runningScene !== null)) {
563             this._runningScene.onEnter();
564             this._runningScene.onEnterTransitionDidFinish();
565         }
566     },
567 
568     /**
569      * Sets Notification Node
570      * @param {cc.Node} node
571      */
572     setNotificationNode: function (node) {
573         cc.renderer.childrenOrderDirty = true;
574         if(this._notificationNode){
575             this._notificationNode.onExitTransitionDidStart();
576             this._notificationNode.onExit();
577             this._notificationNode.cleanup();
578         }
579         this._notificationNode = node;
580         if(!node)
581             return;
582         this._notificationNode.onEnter();
583         this._notificationNode.onEnterTransitionDidFinish();
584     },
585 
586     /**
587      * Returns the cc.director delegate.
588      * @return {cc.DirectorDelegate}
589      */
590     getDelegate: function () {
591         return this._projectionDelegate;
592     },
593 
594     /**
595      * Sets the cc.director delegate. It shall implement the CCDirectorDelegate protocol
596      * @return {cc.DirectorDelegate}
597      */
598     setDelegate: function (delegate) {
599         this._projectionDelegate = delegate;
600     },
601 
602     /**
603      * Sets the view, where everything is rendered, do not call this function.<br/>
604      * Implementation can be found in CCDirectorCanvas.js/CCDirectorWebGL.js.
605      * @function
606      * @param {cc.view} openGLView
607      */
608     setOpenGLView: null,
609 
610     /**
611      * Sets an OpenGL projection.<br/>
612      * Implementation can be found in CCDirectorCanvas.js/CCDirectorWebGL.js.
613      * @function
614      * @param {Number} projection
615      */
616     setProjection: null,
617 
618     /**
619      * Update the view port.<br/>
620      * Implementation can be found in CCDirectorCanvas.js/CCDirectorWebGL.js.
621      * @function
622      */
623     setViewport: null,
624 
625     /**
626      * Get the CCEGLView, where everything is rendered.<br/>
627      * Implementation can be found in CCDirectorCanvas.js/CCDirectorWebGL.js.
628      * @function
629      * @return {cc.view}
630      */
631     getOpenGLView: null,
632 
633     /**
634      * Sets an OpenGL projection.<br/>
635      * Implementation can be found in CCDirectorCanvas.js/CCDirectorWebGL.js.
636      * @function
637      * @return {Number}
638      */
639     getProjection: null,
640 
641     /**
642      * Enables/disables OpenGL alpha blending.<br/>
643      * Implementation can be found in CCDirectorCanvas.js/CCDirectorWebGL.js.
644      * @function
645      * @param {Boolean} on
646      */
647     setAlphaBlending: null,
648 
649     /**
650      * Returns whether or not the replaced scene will receive the cleanup message.<br>
651      * If the new scene is pushed, then the old scene won't receive the "cleanup" message.<br/>
652      * If the new scene replaces the old one, the it will receive the "cleanup" message.
653      * @return {Boolean}
654      */
655     isSendCleanupToScene: function () {
656         return this._sendCleanupToScene;
657     },
658 
659     /**
660      * Returns current running Scene. Director can only run one Scene at the time
661      * @return {cc.Scene}
662      */
663     getRunningScene: function () {
664         return this._runningScene;
665     },
666 
667     /**
668      * Returns the FPS value
669      * @return {Number}
670      */
671     getAnimationInterval: function () {
672         return this._animationInterval;
673     },
674 
675     /**
676      * Returns whether or not to display the FPS informations
677      * @return {Boolean}
678      */
679     isDisplayStats: function () {
680         return cc.profiler ? cc.profiler.isShowingStats() : false;
681     },
682 
683     /**
684      * Sets whether display the FPS on the bottom-left corner
685      * @param {Boolean} displayStats
686      */
687     setDisplayStats: function (displayStats) {
688         if (cc.profiler) {
689             displayStats ? cc.profiler.showStats() : cc.profiler.hideStats();
690         }
691     },
692 
693     /**
694      * Returns seconds per frame
695      * @return {Number}
696      */
697     getSecondsPerFrame: function () {
698         return this._secondsPerFrame;
699     },
700 
701     /**
702      * Returns whether next delta time equals to zero
703      * @return {Boolean}
704      */
705     isNextDeltaTimeZero: function () {
706         return this._nextDeltaTimeZero;
707     },
708 
709     /**
710      * Returns whether or not the Director is paused
711      * @return {Boolean}
712      */
713     isPaused: function () {
714         return this._paused;
715     },
716 
717     /**
718      * Returns how many frames were called since the director started
719      * @return {Number}
720      */
721     getTotalFrames: function () {
722         return this._totalFrames;
723     },
724 
725     /**
726      * Pops out all scenes from the queue until the root scene in the queue. <br/>
727      * This scene will replace the running one.  <br/>
728      * Internally it will call "popToSceneStackLevel(1)"
729      */
730     popToRootScene: function () {
731         this.popToSceneStackLevel(1);
732     },
733 
734     /**
735      * Pops out all scenes from the queue until it reaches "level".                             <br/>
736      * If level is 0, it will end the director.                                                 <br/>
737      * If level is 1, it will pop all scenes until it reaches to root scene.                    <br/>
738      * If level is <= than the current stack level, it won't do anything.
739      * @param {Number} level
740      */
741     popToSceneStackLevel: function (level) {
742         cc.assert(this._runningScene, cc._LogInfos.Director_popToSceneStackLevel_2);
743 
744         var locScenesStack = this._scenesStack;
745         var c = locScenesStack.length;
746 
747         if (level === 0) {
748             this.end();
749             return;
750         }
751         // stack overflow
752         if (level >= c)
753             return;
754 
755         // pop stack until reaching desired level
756         while (c > level) {
757             var current = locScenesStack.pop();
758             if (current.running) {
759                 current.onExitTransitionDidStart();
760                 current.onExit();
761             }
762             current.cleanup();
763             c--;
764         }
765         this._nextScene = locScenesStack[locScenesStack.length - 1];
766         this._sendCleanupToScene = true;
767     },
768 
769     /**
770      * Returns the cc.Scheduler associated with this director
771      * @return {cc.Scheduler}
772      */
773     getScheduler: function () {
774         return this._scheduler;
775     },
776 
777     /**
778      * Sets the cc.Scheduler associated with this director
779      * @param {cc.Scheduler} scheduler
780      */
781     setScheduler: function (scheduler) {
782         if (this._scheduler !== scheduler) {
783             this._scheduler = scheduler;
784         }
785     },
786 
787     /**
788      * Returns the cc.ActionManager associated with this director
789      * @return {cc.ActionManager}
790      */
791     getActionManager: function () {
792         return this._actionManager;
793     },
794     /**
795      * Sets the cc.ActionManager associated with this director
796      * @param {cc.ActionManager} actionManager
797      */
798     setActionManager: function (actionManager) {
799         if (this._actionManager !== actionManager) {
800             this._actionManager = actionManager;
801         }
802     },
803 
804     /**
805      * Returns the delta time since last frame
806      * @return {Number}
807      */
808     getDeltaTime: function () {
809         return this._deltaTime;
810     },
811 
812     _calculateMPF: function () {
813         var now = Date.now();
814         this._secondsPerFrame = (now - this._lastUpdate) / 1000;
815     }
816 });
817 
818 /**
819  * The event projection changed of cc.Director
820  * @constant
821  * @type {string}
822  * @example
823  *   cc.eventManager.addCustomListener(cc.Director.EVENT_PROJECTION_CHANGED, function(event) {
824  *           cc.log("Projection changed.");
825  *       });
826  */
827 cc.Director.EVENT_PROJECTION_CHANGED = "director_projection_changed";
828 
829 /**
830  * The event after update of cc.Director
831  * @constant
832  * @type {string}
833  * @example
834  *   cc.eventManager.addCustomListener(cc.Director.EVENT_AFTER_UPDATE, function(event) {
835  *           cc.log("after update event.");
836  *       });
837  */
838 cc.Director.EVENT_AFTER_UPDATE = "director_after_update";
839 
840 /**
841  * The event after visit of cc.Director
842  * @constant
843  * @type {string}
844  * @example
845  *   cc.eventManager.addCustomListener(cc.Director.EVENT_AFTER_VISIT, function(event) {
846  *           cc.log("after visit event.");
847  *       });
848  */
849 cc.Director.EVENT_AFTER_VISIT = "director_after_visit";
850 
851 /**
852  * The event after draw of cc.Director
853  * @constant
854  * @type {string}
855  * @example
856  *   cc.eventManager.addCustomListener(cc.Director.EVENT_AFTER_DRAW, function(event) {
857  *           cc.log("after draw event.");
858  *       });
859  */
860 cc.Director.EVENT_AFTER_DRAW = "director_after_draw";
861 
862 /***************************************************
863  * implementation of DisplayLinkDirector
864  **************************************************/
865 cc.DisplayLinkDirector = cc.Director.extend(/** @lends cc.Director# */{
866     invalid: false,
867 
868     /**
869      * Starts Animation
870      */
871     startAnimation: function () {
872         this._nextDeltaTimeZero = true;
873         this.invalid = false;
874     },
875 
876     /**
877      * Run main loop of director
878      */
879     mainLoop: function () {
880         if (this._purgeDirectorInNextLoop) {
881             this._purgeDirectorInNextLoop = false;
882             this.purgeDirector();
883         }
884         else if (!this.invalid) {
885             this.drawScene();
886         }
887     },
888 
889     /**
890      * Stops animation
891      */
892     stopAnimation: function () {
893         this.invalid = true;
894     },
895 
896     /**
897      * Sets animation interval
898      * @param {Number} value the animation interval desired
899      */
900     setAnimationInterval: function (value) {
901         this._animationInterval = value;
902         if (!this.invalid) {
903             this.stopAnimation();
904             this.startAnimation();
905         }
906     }
907 });
908 
909 cc.Director.sharedDirector = null;
910 cc.Director.firstUseDirector = true;
911 
912 cc.Director._getInstance = function () {
913     if (cc.Director.firstUseDirector) {
914         cc.Director.firstUseDirector = false;
915         cc.Director.sharedDirector = new cc.DisplayLinkDirector();
916         cc.Director.sharedDirector.init();
917     }
918     return cc.Director.sharedDirector;
919 };
920 
921 /**
922  * Default fps is 60
923  * @type {Number}
924  */
925 cc.defaultFPS = 60;
926 
927 //Possible OpenGL projections used by director
928 /**
929  * Constant for 2D projection (orthogonal projection)
930  * @constant
931  * @type {Number}
932  */
933 cc.Director.PROJECTION_2D = 0;
934 
935 /**
936  * Constant for 3D projection with a fovy=60, znear=0.5f and zfar=1500.
937  * @constant
938  * @type {Number}
939  */
940 cc.Director.PROJECTION_3D = 1;
941 
942 /**
943  * Constant for custom projection, if cc.Director's projection set to it, it calls "updateProjection" on the projection delegate.
944  * @constant
945  * @type {Number}
946  */
947 cc.Director.PROJECTION_CUSTOM = 3;
948 
949 /**
950  * Constant for default projection of cc.Director, default projection is 3D projection
951  * @constant
952  * @type {Number}
953  */
954 cc.Director.PROJECTION_DEFAULT = cc.Director.PROJECTION_2D;
955