diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/src/core/const.js b/src/core/const.js index fe5bd07..773a4d8 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -35,6 +35,15 @@ DEG_TO_RAD: Math.PI / 180, /** + * Target frames per millisecond. + * + * @static + * @constant + * @property {number} TARGET_FPMS=0.06 + */ + TARGET_FPMS: 0.06, + + /** * Constant to identify the Renderer Type. * * @static @@ -178,5 +187,7 @@ RREC: 4 }, + // TODO: maybe change to SPRITE.BATCH_SIZE: 2000 + // TODO: maybe add PARTICLE.BATCH_SIZE: 15000 SPRITE_BATCH_SIZE: 2000 //nice balance between mobile and desktop machines }; diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/src/core/const.js b/src/core/const.js index fe5bd07..773a4d8 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -35,6 +35,15 @@ DEG_TO_RAD: Math.PI / 180, /** + * Target frames per millisecond. + * + * @static + * @constant + * @property {number} TARGET_FPMS=0.06 + */ + TARGET_FPMS: 0.06, + + /** * Constant to identify the Renderer Type. * * @static @@ -178,5 +187,7 @@ RREC: 4 }, + // TODO: maybe change to SPRITE.BATCH_SIZE: 2000 + // TODO: maybe add PARTICLE.BATCH_SIZE: 15000 SPRITE_BATCH_SIZE: 2000 //nice balance between mobile and desktop machines }; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 6a34990..b604386 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,5 @@ var core = require('../core'), - Ticker = require('./Ticker'); + ticker = require('../ticker'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -129,7 +129,7 @@ } this.playing = false; - Ticker.sharedTicker.remove(this.update); + ticker.shared.remove(this.update); }; /** @@ -144,7 +144,7 @@ } this.playing = true; - Ticker.sharedTicker.add(this.update, this); + ticker.shared.add(this.update, this); }; /** diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/src/core/const.js b/src/core/const.js index fe5bd07..773a4d8 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -35,6 +35,15 @@ DEG_TO_RAD: Math.PI / 180, /** + * Target frames per millisecond. + * + * @static + * @constant + * @property {number} TARGET_FPMS=0.06 + */ + TARGET_FPMS: 0.06, + + /** * Constant to identify the Renderer Type. * * @static @@ -178,5 +187,7 @@ RREC: 4 }, + // TODO: maybe change to SPRITE.BATCH_SIZE: 2000 + // TODO: maybe add PARTICLE.BATCH_SIZE: 15000 SPRITE_BATCH_SIZE: 2000 //nice balance between mobile and desktop machines }; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 6a34990..b604386 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,5 @@ var core = require('../core'), - Ticker = require('./Ticker'); + ticker = require('../ticker'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -129,7 +129,7 @@ } this.playing = false; - Ticker.sharedTicker.remove(this.update); + ticker.shared.remove(this.update); }; /** @@ -144,7 +144,7 @@ } this.playing = true; - Ticker.sharedTicker.add(this.update, this); + ticker.shared.add(this.update, this); }; /** diff --git a/src/extras/Ticker.js b/src/extras/Ticker.js deleted file mode 100644 index 973d5a6..0000000 --- a/src/extras/Ticker.js +++ /dev/null @@ -1,285 +0,0 @@ -var EventEmitter = require('eventemitter3').EventEmitter, - performance = global.performance, - TICK = 'tick'; - -/** - * Yes, this is accessing an internal module:eventemitter3 api. - * Ugly, but calling module:eventemitter3.EventEmitter#listeners - * does a bit too much for what this is for. - * This is simple enough to keep track of and contribute - * back to the eventemitter3 project in the near future. - * - * @private - */ -function hasListeners(emitter) -{ - return !!(emitter._events && emitter._events[TICK]); -} - -/** - * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners - * meant for execution on the next requested animation frame. - * Animation frames are requested only when necessary, - * e.g. When the ticker is started and the emitter has listeners. - * - * @class - * @memberof PIXI.extras - */ -function Ticker() -{ - var _this = this; - /** - * Internal tick method bound to ticker instance. - * This is because in early 2015, Function.bind - * is still 60% slower in high performance scenarios. - * - * @private - */ - this._tick = function _tick(time) { - _this.update(time); - }; - /** - * Internal emitter - * @private - */ - this._emitter = new EventEmitter(); - /** - * Internal frame request reference - * @private - */ - this._rafId = null; - - /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.extras.Ticker.start} has been called. - * `false` if {@link PIXI.extras.Ticker.stop} has been called. - * - * @member {boolean} - */ - this.started = false; - - /** - * Whether or not this ticker should - * start automatically when a listener is added. - * - * @member {boolean} - */ - this.autoStart = false; - - /** - * The deltaTime - * @todo better description - * - * @member {number} - */ - this.deltaTime = 1; - - /** - * The time at the last frame - * @todo better description - * - * @member {number} - */ - this.lastTime = 0; - - /** - * The speed - * @todo better description - * - * @member {number} - */ - this.speed = 1; - - /** - * The maximum time between 2 frames - * @todo better description - * - * @member {number} - */ - this.maxTimeElapsed = 100; -} - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - if (this._rafId === null && hasListeners(this._emitter)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._rafId = requestAnimationFrame(this._tick); - } - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._rafId !== null) - { - cancelAnimationFrame(this._rafId); - this._rafId = null; - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.remove = function remove(fn, once) -{ - // remove listener(s) from internal emitter - this._emitter.off(TICK, fn, once); - - // If there are no listeners, cancel the request. - if (!hasListeners(this._emitter)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._startIfPossible(); - } - - return this; -}; - -/** - * Stops the ticker. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Triggers an update, setting deltaTime, lastTime, and - * firing the internal 'tick' event. After this, if the - * ticker is still started and has listeners, - * another frame is requested. - */ -Ticker.prototype.update = function update(currentTime) -{ - var timeElapsed; - - this._rafId = null; - - if (this.started) - { - // Allow calling tick directly getting correct currentTime - currentTime = currentTime || performance.now(); - timeElapsed = currentTime - this.lastTime; - - // cap the time! - // TODO: Is this there a better way to do this? - if (timeElapsed > this.maxTimeElapsed) - { - timeElapsed = this.maxTimeElapsed; - } - - // TODO: Would love to know what to name this magic number 0.6 - this.deltaTime = (timeElapsed * 0.06); - this.deltaTime *= this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - - this.lastTime = currentTime; - } - - // Check again here because listeners could have side effects - // and may have modified state during frame execution. - // A new frame may have been requested or listeners removed. - if (this.started && this._rafId === null && hasListeners(this._emitter)) - { - this._rafId = requestAnimationFrame(this._tick); - } -}; - -/** - * The shared ticker instance used by {@link PIXI.extras.MovieClip}. - * The property {@link PIXI.extras.Ticker#autoStart} is set to true - * for this instance. - * - * @type {PIXI.extras.Ticker} - * @memberof PIXI.extras.Ticker - */ -Ticker.sharedTicker = new Ticker(); -Ticker.sharedTicker.autoStart = true; - -module.exports = Ticker; diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/src/core/const.js b/src/core/const.js index fe5bd07..773a4d8 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -35,6 +35,15 @@ DEG_TO_RAD: Math.PI / 180, /** + * Target frames per millisecond. + * + * @static + * @constant + * @property {number} TARGET_FPMS=0.06 + */ + TARGET_FPMS: 0.06, + + /** * Constant to identify the Renderer Type. * * @static @@ -178,5 +187,7 @@ RREC: 4 }, + // TODO: maybe change to SPRITE.BATCH_SIZE: 2000 + // TODO: maybe add PARTICLE.BATCH_SIZE: 15000 SPRITE_BATCH_SIZE: 2000 //nice balance between mobile and desktop machines }; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 6a34990..b604386 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,5 @@ var core = require('../core'), - Ticker = require('./Ticker'); + ticker = require('../ticker'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -129,7 +129,7 @@ } this.playing = false; - Ticker.sharedTicker.remove(this.update); + ticker.shared.remove(this.update); }; /** @@ -144,7 +144,7 @@ } this.playing = true; - Ticker.sharedTicker.add(this.update, this); + ticker.shared.add(this.update, this); }; /** diff --git a/src/extras/Ticker.js b/src/extras/Ticker.js deleted file mode 100644 index 973d5a6..0000000 --- a/src/extras/Ticker.js +++ /dev/null @@ -1,285 +0,0 @@ -var EventEmitter = require('eventemitter3').EventEmitter, - performance = global.performance, - TICK = 'tick'; - -/** - * Yes, this is accessing an internal module:eventemitter3 api. - * Ugly, but calling module:eventemitter3.EventEmitter#listeners - * does a bit too much for what this is for. - * This is simple enough to keep track of and contribute - * back to the eventemitter3 project in the near future. - * - * @private - */ -function hasListeners(emitter) -{ - return !!(emitter._events && emitter._events[TICK]); -} - -/** - * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners - * meant for execution on the next requested animation frame. - * Animation frames are requested only when necessary, - * e.g. When the ticker is started and the emitter has listeners. - * - * @class - * @memberof PIXI.extras - */ -function Ticker() -{ - var _this = this; - /** - * Internal tick method bound to ticker instance. - * This is because in early 2015, Function.bind - * is still 60% slower in high performance scenarios. - * - * @private - */ - this._tick = function _tick(time) { - _this.update(time); - }; - /** - * Internal emitter - * @private - */ - this._emitter = new EventEmitter(); - /** - * Internal frame request reference - * @private - */ - this._rafId = null; - - /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.extras.Ticker.start} has been called. - * `false` if {@link PIXI.extras.Ticker.stop} has been called. - * - * @member {boolean} - */ - this.started = false; - - /** - * Whether or not this ticker should - * start automatically when a listener is added. - * - * @member {boolean} - */ - this.autoStart = false; - - /** - * The deltaTime - * @todo better description - * - * @member {number} - */ - this.deltaTime = 1; - - /** - * The time at the last frame - * @todo better description - * - * @member {number} - */ - this.lastTime = 0; - - /** - * The speed - * @todo better description - * - * @member {number} - */ - this.speed = 1; - - /** - * The maximum time between 2 frames - * @todo better description - * - * @member {number} - */ - this.maxTimeElapsed = 100; -} - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - if (this._rafId === null && hasListeners(this._emitter)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._rafId = requestAnimationFrame(this._tick); - } - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._rafId !== null) - { - cancelAnimationFrame(this._rafId); - this._rafId = null; - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.remove = function remove(fn, once) -{ - // remove listener(s) from internal emitter - this._emitter.off(TICK, fn, once); - - // If there are no listeners, cancel the request. - if (!hasListeners(this._emitter)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._startIfPossible(); - } - - return this; -}; - -/** - * Stops the ticker. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Triggers an update, setting deltaTime, lastTime, and - * firing the internal 'tick' event. After this, if the - * ticker is still started and has listeners, - * another frame is requested. - */ -Ticker.prototype.update = function update(currentTime) -{ - var timeElapsed; - - this._rafId = null; - - if (this.started) - { - // Allow calling tick directly getting correct currentTime - currentTime = currentTime || performance.now(); - timeElapsed = currentTime - this.lastTime; - - // cap the time! - // TODO: Is this there a better way to do this? - if (timeElapsed > this.maxTimeElapsed) - { - timeElapsed = this.maxTimeElapsed; - } - - // TODO: Would love to know what to name this magic number 0.6 - this.deltaTime = (timeElapsed * 0.06); - this.deltaTime *= this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - - this.lastTime = currentTime; - } - - // Check again here because listeners could have side effects - // and may have modified state during frame execution. - // A new frame may have been requested or listeners removed. - if (this.started && this._rafId === null && hasListeners(this._emitter)) - { - this._rafId = requestAnimationFrame(this._tick); - } -}; - -/** - * The shared ticker instance used by {@link PIXI.extras.MovieClip}. - * The property {@link PIXI.extras.Ticker#autoStart} is set to true - * for this instance. - * - * @type {PIXI.extras.Ticker} - * @memberof PIXI.extras.Ticker - */ -Ticker.sharedTicker = new Ticker(); -Ticker.sharedTicker.autoStart = true; - -module.exports = Ticker; diff --git a/src/extras/index.js b/src/extras/index.js index aa08e38..1f85635 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -13,7 +13,6 @@ * @namespace PIXI.extras */ module.exports = { - Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), BitmapText: require('./BitmapText'), diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/src/core/const.js b/src/core/const.js index fe5bd07..773a4d8 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -35,6 +35,15 @@ DEG_TO_RAD: Math.PI / 180, /** + * Target frames per millisecond. + * + * @static + * @constant + * @property {number} TARGET_FPMS=0.06 + */ + TARGET_FPMS: 0.06, + + /** * Constant to identify the Renderer Type. * * @static @@ -178,5 +187,7 @@ RREC: 4 }, + // TODO: maybe change to SPRITE.BATCH_SIZE: 2000 + // TODO: maybe add PARTICLE.BATCH_SIZE: 15000 SPRITE_BATCH_SIZE: 2000 //nice balance between mobile and desktop machines }; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 6a34990..b604386 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,5 @@ var core = require('../core'), - Ticker = require('./Ticker'); + ticker = require('../ticker'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -129,7 +129,7 @@ } this.playing = false; - Ticker.sharedTicker.remove(this.update); + ticker.shared.remove(this.update); }; /** @@ -144,7 +144,7 @@ } this.playing = true; - Ticker.sharedTicker.add(this.update, this); + ticker.shared.add(this.update, this); }; /** diff --git a/src/extras/Ticker.js b/src/extras/Ticker.js deleted file mode 100644 index 973d5a6..0000000 --- a/src/extras/Ticker.js +++ /dev/null @@ -1,285 +0,0 @@ -var EventEmitter = require('eventemitter3').EventEmitter, - performance = global.performance, - TICK = 'tick'; - -/** - * Yes, this is accessing an internal module:eventemitter3 api. - * Ugly, but calling module:eventemitter3.EventEmitter#listeners - * does a bit too much for what this is for. - * This is simple enough to keep track of and contribute - * back to the eventemitter3 project in the near future. - * - * @private - */ -function hasListeners(emitter) -{ - return !!(emitter._events && emitter._events[TICK]); -} - -/** - * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners - * meant for execution on the next requested animation frame. - * Animation frames are requested only when necessary, - * e.g. When the ticker is started and the emitter has listeners. - * - * @class - * @memberof PIXI.extras - */ -function Ticker() -{ - var _this = this; - /** - * Internal tick method bound to ticker instance. - * This is because in early 2015, Function.bind - * is still 60% slower in high performance scenarios. - * - * @private - */ - this._tick = function _tick(time) { - _this.update(time); - }; - /** - * Internal emitter - * @private - */ - this._emitter = new EventEmitter(); - /** - * Internal frame request reference - * @private - */ - this._rafId = null; - - /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.extras.Ticker.start} has been called. - * `false` if {@link PIXI.extras.Ticker.stop} has been called. - * - * @member {boolean} - */ - this.started = false; - - /** - * Whether or not this ticker should - * start automatically when a listener is added. - * - * @member {boolean} - */ - this.autoStart = false; - - /** - * The deltaTime - * @todo better description - * - * @member {number} - */ - this.deltaTime = 1; - - /** - * The time at the last frame - * @todo better description - * - * @member {number} - */ - this.lastTime = 0; - - /** - * The speed - * @todo better description - * - * @member {number} - */ - this.speed = 1; - - /** - * The maximum time between 2 frames - * @todo better description - * - * @member {number} - */ - this.maxTimeElapsed = 100; -} - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - if (this._rafId === null && hasListeners(this._emitter)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._rafId = requestAnimationFrame(this._tick); - } - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._rafId !== null) - { - cancelAnimationFrame(this._rafId); - this._rafId = null; - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.remove = function remove(fn, once) -{ - // remove listener(s) from internal emitter - this._emitter.off(TICK, fn, once); - - // If there are no listeners, cancel the request. - if (!hasListeners(this._emitter)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._startIfPossible(); - } - - return this; -}; - -/** - * Stops the ticker. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Triggers an update, setting deltaTime, lastTime, and - * firing the internal 'tick' event. After this, if the - * ticker is still started and has listeners, - * another frame is requested. - */ -Ticker.prototype.update = function update(currentTime) -{ - var timeElapsed; - - this._rafId = null; - - if (this.started) - { - // Allow calling tick directly getting correct currentTime - currentTime = currentTime || performance.now(); - timeElapsed = currentTime - this.lastTime; - - // cap the time! - // TODO: Is this there a better way to do this? - if (timeElapsed > this.maxTimeElapsed) - { - timeElapsed = this.maxTimeElapsed; - } - - // TODO: Would love to know what to name this magic number 0.6 - this.deltaTime = (timeElapsed * 0.06); - this.deltaTime *= this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - - this.lastTime = currentTime; - } - - // Check again here because listeners could have side effects - // and may have modified state during frame execution. - // A new frame may have been requested or listeners removed. - if (this.started && this._rafId === null && hasListeners(this._emitter)) - { - this._rafId = requestAnimationFrame(this._tick); - } -}; - -/** - * The shared ticker instance used by {@link PIXI.extras.MovieClip}. - * The property {@link PIXI.extras.Ticker#autoStart} is set to true - * for this instance. - * - * @type {PIXI.extras.Ticker} - * @memberof PIXI.extras.Ticker - */ -Ticker.sharedTicker = new Ticker(); -Ticker.sharedTicker.autoStart = true; - -module.exports = Ticker; diff --git a/src/extras/index.js b/src/extras/index.js index aa08e38..1f85635 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -13,7 +13,6 @@ * @namespace PIXI.extras */ module.exports = { - Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), BitmapText: require('./BitmapText'), diff --git a/src/index.js b/src/index.js index a149f72..a6e9ca7 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ core.loaders = require('./loaders'); core.mesh = require('./mesh'); core.spine = require('pixi-spine'); +core.ticker = require('./ticker'); // export a premade loader instance core.loader = new core.loaders.Loader(); diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/src/core/const.js b/src/core/const.js index fe5bd07..773a4d8 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -35,6 +35,15 @@ DEG_TO_RAD: Math.PI / 180, /** + * Target frames per millisecond. + * + * @static + * @constant + * @property {number} TARGET_FPMS=0.06 + */ + TARGET_FPMS: 0.06, + + /** * Constant to identify the Renderer Type. * * @static @@ -178,5 +187,7 @@ RREC: 4 }, + // TODO: maybe change to SPRITE.BATCH_SIZE: 2000 + // TODO: maybe add PARTICLE.BATCH_SIZE: 15000 SPRITE_BATCH_SIZE: 2000 //nice balance between mobile and desktop machines }; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 6a34990..b604386 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,5 @@ var core = require('../core'), - Ticker = require('./Ticker'); + ticker = require('../ticker'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -129,7 +129,7 @@ } this.playing = false; - Ticker.sharedTicker.remove(this.update); + ticker.shared.remove(this.update); }; /** @@ -144,7 +144,7 @@ } this.playing = true; - Ticker.sharedTicker.add(this.update, this); + ticker.shared.add(this.update, this); }; /** diff --git a/src/extras/Ticker.js b/src/extras/Ticker.js deleted file mode 100644 index 973d5a6..0000000 --- a/src/extras/Ticker.js +++ /dev/null @@ -1,285 +0,0 @@ -var EventEmitter = require('eventemitter3').EventEmitter, - performance = global.performance, - TICK = 'tick'; - -/** - * Yes, this is accessing an internal module:eventemitter3 api. - * Ugly, but calling module:eventemitter3.EventEmitter#listeners - * does a bit too much for what this is for. - * This is simple enough to keep track of and contribute - * back to the eventemitter3 project in the near future. - * - * @private - */ -function hasListeners(emitter) -{ - return !!(emitter._events && emitter._events[TICK]); -} - -/** - * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners - * meant for execution on the next requested animation frame. - * Animation frames are requested only when necessary, - * e.g. When the ticker is started and the emitter has listeners. - * - * @class - * @memberof PIXI.extras - */ -function Ticker() -{ - var _this = this; - /** - * Internal tick method bound to ticker instance. - * This is because in early 2015, Function.bind - * is still 60% slower in high performance scenarios. - * - * @private - */ - this._tick = function _tick(time) { - _this.update(time); - }; - /** - * Internal emitter - * @private - */ - this._emitter = new EventEmitter(); - /** - * Internal frame request reference - * @private - */ - this._rafId = null; - - /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.extras.Ticker.start} has been called. - * `false` if {@link PIXI.extras.Ticker.stop} has been called. - * - * @member {boolean} - */ - this.started = false; - - /** - * Whether or not this ticker should - * start automatically when a listener is added. - * - * @member {boolean} - */ - this.autoStart = false; - - /** - * The deltaTime - * @todo better description - * - * @member {number} - */ - this.deltaTime = 1; - - /** - * The time at the last frame - * @todo better description - * - * @member {number} - */ - this.lastTime = 0; - - /** - * The speed - * @todo better description - * - * @member {number} - */ - this.speed = 1; - - /** - * The maximum time between 2 frames - * @todo better description - * - * @member {number} - */ - this.maxTimeElapsed = 100; -} - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - if (this._rafId === null && hasListeners(this._emitter)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._rafId = requestAnimationFrame(this._tick); - } - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._rafId !== null) - { - cancelAnimationFrame(this._rafId); - this._rafId = null; - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.remove = function remove(fn, once) -{ - // remove listener(s) from internal emitter - this._emitter.off(TICK, fn, once); - - // If there are no listeners, cancel the request. - if (!hasListeners(this._emitter)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._startIfPossible(); - } - - return this; -}; - -/** - * Stops the ticker. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Triggers an update, setting deltaTime, lastTime, and - * firing the internal 'tick' event. After this, if the - * ticker is still started and has listeners, - * another frame is requested. - */ -Ticker.prototype.update = function update(currentTime) -{ - var timeElapsed; - - this._rafId = null; - - if (this.started) - { - // Allow calling tick directly getting correct currentTime - currentTime = currentTime || performance.now(); - timeElapsed = currentTime - this.lastTime; - - // cap the time! - // TODO: Is this there a better way to do this? - if (timeElapsed > this.maxTimeElapsed) - { - timeElapsed = this.maxTimeElapsed; - } - - // TODO: Would love to know what to name this magic number 0.6 - this.deltaTime = (timeElapsed * 0.06); - this.deltaTime *= this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - - this.lastTime = currentTime; - } - - // Check again here because listeners could have side effects - // and may have modified state during frame execution. - // A new frame may have been requested or listeners removed. - if (this.started && this._rafId === null && hasListeners(this._emitter)) - { - this._rafId = requestAnimationFrame(this._tick); - } -}; - -/** - * The shared ticker instance used by {@link PIXI.extras.MovieClip}. - * The property {@link PIXI.extras.Ticker#autoStart} is set to true - * for this instance. - * - * @type {PIXI.extras.Ticker} - * @memberof PIXI.extras.Ticker - */ -Ticker.sharedTicker = new Ticker(); -Ticker.sharedTicker.autoStart = true; - -module.exports = Ticker; diff --git a/src/extras/index.js b/src/extras/index.js index aa08e38..1f85635 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -13,7 +13,6 @@ * @namespace PIXI.extras */ module.exports = { - Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), BitmapText: require('./BitmapText'), diff --git a/src/index.js b/src/index.js index a149f72..a6e9ca7 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ core.loaders = require('./loaders'); core.mesh = require('./mesh'); core.spine = require('pixi-spine'); +core.ticker = require('./ticker'); // export a premade loader instance core.loader = new core.loaders.Loader(); diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 338981e..2de4279 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -91,14 +91,6 @@ */ this.eventsAdded = false; - /** - * The ID of the requestAnimationFrame call, so we can clear it in destroy. - * - * @member {number} - * @private - */ - this.requestId = 0; - //this will make it so that you don't have to call bind all the time /** @@ -169,17 +161,7 @@ */ this.resolution = 1; - /** - * The update method bound to our context. - * - * @member {function} - * @private - */ - this.updateBound = this.update.bind(this); - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - this.update(); } InteractionManager.prototype.constructor = InteractionManager; @@ -216,6 +198,8 @@ return; } + core.ticker.shared.add(this.update, this); + if (window.navigator.msPointerEnabled) { this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; @@ -246,6 +230,8 @@ return; } + core.ticker.shared.remove(this.update); + if (window.navigator.msPointerEnabled) { this.interactionDOMElement.style['-ms-content-zooming'] = ''; @@ -268,15 +254,24 @@ }; /** - * updates the state of interactive objects + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. * - * @private + * @param deltaTime {number} */ -InteractionManager.prototype.update = function () +InteractionManager.prototype.update = function (deltaTime) { - this.requestId = requestAnimationFrame(this.updateBound); + this._deltaTime += deltaTime; - if( this.throttleUpdate() || !this.interactionDOMElement) + if (this._deltaTime < this.interactionFrequency) + { + return; + } + + this._deltaTime = 0; + + if (!this.interactionDOMElement) { return; } @@ -290,7 +285,7 @@ this.cursor = 'inherit'; - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered , this.processMouseOverOut.bind(this) , true ); + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); if (this.currentCursorStyle !== this.cursor) { @@ -325,29 +320,6 @@ }; /** - * Ensures the interaction checks don't happen too often by delaying the update loop - * - * @private - */ -InteractionManager.prototype.throttleUpdate = function () -{ - // frequency of 30fps?? - var now = Date.now(); - var diff = now - this.last; - - diff = (diff * this.interactionFrequency ) / 1000; - - if (diff < 1) - { - return true; - } - - this.last = now; - - return false; -}; - -/** * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. * @@ -488,7 +460,7 @@ this.mouse.originalEvent = event; this.eventData.data = this.mouse; this.eventData.stopped = false; - + // Update internal mouse reference this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); @@ -865,10 +837,6 @@ this.processTouchMove = null; this._tempPoint = null; - - cancelAnimationFrame(this.requestId); - - this.updateBound = null; }; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/src/core/const.js b/src/core/const.js index fe5bd07..773a4d8 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -35,6 +35,15 @@ DEG_TO_RAD: Math.PI / 180, /** + * Target frames per millisecond. + * + * @static + * @constant + * @property {number} TARGET_FPMS=0.06 + */ + TARGET_FPMS: 0.06, + + /** * Constant to identify the Renderer Type. * * @static @@ -178,5 +187,7 @@ RREC: 4 }, + // TODO: maybe change to SPRITE.BATCH_SIZE: 2000 + // TODO: maybe add PARTICLE.BATCH_SIZE: 15000 SPRITE_BATCH_SIZE: 2000 //nice balance between mobile and desktop machines }; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 6a34990..b604386 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,5 @@ var core = require('../core'), - Ticker = require('./Ticker'); + ticker = require('../ticker'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -129,7 +129,7 @@ } this.playing = false; - Ticker.sharedTicker.remove(this.update); + ticker.shared.remove(this.update); }; /** @@ -144,7 +144,7 @@ } this.playing = true; - Ticker.sharedTicker.add(this.update, this); + ticker.shared.add(this.update, this); }; /** diff --git a/src/extras/Ticker.js b/src/extras/Ticker.js deleted file mode 100644 index 973d5a6..0000000 --- a/src/extras/Ticker.js +++ /dev/null @@ -1,285 +0,0 @@ -var EventEmitter = require('eventemitter3').EventEmitter, - performance = global.performance, - TICK = 'tick'; - -/** - * Yes, this is accessing an internal module:eventemitter3 api. - * Ugly, but calling module:eventemitter3.EventEmitter#listeners - * does a bit too much for what this is for. - * This is simple enough to keep track of and contribute - * back to the eventemitter3 project in the near future. - * - * @private - */ -function hasListeners(emitter) -{ - return !!(emitter._events && emitter._events[TICK]); -} - -/** - * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners - * meant for execution on the next requested animation frame. - * Animation frames are requested only when necessary, - * e.g. When the ticker is started and the emitter has listeners. - * - * @class - * @memberof PIXI.extras - */ -function Ticker() -{ - var _this = this; - /** - * Internal tick method bound to ticker instance. - * This is because in early 2015, Function.bind - * is still 60% slower in high performance scenarios. - * - * @private - */ - this._tick = function _tick(time) { - _this.update(time); - }; - /** - * Internal emitter - * @private - */ - this._emitter = new EventEmitter(); - /** - * Internal frame request reference - * @private - */ - this._rafId = null; - - /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.extras.Ticker.start} has been called. - * `false` if {@link PIXI.extras.Ticker.stop} has been called. - * - * @member {boolean} - */ - this.started = false; - - /** - * Whether or not this ticker should - * start automatically when a listener is added. - * - * @member {boolean} - */ - this.autoStart = false; - - /** - * The deltaTime - * @todo better description - * - * @member {number} - */ - this.deltaTime = 1; - - /** - * The time at the last frame - * @todo better description - * - * @member {number} - */ - this.lastTime = 0; - - /** - * The speed - * @todo better description - * - * @member {number} - */ - this.speed = 1; - - /** - * The maximum time between 2 frames - * @todo better description - * - * @member {number} - */ - this.maxTimeElapsed = 100; -} - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - if (this._rafId === null && hasListeners(this._emitter)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._rafId = requestAnimationFrame(this._tick); - } - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._rafId !== null) - { - cancelAnimationFrame(this._rafId); - this._rafId = null; - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.remove = function remove(fn, once) -{ - // remove listener(s) from internal emitter - this._emitter.off(TICK, fn, once); - - // If there are no listeners, cancel the request. - if (!hasListeners(this._emitter)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._startIfPossible(); - } - - return this; -}; - -/** - * Stops the ticker. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Triggers an update, setting deltaTime, lastTime, and - * firing the internal 'tick' event. After this, if the - * ticker is still started and has listeners, - * another frame is requested. - */ -Ticker.prototype.update = function update(currentTime) -{ - var timeElapsed; - - this._rafId = null; - - if (this.started) - { - // Allow calling tick directly getting correct currentTime - currentTime = currentTime || performance.now(); - timeElapsed = currentTime - this.lastTime; - - // cap the time! - // TODO: Is this there a better way to do this? - if (timeElapsed > this.maxTimeElapsed) - { - timeElapsed = this.maxTimeElapsed; - } - - // TODO: Would love to know what to name this magic number 0.6 - this.deltaTime = (timeElapsed * 0.06); - this.deltaTime *= this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - - this.lastTime = currentTime; - } - - // Check again here because listeners could have side effects - // and may have modified state during frame execution. - // A new frame may have been requested or listeners removed. - if (this.started && this._rafId === null && hasListeners(this._emitter)) - { - this._rafId = requestAnimationFrame(this._tick); - } -}; - -/** - * The shared ticker instance used by {@link PIXI.extras.MovieClip}. - * The property {@link PIXI.extras.Ticker#autoStart} is set to true - * for this instance. - * - * @type {PIXI.extras.Ticker} - * @memberof PIXI.extras.Ticker - */ -Ticker.sharedTicker = new Ticker(); -Ticker.sharedTicker.autoStart = true; - -module.exports = Ticker; diff --git a/src/extras/index.js b/src/extras/index.js index aa08e38..1f85635 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -13,7 +13,6 @@ * @namespace PIXI.extras */ module.exports = { - Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), BitmapText: require('./BitmapText'), diff --git a/src/index.js b/src/index.js index a149f72..a6e9ca7 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ core.loaders = require('./loaders'); core.mesh = require('./mesh'); core.spine = require('pixi-spine'); +core.ticker = require('./ticker'); // export a premade loader instance core.loader = new core.loaders.Loader(); diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 338981e..2de4279 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -91,14 +91,6 @@ */ this.eventsAdded = false; - /** - * The ID of the requestAnimationFrame call, so we can clear it in destroy. - * - * @member {number} - * @private - */ - this.requestId = 0; - //this will make it so that you don't have to call bind all the time /** @@ -169,17 +161,7 @@ */ this.resolution = 1; - /** - * The update method bound to our context. - * - * @member {function} - * @private - */ - this.updateBound = this.update.bind(this); - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - this.update(); } InteractionManager.prototype.constructor = InteractionManager; @@ -216,6 +198,8 @@ return; } + core.ticker.shared.add(this.update, this); + if (window.navigator.msPointerEnabled) { this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; @@ -246,6 +230,8 @@ return; } + core.ticker.shared.remove(this.update); + if (window.navigator.msPointerEnabled) { this.interactionDOMElement.style['-ms-content-zooming'] = ''; @@ -268,15 +254,24 @@ }; /** - * updates the state of interactive objects + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. * - * @private + * @param deltaTime {number} */ -InteractionManager.prototype.update = function () +InteractionManager.prototype.update = function (deltaTime) { - this.requestId = requestAnimationFrame(this.updateBound); + this._deltaTime += deltaTime; - if( this.throttleUpdate() || !this.interactionDOMElement) + if (this._deltaTime < this.interactionFrequency) + { + return; + } + + this._deltaTime = 0; + + if (!this.interactionDOMElement) { return; } @@ -290,7 +285,7 @@ this.cursor = 'inherit'; - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered , this.processMouseOverOut.bind(this) , true ); + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); if (this.currentCursorStyle !== this.cursor) { @@ -325,29 +320,6 @@ }; /** - * Ensures the interaction checks don't happen too often by delaying the update loop - * - * @private - */ -InteractionManager.prototype.throttleUpdate = function () -{ - // frequency of 30fps?? - var now = Date.now(); - var diff = now - this.last; - - diff = (diff * this.interactionFrequency ) / 1000; - - if (diff < 1) - { - return true; - } - - this.last = now; - - return false; -}; - -/** * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. * @@ -488,7 +460,7 @@ this.mouse.originalEvent = event; this.eventData.data = this.mouse; this.eventData.stopped = false; - + // Update internal mouse reference this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); @@ -865,10 +837,6 @@ this.processTouchMove = null; this._tempPoint = null; - - cancelAnimationFrame(this.requestId); - - this.updateBound = null; }; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/polyfill/requestAnimationFrame.js b/src/polyfill/requestAnimationFrame.js index a3d1a99..980a397 100644 --- a/src/polyfill/requestAnimationFrame.js +++ b/src/polyfill/requestAnimationFrame.js @@ -54,7 +54,7 @@ return setTimeout(function () { lastTime = Date.now(); - callback(global.performance.now()); + callback(performance.now()); }, delay); }; } diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/src/core/const.js b/src/core/const.js index fe5bd07..773a4d8 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -35,6 +35,15 @@ DEG_TO_RAD: Math.PI / 180, /** + * Target frames per millisecond. + * + * @static + * @constant + * @property {number} TARGET_FPMS=0.06 + */ + TARGET_FPMS: 0.06, + + /** * Constant to identify the Renderer Type. * * @static @@ -178,5 +187,7 @@ RREC: 4 }, + // TODO: maybe change to SPRITE.BATCH_SIZE: 2000 + // TODO: maybe add PARTICLE.BATCH_SIZE: 15000 SPRITE_BATCH_SIZE: 2000 //nice balance between mobile and desktop machines }; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 6a34990..b604386 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,5 @@ var core = require('../core'), - Ticker = require('./Ticker'); + ticker = require('../ticker'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -129,7 +129,7 @@ } this.playing = false; - Ticker.sharedTicker.remove(this.update); + ticker.shared.remove(this.update); }; /** @@ -144,7 +144,7 @@ } this.playing = true; - Ticker.sharedTicker.add(this.update, this); + ticker.shared.add(this.update, this); }; /** diff --git a/src/extras/Ticker.js b/src/extras/Ticker.js deleted file mode 100644 index 973d5a6..0000000 --- a/src/extras/Ticker.js +++ /dev/null @@ -1,285 +0,0 @@ -var EventEmitter = require('eventemitter3').EventEmitter, - performance = global.performance, - TICK = 'tick'; - -/** - * Yes, this is accessing an internal module:eventemitter3 api. - * Ugly, but calling module:eventemitter3.EventEmitter#listeners - * does a bit too much for what this is for. - * This is simple enough to keep track of and contribute - * back to the eventemitter3 project in the near future. - * - * @private - */ -function hasListeners(emitter) -{ - return !!(emitter._events && emitter._events[TICK]); -} - -/** - * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners - * meant for execution on the next requested animation frame. - * Animation frames are requested only when necessary, - * e.g. When the ticker is started and the emitter has listeners. - * - * @class - * @memberof PIXI.extras - */ -function Ticker() -{ - var _this = this; - /** - * Internal tick method bound to ticker instance. - * This is because in early 2015, Function.bind - * is still 60% slower in high performance scenarios. - * - * @private - */ - this._tick = function _tick(time) { - _this.update(time); - }; - /** - * Internal emitter - * @private - */ - this._emitter = new EventEmitter(); - /** - * Internal frame request reference - * @private - */ - this._rafId = null; - - /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.extras.Ticker.start} has been called. - * `false` if {@link PIXI.extras.Ticker.stop} has been called. - * - * @member {boolean} - */ - this.started = false; - - /** - * Whether or not this ticker should - * start automatically when a listener is added. - * - * @member {boolean} - */ - this.autoStart = false; - - /** - * The deltaTime - * @todo better description - * - * @member {number} - */ - this.deltaTime = 1; - - /** - * The time at the last frame - * @todo better description - * - * @member {number} - */ - this.lastTime = 0; - - /** - * The speed - * @todo better description - * - * @member {number} - */ - this.speed = 1; - - /** - * The maximum time between 2 frames - * @todo better description - * - * @member {number} - */ - this.maxTimeElapsed = 100; -} - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - if (this._rafId === null && hasListeners(this._emitter)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._rafId = requestAnimationFrame(this._tick); - } - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._rafId !== null) - { - cancelAnimationFrame(this._rafId); - this._rafId = null; - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.remove = function remove(fn, once) -{ - // remove listener(s) from internal emitter - this._emitter.off(TICK, fn, once); - - // If there are no listeners, cancel the request. - if (!hasListeners(this._emitter)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._startIfPossible(); - } - - return this; -}; - -/** - * Stops the ticker. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Triggers an update, setting deltaTime, lastTime, and - * firing the internal 'tick' event. After this, if the - * ticker is still started and has listeners, - * another frame is requested. - */ -Ticker.prototype.update = function update(currentTime) -{ - var timeElapsed; - - this._rafId = null; - - if (this.started) - { - // Allow calling tick directly getting correct currentTime - currentTime = currentTime || performance.now(); - timeElapsed = currentTime - this.lastTime; - - // cap the time! - // TODO: Is this there a better way to do this? - if (timeElapsed > this.maxTimeElapsed) - { - timeElapsed = this.maxTimeElapsed; - } - - // TODO: Would love to know what to name this magic number 0.6 - this.deltaTime = (timeElapsed * 0.06); - this.deltaTime *= this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - - this.lastTime = currentTime; - } - - // Check again here because listeners could have side effects - // and may have modified state during frame execution. - // A new frame may have been requested or listeners removed. - if (this.started && this._rafId === null && hasListeners(this._emitter)) - { - this._rafId = requestAnimationFrame(this._tick); - } -}; - -/** - * The shared ticker instance used by {@link PIXI.extras.MovieClip}. - * The property {@link PIXI.extras.Ticker#autoStart} is set to true - * for this instance. - * - * @type {PIXI.extras.Ticker} - * @memberof PIXI.extras.Ticker - */ -Ticker.sharedTicker = new Ticker(); -Ticker.sharedTicker.autoStart = true; - -module.exports = Ticker; diff --git a/src/extras/index.js b/src/extras/index.js index aa08e38..1f85635 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -13,7 +13,6 @@ * @namespace PIXI.extras */ module.exports = { - Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), BitmapText: require('./BitmapText'), diff --git a/src/index.js b/src/index.js index a149f72..a6e9ca7 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ core.loaders = require('./loaders'); core.mesh = require('./mesh'); core.spine = require('pixi-spine'); +core.ticker = require('./ticker'); // export a premade loader instance core.loader = new core.loaders.Loader(); diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 338981e..2de4279 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -91,14 +91,6 @@ */ this.eventsAdded = false; - /** - * The ID of the requestAnimationFrame call, so we can clear it in destroy. - * - * @member {number} - * @private - */ - this.requestId = 0; - //this will make it so that you don't have to call bind all the time /** @@ -169,17 +161,7 @@ */ this.resolution = 1; - /** - * The update method bound to our context. - * - * @member {function} - * @private - */ - this.updateBound = this.update.bind(this); - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - this.update(); } InteractionManager.prototype.constructor = InteractionManager; @@ -216,6 +198,8 @@ return; } + core.ticker.shared.add(this.update, this); + if (window.navigator.msPointerEnabled) { this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; @@ -246,6 +230,8 @@ return; } + core.ticker.shared.remove(this.update); + if (window.navigator.msPointerEnabled) { this.interactionDOMElement.style['-ms-content-zooming'] = ''; @@ -268,15 +254,24 @@ }; /** - * updates the state of interactive objects + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. * - * @private + * @param deltaTime {number} */ -InteractionManager.prototype.update = function () +InteractionManager.prototype.update = function (deltaTime) { - this.requestId = requestAnimationFrame(this.updateBound); + this._deltaTime += deltaTime; - if( this.throttleUpdate() || !this.interactionDOMElement) + if (this._deltaTime < this.interactionFrequency) + { + return; + } + + this._deltaTime = 0; + + if (!this.interactionDOMElement) { return; } @@ -290,7 +285,7 @@ this.cursor = 'inherit'; - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered , this.processMouseOverOut.bind(this) , true ); + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); if (this.currentCursorStyle !== this.cursor) { @@ -325,29 +320,6 @@ }; /** - * Ensures the interaction checks don't happen too often by delaying the update loop - * - * @private - */ -InteractionManager.prototype.throttleUpdate = function () -{ - // frequency of 30fps?? - var now = Date.now(); - var diff = now - this.last; - - diff = (diff * this.interactionFrequency ) / 1000; - - if (diff < 1) - { - return true; - } - - this.last = now; - - return false; -}; - -/** * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. * @@ -488,7 +460,7 @@ this.mouse.originalEvent = event; this.eventData.data = this.mouse; this.eventData.stopped = false; - + // Update internal mouse reference this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); @@ -865,10 +837,6 @@ this.processTouchMove = null; this._tempPoint = null; - - cancelAnimationFrame(this.requestId); - - this.updateBound = null; }; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/polyfill/requestAnimationFrame.js b/src/polyfill/requestAnimationFrame.js index a3d1a99..980a397 100644 --- a/src/polyfill/requestAnimationFrame.js +++ b/src/polyfill/requestAnimationFrame.js @@ -54,7 +54,7 @@ return setTimeout(function () { lastTime = Date.now(); - callback(global.performance.now()); + callback(performance.now()); }, delay); }; } diff --git a/src/ticker/Ticker.js b/src/ticker/Ticker.js new file mode 100644 index 0000000..3309b77 --- /dev/null +++ b/src/ticker/Ticker.js @@ -0,0 +1,327 @@ +var core = require('../core'), + EventEmitter = require('eventemitter3').EventEmitter, + // Internal event used by composed emitter + TICK = 'tick'; + +/** + * Yes, this is accessing an internal module:eventemitter3 api. + * Ugly, but calling module:eventemitter3.EventEmitter#listeners + * does a bit too much for what this is for. + * This is simple enough to keep track of and contribute + * back to the eventemitter3 project in the near future. + * + * @private + */ +function hasListeners(emitter) +{ + return !!(emitter._events && emitter._events[TICK]); +} + +/** + * A Ticker class that runs an update loop that other objects listen to. + * This class is composed around an EventEmitter object to add listeners + * meant for execution on the next requested animation frame. + * Animation frames are requested only when necessary, + * e.g. When the ticker is started and the emitter has listeners. + * + * @class + * @memberof PIXI.ticker + */ +function Ticker() +{ + var _this = this; + /** + * Internal tick method bound to ticker instance. + * This is because in early 2015, Function.bind + * is still 60% slower in high performance scenarios. + * Also separating frame requests from update method + * so listeners may be called at any time and with + * any animation API, just invoke ticker.update(time). + * + * @private + */ + this._tick = function _tick(time) { + + _this._requestId = null; + + if (_this.started) + { + _this.update(time); + } + // Check here because listeners could have side effects + // and may have modified state during frame execution. + // A new frame may have been requested or listeners removed. + if (_this.started && _this._requestId === null && hasListeners(_this._emitter)) + { + _this._requestId = requestAnimationFrame(_this._tick); + } + }; + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed millseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should + * start automatically when a listener is added. + * + * @member {boolean} + */ + this.autoStart = false; + + /** + * The current percentage of the + * target FPS with speed factored in. + * + * @member {number} + */ + this.deltaTime = 1; + + /** + * The time elapsed in milliseconds + * from current frame since the last frame. + * + * @member {number} + */ + this.elapsedMS = 0; + + /** + * The last time {@link PIXI.ticker.Ticker#update} + * was invoked by animation frame callback or manually. + * + * @member {number} + */ + this.lastTime = 0; + + /** + * Factor of current FPS. + * @example + * ticker.speed = 2; // Approximately 120 FPS, or 0.12 FPMS. + * + * @member {number} + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker.start} has been called. + * `false` if {@link PIXI.ticker.Ticker.stop} has been called. + * + * @member {boolean} + */ + this.started = false; +} + +Object.defineProperties(Ticker.prototype, { + /** + * Gets the frames per second for which this + * ticker is running. The default is appoximately + * 60 FPS in modern browsers, but may vary. + * This also factors in the property value of + * {@link PIXI.ticker.Ticker#speed}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @readonly + */ + FPS: { + get: function() + { + return core.TARGET_FPMS * 1000 * this.deltaTime; + } + }, + + /** + * This property manages the maximum amount + * of time allowed to elapse between ticks, + * or calls to {@link PIXI.ticker.Ticker#update}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @default 10 + */ + minFPS: { + get: function() + { + return 1000 / this._maxElapsedMS; + }, + set: function(fps) + { + // Clamp: 0 to TARGET_FPMS + var minFPMS = Math.min(Math.max(0, fps) / 1000, core.TARGET_FPMS); + this._maxElapsedMS = 1 / minFPMS; + } + } +}); + +/** + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * + * @private + */ +Ticker.prototype._requestIfNeeded = function _requestIfNeeded() +{ + if (this._requestId === null && hasListeners(this._emitter)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } +}; + +/** + * Conditionally cancels a pending animation frame. + * + * @private + */ +Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() +{ + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } +}; + +/** + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * + * @private + */ +Ticker.prototype._startIfPossible = function _startIfPossible() +{ + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } +}; + +/** + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. + * + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.add = function add(fn, context) +{ + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; +}; + +/** + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. + * + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.addOnce = function addOnce(fn, context) +{ + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; +}; + +/** + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. + * + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.remove = function remove(fn, once) +{ + this._emitter.off(TICK, fn, once); + + if (!hasListeners(this._emitter)) + { + this._cancelIfNeeded(); + } + + return this; +}; + +/** + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. + */ +Ticker.prototype.start = function start() +{ + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } +}; + +/** + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. + */ +Ticker.prototype.stop = function stop() +{ + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } +}; + +/** + * Triggers an update, setting deltaTime, lastTime, and + * firing the internal 'tick' event invoking all listeners. + * + * @param [currentTime=performance.now()] {number} the current time of execution + */ +Ticker.prototype.update = function update(currentTime) +{ + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed + if (this.elapsedMS > this._maxElapsedMS) + { + this.elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = (this.elapsedMS * core.TARGET_FPMS); + // Factor in speed + this.deltaTime *= this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + + this.lastTime = currentTime; +}; + +module.exports = Ticker; diff --git a/.jshintrc b/.jshintrc index 981ab3f..e8dff7e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -37,6 +37,7 @@ "maxcomplexity" : false, // Restrict the cyclomatic complexity of the code. "maxlen" : 220, // Require that all lines are 100 characters or less. "globals" : { // Register globals that are used in the code. + "performance": false // Depends on polyfill, simplifies usage }, // == Relaxing Options ================================================ diff --git a/src/core/const.js b/src/core/const.js index fe5bd07..773a4d8 100644 --- a/src/core/const.js +++ b/src/core/const.js @@ -35,6 +35,15 @@ DEG_TO_RAD: Math.PI / 180, /** + * Target frames per millisecond. + * + * @static + * @constant + * @property {number} TARGET_FPMS=0.06 + */ + TARGET_FPMS: 0.06, + + /** * Constant to identify the Renderer Type. * * @static @@ -178,5 +187,7 @@ RREC: 4 }, + // TODO: maybe change to SPRITE.BATCH_SIZE: 2000 + // TODO: maybe add PARTICLE.BATCH_SIZE: 15000 SPRITE_BATCH_SIZE: 2000 //nice balance between mobile and desktop machines }; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 6a34990..b604386 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,5 @@ var core = require('../core'), - Ticker = require('./Ticker'); + ticker = require('../ticker'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -129,7 +129,7 @@ } this.playing = false; - Ticker.sharedTicker.remove(this.update); + ticker.shared.remove(this.update); }; /** @@ -144,7 +144,7 @@ } this.playing = true; - Ticker.sharedTicker.add(this.update, this); + ticker.shared.add(this.update, this); }; /** diff --git a/src/extras/Ticker.js b/src/extras/Ticker.js deleted file mode 100644 index 973d5a6..0000000 --- a/src/extras/Ticker.js +++ /dev/null @@ -1,285 +0,0 @@ -var EventEmitter = require('eventemitter3').EventEmitter, - performance = global.performance, - TICK = 'tick'; - -/** - * Yes, this is accessing an internal module:eventemitter3 api. - * Ugly, but calling module:eventemitter3.EventEmitter#listeners - * does a bit too much for what this is for. - * This is simple enough to keep track of and contribute - * back to the eventemitter3 project in the near future. - * - * @private - */ -function hasListeners(emitter) -{ - return !!(emitter._events && emitter._events[TICK]); -} - -/** - * A Ticker class that runs an update loop that other objects listen to. - * This class is composed around an EventEmitter object to add listeners - * meant for execution on the next requested animation frame. - * Animation frames are requested only when necessary, - * e.g. When the ticker is started and the emitter has listeners. - * - * @class - * @memberof PIXI.extras - */ -function Ticker() -{ - var _this = this; - /** - * Internal tick method bound to ticker instance. - * This is because in early 2015, Function.bind - * is still 60% slower in high performance scenarios. - * - * @private - */ - this._tick = function _tick(time) { - _this.update(time); - }; - /** - * Internal emitter - * @private - */ - this._emitter = new EventEmitter(); - /** - * Internal frame request reference - * @private - */ - this._rafId = null; - - /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.extras.Ticker.start} has been called. - * `false` if {@link PIXI.extras.Ticker.stop} has been called. - * - * @member {boolean} - */ - this.started = false; - - /** - * Whether or not this ticker should - * start automatically when a listener is added. - * - * @member {boolean} - */ - this.autoStart = false; - - /** - * The deltaTime - * @todo better description - * - * @member {number} - */ - this.deltaTime = 1; - - /** - * The time at the last frame - * @todo better description - * - * @member {number} - */ - this.lastTime = 0; - - /** - * The speed - * @todo better description - * - * @member {number} - */ - this.speed = 1; - - /** - * The maximum time between 2 frames - * @todo better description - * - * @member {number} - */ - this.maxTimeElapsed = 100; -} - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - if (this._rafId === null && hasListeners(this._emitter)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._rafId = requestAnimationFrame(this._tick); - } - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._rafId !== null) - { - cancelAnimationFrame(this._rafId); - this._rafId = null; - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.remove = function remove(fn, once) -{ - // remove listener(s) from internal emitter - this._emitter.off(TICK, fn, once); - - // If there are no listeners, cancel the request. - if (!hasListeners(this._emitter)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._startIfPossible(); - } - - return this; -}; - -/** - * Stops the ticker. - * - * @returns {PIXI.extras.Ticker} this - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Triggers an update, setting deltaTime, lastTime, and - * firing the internal 'tick' event. After this, if the - * ticker is still started and has listeners, - * another frame is requested. - */ -Ticker.prototype.update = function update(currentTime) -{ - var timeElapsed; - - this._rafId = null; - - if (this.started) - { - // Allow calling tick directly getting correct currentTime - currentTime = currentTime || performance.now(); - timeElapsed = currentTime - this.lastTime; - - // cap the time! - // TODO: Is this there a better way to do this? - if (timeElapsed > this.maxTimeElapsed) - { - timeElapsed = this.maxTimeElapsed; - } - - // TODO: Would love to know what to name this magic number 0.6 - this.deltaTime = (timeElapsed * 0.06); - this.deltaTime *= this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - - this.lastTime = currentTime; - } - - // Check again here because listeners could have side effects - // and may have modified state during frame execution. - // A new frame may have been requested or listeners removed. - if (this.started && this._rafId === null && hasListeners(this._emitter)) - { - this._rafId = requestAnimationFrame(this._tick); - } -}; - -/** - * The shared ticker instance used by {@link PIXI.extras.MovieClip}. - * The property {@link PIXI.extras.Ticker#autoStart} is set to true - * for this instance. - * - * @type {PIXI.extras.Ticker} - * @memberof PIXI.extras.Ticker - */ -Ticker.sharedTicker = new Ticker(); -Ticker.sharedTicker.autoStart = true; - -module.exports = Ticker; diff --git a/src/extras/index.js b/src/extras/index.js index aa08e38..1f85635 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -13,7 +13,6 @@ * @namespace PIXI.extras */ module.exports = { - Ticker: require('./Ticker'), MovieClip: require('./MovieClip'), TilingSprite: require('./TilingSprite'), BitmapText: require('./BitmapText'), diff --git a/src/index.js b/src/index.js index a149f72..a6e9ca7 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,7 @@ core.loaders = require('./loaders'); core.mesh = require('./mesh'); core.spine = require('pixi-spine'); +core.ticker = require('./ticker'); // export a premade loader instance core.loader = new core.loaders.Loader(); diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 338981e..2de4279 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -91,14 +91,6 @@ */ this.eventsAdded = false; - /** - * The ID of the requestAnimationFrame call, so we can clear it in destroy. - * - * @member {number} - * @private - */ - this.requestId = 0; - //this will make it so that you don't have to call bind all the time /** @@ -169,17 +161,7 @@ */ this.resolution = 1; - /** - * The update method bound to our context. - * - * @member {function} - * @private - */ - this.updateBound = this.update.bind(this); - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - this.update(); } InteractionManager.prototype.constructor = InteractionManager; @@ -216,6 +198,8 @@ return; } + core.ticker.shared.add(this.update, this); + if (window.navigator.msPointerEnabled) { this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; @@ -246,6 +230,8 @@ return; } + core.ticker.shared.remove(this.update); + if (window.navigator.msPointerEnabled) { this.interactionDOMElement.style['-ms-content-zooming'] = ''; @@ -268,15 +254,24 @@ }; /** - * updates the state of interactive objects + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. * - * @private + * @param deltaTime {number} */ -InteractionManager.prototype.update = function () +InteractionManager.prototype.update = function (deltaTime) { - this.requestId = requestAnimationFrame(this.updateBound); + this._deltaTime += deltaTime; - if( this.throttleUpdate() || !this.interactionDOMElement) + if (this._deltaTime < this.interactionFrequency) + { + return; + } + + this._deltaTime = 0; + + if (!this.interactionDOMElement) { return; } @@ -290,7 +285,7 @@ this.cursor = 'inherit'; - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered , this.processMouseOverOut.bind(this) , true ); + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); if (this.currentCursorStyle !== this.cursor) { @@ -325,29 +320,6 @@ }; /** - * Ensures the interaction checks don't happen too often by delaying the update loop - * - * @private - */ -InteractionManager.prototype.throttleUpdate = function () -{ - // frequency of 30fps?? - var now = Date.now(); - var diff = now - this.last; - - diff = (diff * this.interactionFrequency ) / 1000; - - if (diff < 1) - { - return true; - } - - this.last = now; - - return false; -}; - -/** * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. * @@ -488,7 +460,7 @@ this.mouse.originalEvent = event; this.eventData.data = this.mouse; this.eventData.stopped = false; - + // Update internal mouse reference this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); @@ -865,10 +837,6 @@ this.processTouchMove = null; this._tempPoint = null; - - cancelAnimationFrame(this.requestId); - - this.updateBound = null; }; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/polyfill/requestAnimationFrame.js b/src/polyfill/requestAnimationFrame.js index a3d1a99..980a397 100644 --- a/src/polyfill/requestAnimationFrame.js +++ b/src/polyfill/requestAnimationFrame.js @@ -54,7 +54,7 @@ return setTimeout(function () { lastTime = Date.now(); - callback(global.performance.now()); + callback(performance.now()); }, delay); }; } diff --git a/src/ticker/Ticker.js b/src/ticker/Ticker.js new file mode 100644 index 0000000..3309b77 --- /dev/null +++ b/src/ticker/Ticker.js @@ -0,0 +1,327 @@ +var core = require('../core'), + EventEmitter = require('eventemitter3').EventEmitter, + // Internal event used by composed emitter + TICK = 'tick'; + +/** + * Yes, this is accessing an internal module:eventemitter3 api. + * Ugly, but calling module:eventemitter3.EventEmitter#listeners + * does a bit too much for what this is for. + * This is simple enough to keep track of and contribute + * back to the eventemitter3 project in the near future. + * + * @private + */ +function hasListeners(emitter) +{ + return !!(emitter._events && emitter._events[TICK]); +} + +/** + * A Ticker class that runs an update loop that other objects listen to. + * This class is composed around an EventEmitter object to add listeners + * meant for execution on the next requested animation frame. + * Animation frames are requested only when necessary, + * e.g. When the ticker is started and the emitter has listeners. + * + * @class + * @memberof PIXI.ticker + */ +function Ticker() +{ + var _this = this; + /** + * Internal tick method bound to ticker instance. + * This is because in early 2015, Function.bind + * is still 60% slower in high performance scenarios. + * Also separating frame requests from update method + * so listeners may be called at any time and with + * any animation API, just invoke ticker.update(time). + * + * @private + */ + this._tick = function _tick(time) { + + _this._requestId = null; + + if (_this.started) + { + _this.update(time); + } + // Check here because listeners could have side effects + // and may have modified state during frame execution. + // A new frame may have been requested or listeners removed. + if (_this.started && _this._requestId === null && hasListeners(_this._emitter)) + { + _this._requestId = requestAnimationFrame(_this._tick); + } + }; + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed millseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should + * start automatically when a listener is added. + * + * @member {boolean} + */ + this.autoStart = false; + + /** + * The current percentage of the + * target FPS with speed factored in. + * + * @member {number} + */ + this.deltaTime = 1; + + /** + * The time elapsed in milliseconds + * from current frame since the last frame. + * + * @member {number} + */ + this.elapsedMS = 0; + + /** + * The last time {@link PIXI.ticker.Ticker#update} + * was invoked by animation frame callback or manually. + * + * @member {number} + */ + this.lastTime = 0; + + /** + * Factor of current FPS. + * @example + * ticker.speed = 2; // Approximately 120 FPS, or 0.12 FPMS. + * + * @member {number} + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker.start} has been called. + * `false` if {@link PIXI.ticker.Ticker.stop} has been called. + * + * @member {boolean} + */ + this.started = false; +} + +Object.defineProperties(Ticker.prototype, { + /** + * Gets the frames per second for which this + * ticker is running. The default is appoximately + * 60 FPS in modern browsers, but may vary. + * This also factors in the property value of + * {@link PIXI.ticker.Ticker#speed}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @readonly + */ + FPS: { + get: function() + { + return core.TARGET_FPMS * 1000 * this.deltaTime; + } + }, + + /** + * This property manages the maximum amount + * of time allowed to elapse between ticks, + * or calls to {@link PIXI.ticker.Ticker#update}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @default 10 + */ + minFPS: { + get: function() + { + return 1000 / this._maxElapsedMS; + }, + set: function(fps) + { + // Clamp: 0 to TARGET_FPMS + var minFPMS = Math.min(Math.max(0, fps) / 1000, core.TARGET_FPMS); + this._maxElapsedMS = 1 / minFPMS; + } + } +}); + +/** + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * + * @private + */ +Ticker.prototype._requestIfNeeded = function _requestIfNeeded() +{ + if (this._requestId === null && hasListeners(this._emitter)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } +}; + +/** + * Conditionally cancels a pending animation frame. + * + * @private + */ +Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() +{ + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } +}; + +/** + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * + * @private + */ +Ticker.prototype._startIfPossible = function _startIfPossible() +{ + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } +}; + +/** + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. + * + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.add = function add(fn, context) +{ + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; +}; + +/** + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. + * + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.addOnce = function addOnce(fn, context) +{ + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; +}; + +/** + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. + * + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.remove = function remove(fn, once) +{ + this._emitter.off(TICK, fn, once); + + if (!hasListeners(this._emitter)) + { + this._cancelIfNeeded(); + } + + return this; +}; + +/** + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. + */ +Ticker.prototype.start = function start() +{ + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } +}; + +/** + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. + */ +Ticker.prototype.stop = function stop() +{ + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } +}; + +/** + * Triggers an update, setting deltaTime, lastTime, and + * firing the internal 'tick' event invoking all listeners. + * + * @param [currentTime=performance.now()] {number} the current time of execution + */ +Ticker.prototype.update = function update(currentTime) +{ + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed + if (this.elapsedMS > this._maxElapsedMS) + { + this.elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = (this.elapsedMS * core.TARGET_FPMS); + // Factor in speed + this.deltaTime *= this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + + this.lastTime = currentTime; +}; + +module.exports = Ticker; diff --git a/src/ticker/index.js b/src/ticker/index.js new file mode 100644 index 0000000..448b323 --- /dev/null +++ b/src/ticker/index.js @@ -0,0 +1,24 @@ +/** + * @file Main export of the PIXI extras library + * @author Mat Groves + * @copyright 2013-2015 GoodBoyDigital + * @license {@link https://github.com/GoodBoyDigital/pixi.js/blob/master/LICENSE|MIT License} + */ +var Ticker = require('./Ticker'); + +/** + * The shared ticker instance used by {@link PIXI.extras.MovieClip}. + * and by {@link PIXI.interaction.InteractionManager}. + * The property {@link PIXI.ticker.Ticker#autoStart} is set to true + * for this instance. + * + * @type {PIXI.ticker.Ticker} + * @memberof PIXI.ticker + */ +var shared = new Ticker(); +shared.autoStart = true; + +module.exports = { + shared: shared, + Ticker: Ticker +};