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/Ticker.js b/src/extras/Ticker.js index 973d5a6..e7433c1 100644 --- a/src/extras/Ticker.js +++ b/src/extras/Ticker.js @@ -1,5 +1,6 @@ -var EventEmitter = require('eventemitter3').EventEmitter, - performance = global.performance, +var core = require('../core'), + EventEmitter = require('eventemitter3').EventEmitter, + // Internal event used by composed emitter TICK = 'tick'; /** @@ -33,31 +34,44 @@ * 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.update(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 + * Internal emitter used to fire 'tick' event * @private */ this._emitter = new EventEmitter(); /** - * Internal frame request reference + * Internal current frame request ID * @private */ - this._rafId = null; - + this._requestId = 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} + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed millseconds between updates. + * @private */ - this.started = false; + this._maxElapsedMS = 100; /** * Whether or not this ticker should @@ -68,38 +82,112 @@ this.autoStart = false; /** - * The deltaTime - * @todo better description + * The current percentage of the + * target FPS with speed factored in. * * @member {number} */ this.deltaTime = 1; /** - * The time at the last frame - * @todo better description + * The last time {@link PIXI.extras.Ticker#update} + * was invoked by animation frame callback or manually. * * @member {number} */ this.lastTime = 0; /** - * The speed - * @todo better description + * Factor of current FPS. + * @example + * ticker.speed = 2; // Approximately 120 FPS, or 0.12 FPMS. * * @member {number} */ this.speed = 1; /** - * The maximum time between 2 frames - * @todo better description + * 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 {number} + * @member {boolean} */ - this.maxTimeElapsed = 100; + 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.extras.Ticker#speed}. + * + * @member + * @memberof PIXI.extras.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.extras.Ticker#update}. + * + * @member + * @memberof PIXI.extras.Ticker# + * @default 10 + */ + minFPS: { + get: function() + { + return 1000 / this._maxElapsedMS; + }, + set: function(fps) + { + var minFPMS = Math.min(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 @@ -114,12 +202,7 @@ { if (this.started) { - if (this._rafId === null && hasListeners(this._emitter)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._rafId = requestAnimationFrame(this._tick); - } + this._requestIfNeeded(); } else if (this.autoStart) { @@ -128,20 +211,6 @@ }; /** - * 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. @@ -182,10 +251,8 @@ */ 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(); @@ -197,24 +264,19 @@ /** * 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(); + this._requestIfNeeded(); } - - return this; }; /** - * Stops the ticker. - * - * @returns {PIXI.extras.Ticker} this + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ Ticker.prototype.stop = function stop() { @@ -223,52 +285,36 @@ 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. + * firing the internal 'tick' event invoking all listeners. + * + * @param [currentTime=performance.now()] {number} the current time of execution */ Ticker.prototype.update = function update(currentTime) { - var timeElapsed; + var elapsedMS; - this._rafId = null; + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + elapsedMS = currentTime - this.lastTime; - if (this.started) + // cap the milliseconds elapsed + if (elapsedMS > this._maxElapsedMS) { - // 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; + elapsedMS = this._maxElapsedMS; } - // 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); - } + this.deltaTime = (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; }; /** 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/Ticker.js b/src/extras/Ticker.js index 973d5a6..e7433c1 100644 --- a/src/extras/Ticker.js +++ b/src/extras/Ticker.js @@ -1,5 +1,6 @@ -var EventEmitter = require('eventemitter3').EventEmitter, - performance = global.performance, +var core = require('../core'), + EventEmitter = require('eventemitter3').EventEmitter, + // Internal event used by composed emitter TICK = 'tick'; /** @@ -33,31 +34,44 @@ * 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.update(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 + * Internal emitter used to fire 'tick' event * @private */ this._emitter = new EventEmitter(); /** - * Internal frame request reference + * Internal current frame request ID * @private */ - this._rafId = null; - + this._requestId = 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} + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed millseconds between updates. + * @private */ - this.started = false; + this._maxElapsedMS = 100; /** * Whether or not this ticker should @@ -68,38 +82,112 @@ this.autoStart = false; /** - * The deltaTime - * @todo better description + * The current percentage of the + * target FPS with speed factored in. * * @member {number} */ this.deltaTime = 1; /** - * The time at the last frame - * @todo better description + * The last time {@link PIXI.extras.Ticker#update} + * was invoked by animation frame callback or manually. * * @member {number} */ this.lastTime = 0; /** - * The speed - * @todo better description + * Factor of current FPS. + * @example + * ticker.speed = 2; // Approximately 120 FPS, or 0.12 FPMS. * * @member {number} */ this.speed = 1; /** - * The maximum time between 2 frames - * @todo better description + * 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 {number} + * @member {boolean} */ - this.maxTimeElapsed = 100; + 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.extras.Ticker#speed}. + * + * @member + * @memberof PIXI.extras.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.extras.Ticker#update}. + * + * @member + * @memberof PIXI.extras.Ticker# + * @default 10 + */ + minFPS: { + get: function() + { + return 1000 / this._maxElapsedMS; + }, + set: function(fps) + { + var minFPMS = Math.min(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 @@ -114,12 +202,7 @@ { if (this.started) { - if (this._rafId === null && hasListeners(this._emitter)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._rafId = requestAnimationFrame(this._tick); - } + this._requestIfNeeded(); } else if (this.autoStart) { @@ -128,20 +211,6 @@ }; /** - * 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. @@ -182,10 +251,8 @@ */ 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(); @@ -197,24 +264,19 @@ /** * 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(); + this._requestIfNeeded(); } - - return this; }; /** - * Stops the ticker. - * - * @returns {PIXI.extras.Ticker} this + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ Ticker.prototype.stop = function stop() { @@ -223,52 +285,36 @@ 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. + * firing the internal 'tick' event invoking all listeners. + * + * @param [currentTime=performance.now()] {number} the current time of execution */ Ticker.prototype.update = function update(currentTime) { - var timeElapsed; + var elapsedMS; - this._rafId = null; + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + elapsedMS = currentTime - this.lastTime; - if (this.started) + // cap the milliseconds elapsed + if (elapsedMS > this._maxElapsedMS) { - // 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; + elapsedMS = this._maxElapsedMS; } - // 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); - } + this.deltaTime = (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; }; /** 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); }; }