diff --git a/src/core/index.js b/src/core/index.js index 70aa8d6..1c020a6 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -13,6 +13,7 @@ // utils utils: require('./utils'), math: require('./math'), + ticker: require('./ticker'), // display DisplayObject: require('./display/DisplayObject'), diff --git a/src/core/index.js b/src/core/index.js index 70aa8d6..1c020a6 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -13,6 +13,7 @@ // utils utils: require('./utils'), math: require('./math'), + ticker: require('./ticker'), // display DisplayObject: require('./display/DisplayObject'), diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js new file mode 100644 index 0000000..2528897 --- /dev/null +++ b/src/core/ticker/Ticker.js @@ -0,0 +1,349 @@ +var CONST = require('../const'), + EventEmitter = require('eventemitter3'), + // Internal event used by composed emitter + TICK = '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) + { + // Invoke listeners now + _this.update(time); + // Listener side effects may have modified ticker state. + if (_this.started && _this._requestId === null && _this._emitter.listeners(TICK, true)) + { + _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 milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + 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. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; +} + +Object.defineProperties(Ticker.prototype, { + /** + * The frames per second at which this ticker is running. + * The default is approximately 60 in most modern browsers. + * **Note:** This does not factor in the value of + * {@link PIXI.ticker.Ticker#speed}, which is specific + * to scaling {@link PIXI.ticker.Ticker#deltaTime}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @readonly + */ + FPS: { + get: function() + { + return 1000 / this.elapsedMS; + } + }, + + /** + * Manages the maximum amount of milliseconds allowed to + * elapse between invoking {@link PIXI.ticker.Ticker#update}. + * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime}, + * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}. + * When setting this property it is clamped to a value between + * `0` and `PIXI.TARGET_FPMS * 1000`. + * + * @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, CONST.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 && this._emitter.listeners(TICK, true)) + { + // 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. + * + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @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. + * + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @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. + * + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.remove = function remove(fn, context) +{ + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + 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. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. + * + * @param [currentTime=performance.now()] {DOMHighResTimeStamp|number} the current time of execution + */ +Ticker.prototype.update = function update(currentTime) +{ + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + + this.lastTime = currentTime; +}; + +module.exports = Ticker; diff --git a/src/core/index.js b/src/core/index.js index 70aa8d6..1c020a6 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -13,6 +13,7 @@ // utils utils: require('./utils'), math: require('./math'), + ticker: require('./ticker'), // display DisplayObject: require('./display/DisplayObject'), diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js new file mode 100644 index 0000000..2528897 --- /dev/null +++ b/src/core/ticker/Ticker.js @@ -0,0 +1,349 @@ +var CONST = require('../const'), + EventEmitter = require('eventemitter3'), + // Internal event used by composed emitter + TICK = '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) + { + // Invoke listeners now + _this.update(time); + // Listener side effects may have modified ticker state. + if (_this.started && _this._requestId === null && _this._emitter.listeners(TICK, true)) + { + _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 milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + 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. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; +} + +Object.defineProperties(Ticker.prototype, { + /** + * The frames per second at which this ticker is running. + * The default is approximately 60 in most modern browsers. + * **Note:** This does not factor in the value of + * {@link PIXI.ticker.Ticker#speed}, which is specific + * to scaling {@link PIXI.ticker.Ticker#deltaTime}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @readonly + */ + FPS: { + get: function() + { + return 1000 / this.elapsedMS; + } + }, + + /** + * Manages the maximum amount of milliseconds allowed to + * elapse between invoking {@link PIXI.ticker.Ticker#update}. + * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime}, + * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}. + * When setting this property it is clamped to a value between + * `0` and `PIXI.TARGET_FPMS * 1000`. + * + * @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, CONST.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 && this._emitter.listeners(TICK, true)) + { + // 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. + * + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @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. + * + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @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. + * + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.remove = function remove(fn, context) +{ + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + 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. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. + * + * @param [currentTime=performance.now()] {DOMHighResTimeStamp|number} the current time of execution + */ +Ticker.prototype.update = function update(currentTime) +{ + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + + this.lastTime = currentTime; +}; + +module.exports = Ticker; diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js new file mode 100644 index 0000000..c16ed7c --- /dev/null +++ b/src/core/ticker/index.js @@ -0,0 +1,60 @@ +/** + * @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. Please follow the examples for usage, including + * how to opt-out of auto-starting the shared ticker. + * + * @example + * var ticker = PIXI.ticker.shared; + * // Set this to prevent starting this ticker when listeners are added. + * // By default this is true only for the PIXI.ticker.shared instance. + * ticker.autoStart = false; + * // FYI, call this to ensure the ticker is stopped. It should be stopped + * // if you have not attempted to render anything yet. + * ticker.stop(); + * // Call this when you are ready for a running shared ticker. + * ticker.start(); + * + * @example + * // You may use the shared ticker to render... + * var renderer = PIXI.autoDetectRenderer(800, 600); + * var stage = new PIXI.Container(); + * var interactionManager = PIXI.interaction.InteractionManager(renderer); + * document.body.appendChild(renderer.view); + * ticker.add(function (time) { + * renderer.render(stage); + * }); + * + * @example + * // Or you can just update it manually. + * ticker.autoStart = false; + * ticker.stop(); + * function animate(time) { + * ticker.update(time); + * renderer.render(stage); + * requestAnimationFrame(animate); + * } + * animate(performance.now()); + * + * @type {PIXI.ticker.Ticker} + * @memberof PIXI.ticker + */ +var shared = new Ticker(); +shared.autoStart = true; + +/** + * @namespace PIXI.ticker + */ +module.exports = { + shared: shared, + Ticker: Ticker +}; diff --git a/src/core/index.js b/src/core/index.js index 70aa8d6..1c020a6 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -13,6 +13,7 @@ // utils utils: require('./utils'), math: require('./math'), + ticker: require('./ticker'), // display DisplayObject: require('./display/DisplayObject'), diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js new file mode 100644 index 0000000..2528897 --- /dev/null +++ b/src/core/ticker/Ticker.js @@ -0,0 +1,349 @@ +var CONST = require('../const'), + EventEmitter = require('eventemitter3'), + // Internal event used by composed emitter + TICK = '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) + { + // Invoke listeners now + _this.update(time); + // Listener side effects may have modified ticker state. + if (_this.started && _this._requestId === null && _this._emitter.listeners(TICK, true)) + { + _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 milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + 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. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; +} + +Object.defineProperties(Ticker.prototype, { + /** + * The frames per second at which this ticker is running. + * The default is approximately 60 in most modern browsers. + * **Note:** This does not factor in the value of + * {@link PIXI.ticker.Ticker#speed}, which is specific + * to scaling {@link PIXI.ticker.Ticker#deltaTime}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @readonly + */ + FPS: { + get: function() + { + return 1000 / this.elapsedMS; + } + }, + + /** + * Manages the maximum amount of milliseconds allowed to + * elapse between invoking {@link PIXI.ticker.Ticker#update}. + * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime}, + * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}. + * When setting this property it is clamped to a value between + * `0` and `PIXI.TARGET_FPMS * 1000`. + * + * @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, CONST.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 && this._emitter.listeners(TICK, true)) + { + // 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. + * + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @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. + * + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @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. + * + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.remove = function remove(fn, context) +{ + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + 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. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. + * + * @param [currentTime=performance.now()] {DOMHighResTimeStamp|number} the current time of execution + */ +Ticker.prototype.update = function update(currentTime) +{ + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + + this.lastTime = currentTime; +}; + +module.exports = Ticker; diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js new file mode 100644 index 0000000..c16ed7c --- /dev/null +++ b/src/core/ticker/index.js @@ -0,0 +1,60 @@ +/** + * @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. Please follow the examples for usage, including + * how to opt-out of auto-starting the shared ticker. + * + * @example + * var ticker = PIXI.ticker.shared; + * // Set this to prevent starting this ticker when listeners are added. + * // By default this is true only for the PIXI.ticker.shared instance. + * ticker.autoStart = false; + * // FYI, call this to ensure the ticker is stopped. It should be stopped + * // if you have not attempted to render anything yet. + * ticker.stop(); + * // Call this when you are ready for a running shared ticker. + * ticker.start(); + * + * @example + * // You may use the shared ticker to render... + * var renderer = PIXI.autoDetectRenderer(800, 600); + * var stage = new PIXI.Container(); + * var interactionManager = PIXI.interaction.InteractionManager(renderer); + * document.body.appendChild(renderer.view); + * ticker.add(function (time) { + * renderer.render(stage); + * }); + * + * @example + * // Or you can just update it manually. + * ticker.autoStart = false; + * ticker.stop(); + * function animate(time) { + * ticker.update(time); + * renderer.render(stage); + * requestAnimationFrame(animate); + * } + * animate(performance.now()); + * + * @type {PIXI.ticker.Ticker} + * @memberof PIXI.ticker + */ +var shared = new Ticker(); +shared.autoStart = true; + +/** + * @namespace PIXI.ticker + */ +module.exports = { + shared: shared, + Ticker: Ticker +}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index ce6d53f..af39b03 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,4 @@ -var core = require('../core'), - ticker = require('../ticker'); +var core = require('../core'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -119,7 +118,7 @@ * * @member {number} * @memberof PIXI.MovieClip# - * @readonly + * @readonly */ currentFrame: { get: function () @@ -142,7 +141,7 @@ } this.playing = false; - ticker.shared.remove(this.update, this); + core.ticker.shared.remove(this.update, this); }; /** @@ -157,7 +156,7 @@ } this.playing = true; - ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this); }; /** diff --git a/src/core/index.js b/src/core/index.js index 70aa8d6..1c020a6 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -13,6 +13,7 @@ // utils utils: require('./utils'), math: require('./math'), + ticker: require('./ticker'), // display DisplayObject: require('./display/DisplayObject'), diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js new file mode 100644 index 0000000..2528897 --- /dev/null +++ b/src/core/ticker/Ticker.js @@ -0,0 +1,349 @@ +var CONST = require('../const'), + EventEmitter = require('eventemitter3'), + // Internal event used by composed emitter + TICK = '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) + { + // Invoke listeners now + _this.update(time); + // Listener side effects may have modified ticker state. + if (_this.started && _this._requestId === null && _this._emitter.listeners(TICK, true)) + { + _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 milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + 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. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; +} + +Object.defineProperties(Ticker.prototype, { + /** + * The frames per second at which this ticker is running. + * The default is approximately 60 in most modern browsers. + * **Note:** This does not factor in the value of + * {@link PIXI.ticker.Ticker#speed}, which is specific + * to scaling {@link PIXI.ticker.Ticker#deltaTime}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @readonly + */ + FPS: { + get: function() + { + return 1000 / this.elapsedMS; + } + }, + + /** + * Manages the maximum amount of milliseconds allowed to + * elapse between invoking {@link PIXI.ticker.Ticker#update}. + * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime}, + * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}. + * When setting this property it is clamped to a value between + * `0` and `PIXI.TARGET_FPMS * 1000`. + * + * @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, CONST.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 && this._emitter.listeners(TICK, true)) + { + // 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. + * + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @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. + * + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @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. + * + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.remove = function remove(fn, context) +{ + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + 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. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. + * + * @param [currentTime=performance.now()] {DOMHighResTimeStamp|number} the current time of execution + */ +Ticker.prototype.update = function update(currentTime) +{ + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + + this.lastTime = currentTime; +}; + +module.exports = Ticker; diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js new file mode 100644 index 0000000..c16ed7c --- /dev/null +++ b/src/core/ticker/index.js @@ -0,0 +1,60 @@ +/** + * @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. Please follow the examples for usage, including + * how to opt-out of auto-starting the shared ticker. + * + * @example + * var ticker = PIXI.ticker.shared; + * // Set this to prevent starting this ticker when listeners are added. + * // By default this is true only for the PIXI.ticker.shared instance. + * ticker.autoStart = false; + * // FYI, call this to ensure the ticker is stopped. It should be stopped + * // if you have not attempted to render anything yet. + * ticker.stop(); + * // Call this when you are ready for a running shared ticker. + * ticker.start(); + * + * @example + * // You may use the shared ticker to render... + * var renderer = PIXI.autoDetectRenderer(800, 600); + * var stage = new PIXI.Container(); + * var interactionManager = PIXI.interaction.InteractionManager(renderer); + * document.body.appendChild(renderer.view); + * ticker.add(function (time) { + * renderer.render(stage); + * }); + * + * @example + * // Or you can just update it manually. + * ticker.autoStart = false; + * ticker.stop(); + * function animate(time) { + * ticker.update(time); + * renderer.render(stage); + * requestAnimationFrame(animate); + * } + * animate(performance.now()); + * + * @type {PIXI.ticker.Ticker} + * @memberof PIXI.ticker + */ +var shared = new Ticker(); +shared.autoStart = true; + +/** + * @namespace PIXI.ticker + */ +module.exports = { + shared: shared, + Ticker: Ticker +}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index ce6d53f..af39b03 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,4 @@ -var core = require('../core'), - ticker = require('../ticker'); +var core = require('../core'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -119,7 +118,7 @@ * * @member {number} * @memberof PIXI.MovieClip# - * @readonly + * @readonly */ currentFrame: { get: function () @@ -142,7 +141,7 @@ } this.playing = false; - ticker.shared.remove(this.update, this); + core.ticker.shared.remove(this.update, this); }; /** @@ -157,7 +156,7 @@ } this.playing = true; - ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this); }; /** diff --git a/src/index.js b/src/index.js index 8288c0c..be359e3 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,6 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); -core.ticker = require('./ticker'); // export a premade loader instance core.loader = new core.loaders.Loader(); diff --git a/src/core/index.js b/src/core/index.js index 70aa8d6..1c020a6 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -13,6 +13,7 @@ // utils utils: require('./utils'), math: require('./math'), + ticker: require('./ticker'), // display DisplayObject: require('./display/DisplayObject'), diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js new file mode 100644 index 0000000..2528897 --- /dev/null +++ b/src/core/ticker/Ticker.js @@ -0,0 +1,349 @@ +var CONST = require('../const'), + EventEmitter = require('eventemitter3'), + // Internal event used by composed emitter + TICK = '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) + { + // Invoke listeners now + _this.update(time); + // Listener side effects may have modified ticker state. + if (_this.started && _this._requestId === null && _this._emitter.listeners(TICK, true)) + { + _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 milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + 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. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; +} + +Object.defineProperties(Ticker.prototype, { + /** + * The frames per second at which this ticker is running. + * The default is approximately 60 in most modern browsers. + * **Note:** This does not factor in the value of + * {@link PIXI.ticker.Ticker#speed}, which is specific + * to scaling {@link PIXI.ticker.Ticker#deltaTime}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @readonly + */ + FPS: { + get: function() + { + return 1000 / this.elapsedMS; + } + }, + + /** + * Manages the maximum amount of milliseconds allowed to + * elapse between invoking {@link PIXI.ticker.Ticker#update}. + * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime}, + * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}. + * When setting this property it is clamped to a value between + * `0` and `PIXI.TARGET_FPMS * 1000`. + * + * @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, CONST.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 && this._emitter.listeners(TICK, true)) + { + // 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. + * + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @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. + * + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @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. + * + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.remove = function remove(fn, context) +{ + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + 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. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. + * + * @param [currentTime=performance.now()] {DOMHighResTimeStamp|number} the current time of execution + */ +Ticker.prototype.update = function update(currentTime) +{ + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + + this.lastTime = currentTime; +}; + +module.exports = Ticker; diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js new file mode 100644 index 0000000..c16ed7c --- /dev/null +++ b/src/core/ticker/index.js @@ -0,0 +1,60 @@ +/** + * @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. Please follow the examples for usage, including + * how to opt-out of auto-starting the shared ticker. + * + * @example + * var ticker = PIXI.ticker.shared; + * // Set this to prevent starting this ticker when listeners are added. + * // By default this is true only for the PIXI.ticker.shared instance. + * ticker.autoStart = false; + * // FYI, call this to ensure the ticker is stopped. It should be stopped + * // if you have not attempted to render anything yet. + * ticker.stop(); + * // Call this when you are ready for a running shared ticker. + * ticker.start(); + * + * @example + * // You may use the shared ticker to render... + * var renderer = PIXI.autoDetectRenderer(800, 600); + * var stage = new PIXI.Container(); + * var interactionManager = PIXI.interaction.InteractionManager(renderer); + * document.body.appendChild(renderer.view); + * ticker.add(function (time) { + * renderer.render(stage); + * }); + * + * @example + * // Or you can just update it manually. + * ticker.autoStart = false; + * ticker.stop(); + * function animate(time) { + * ticker.update(time); + * renderer.render(stage); + * requestAnimationFrame(animate); + * } + * animate(performance.now()); + * + * @type {PIXI.ticker.Ticker} + * @memberof PIXI.ticker + */ +var shared = new Ticker(); +shared.autoStart = true; + +/** + * @namespace PIXI.ticker + */ +module.exports = { + shared: shared, + Ticker: Ticker +}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index ce6d53f..af39b03 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,4 @@ -var core = require('../core'), - ticker = require('../ticker'); +var core = require('../core'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -119,7 +118,7 @@ * * @member {number} * @memberof PIXI.MovieClip# - * @readonly + * @readonly */ currentFrame: { get: function () @@ -142,7 +141,7 @@ } this.playing = false; - ticker.shared.remove(this.update, this); + core.ticker.shared.remove(this.update, this); }; /** @@ -157,7 +156,7 @@ } this.playing = true; - ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this); }; /** diff --git a/src/index.js b/src/index.js index 8288c0c..be359e3 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,6 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); -core.ticker = require('./ticker'); // export a premade loader instance core.loader = new core.loaders.Loader(); diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index 558d745..a331ca2 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -9,7 +9,7 @@ * * Object.assign( * MyObject.prototype, - * PIXI.interaction.interactiveTarget) + * PIXI.interaction.interactiveTarget * ); */ var interactiveTarget = { diff --git a/src/core/index.js b/src/core/index.js index 70aa8d6..1c020a6 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -13,6 +13,7 @@ // utils utils: require('./utils'), math: require('./math'), + ticker: require('./ticker'), // display DisplayObject: require('./display/DisplayObject'), diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js new file mode 100644 index 0000000..2528897 --- /dev/null +++ b/src/core/ticker/Ticker.js @@ -0,0 +1,349 @@ +var CONST = require('../const'), + EventEmitter = require('eventemitter3'), + // Internal event used by composed emitter + TICK = '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) + { + // Invoke listeners now + _this.update(time); + // Listener side effects may have modified ticker state. + if (_this.started && _this._requestId === null && _this._emitter.listeners(TICK, true)) + { + _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 milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + 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. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; +} + +Object.defineProperties(Ticker.prototype, { + /** + * The frames per second at which this ticker is running. + * The default is approximately 60 in most modern browsers. + * **Note:** This does not factor in the value of + * {@link PIXI.ticker.Ticker#speed}, which is specific + * to scaling {@link PIXI.ticker.Ticker#deltaTime}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @readonly + */ + FPS: { + get: function() + { + return 1000 / this.elapsedMS; + } + }, + + /** + * Manages the maximum amount of milliseconds allowed to + * elapse between invoking {@link PIXI.ticker.Ticker#update}. + * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime}, + * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}. + * When setting this property it is clamped to a value between + * `0` and `PIXI.TARGET_FPMS * 1000`. + * + * @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, CONST.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 && this._emitter.listeners(TICK, true)) + { + // 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. + * + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @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. + * + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @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. + * + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.remove = function remove(fn, context) +{ + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + 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. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. + * + * @param [currentTime=performance.now()] {DOMHighResTimeStamp|number} the current time of execution + */ +Ticker.prototype.update = function update(currentTime) +{ + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + + this.lastTime = currentTime; +}; + +module.exports = Ticker; diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js new file mode 100644 index 0000000..c16ed7c --- /dev/null +++ b/src/core/ticker/index.js @@ -0,0 +1,60 @@ +/** + * @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. Please follow the examples for usage, including + * how to opt-out of auto-starting the shared ticker. + * + * @example + * var ticker = PIXI.ticker.shared; + * // Set this to prevent starting this ticker when listeners are added. + * // By default this is true only for the PIXI.ticker.shared instance. + * ticker.autoStart = false; + * // FYI, call this to ensure the ticker is stopped. It should be stopped + * // if you have not attempted to render anything yet. + * ticker.stop(); + * // Call this when you are ready for a running shared ticker. + * ticker.start(); + * + * @example + * // You may use the shared ticker to render... + * var renderer = PIXI.autoDetectRenderer(800, 600); + * var stage = new PIXI.Container(); + * var interactionManager = PIXI.interaction.InteractionManager(renderer); + * document.body.appendChild(renderer.view); + * ticker.add(function (time) { + * renderer.render(stage); + * }); + * + * @example + * // Or you can just update it manually. + * ticker.autoStart = false; + * ticker.stop(); + * function animate(time) { + * ticker.update(time); + * renderer.render(stage); + * requestAnimationFrame(animate); + * } + * animate(performance.now()); + * + * @type {PIXI.ticker.Ticker} + * @memberof PIXI.ticker + */ +var shared = new Ticker(); +shared.autoStart = true; + +/** + * @namespace PIXI.ticker + */ +module.exports = { + shared: shared, + Ticker: Ticker +}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index ce6d53f..af39b03 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,4 @@ -var core = require('../core'), - ticker = require('../ticker'); +var core = require('../core'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -119,7 +118,7 @@ * * @member {number} * @memberof PIXI.MovieClip# - * @readonly + * @readonly */ currentFrame: { get: function () @@ -142,7 +141,7 @@ } this.playing = false; - ticker.shared.remove(this.update, this); + core.ticker.shared.remove(this.update, this); }; /** @@ -157,7 +156,7 @@ } this.playing = true; - ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this); }; /** diff --git a/src/index.js b/src/index.js index 8288c0c..be359e3 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,6 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); -core.ticker = require('./ticker'); // export a premade loader instance core.loader = new core.loaders.Loader(); diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index 558d745..a331ca2 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -9,7 +9,7 @@ * * Object.assign( * MyObject.prototype, - * PIXI.interaction.interactiveTarget) + * PIXI.interaction.interactiveTarget * ); */ var interactiveTarget = { diff --git a/src/ticker/Ticker.js b/src/ticker/Ticker.js deleted file mode 100644 index 0bc1f3d..0000000 --- a/src/ticker/Ticker.js +++ /dev/null @@ -1,349 +0,0 @@ -var core = require('../core'), - EventEmitter = require('eventemitter3'), - // Internal event used by composed emitter - TICK = '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) - { - // Invoke listeners now - _this.update(time); - // Listener side effects may have modified ticker state. - if (_this.started && _this._requestId === null && _this._emitter.listeners(TICK, true)) - { - _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 milliseconds between updates. - * @private - */ - this._maxElapsedMS = 100; - - /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. - * - * @member {boolean} - * @default false - */ - this.autoStart = false; - - /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. - * - * @member {number} - * @default 1 - */ - this.deltaTime = 1; - - /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {DOMHighResTimeStamp|number} - * @default 1 / TARGET_FPMS - */ - this.elapsedMS = 1 / core.TARGET_FPMS; // default to target frame time - - /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {DOMHighResTimeStamp|number} - * @default 0 - */ - this.lastTime = 0; - - /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 - */ - 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. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. - * - * @member {boolean} - * @default false - */ - this.started = false; -} - -Object.defineProperties(Ticker.prototype, { - /** - * The frames per second at which this ticker is running. - * The default is approximately 60 in most modern browsers. - * **Note:** This does not factor in the value of - * {@link PIXI.ticker.Ticker#speed}, which is specific - * to scaling {@link PIXI.ticker.Ticker#deltaTime}. - * - * @member - * @memberof PIXI.ticker.Ticker# - * @readonly - */ - FPS: { - get: function() - { - return 1000 / this.elapsedMS; - } - }, - - /** - * Manages the maximum amount of milliseconds allowed to - * elapse between invoking {@link PIXI.ticker.Ticker#update}. - * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime}, - * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}. - * When setting this property it is clamped to a value between - * `0` and `PIXI.TARGET_FPMS * 1000`. - * - * @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 && this._emitter.listeners(TICK, true)) - { - // 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. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @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. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @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. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} this - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - 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. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {DOMHighResTimeStamp|number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * core.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - - this.lastTime = currentTime; -}; - -module.exports = Ticker; diff --git a/src/core/index.js b/src/core/index.js index 70aa8d6..1c020a6 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -13,6 +13,7 @@ // utils utils: require('./utils'), math: require('./math'), + ticker: require('./ticker'), // display DisplayObject: require('./display/DisplayObject'), diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js new file mode 100644 index 0000000..2528897 --- /dev/null +++ b/src/core/ticker/Ticker.js @@ -0,0 +1,349 @@ +var CONST = require('../const'), + EventEmitter = require('eventemitter3'), + // Internal event used by composed emitter + TICK = '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) + { + // Invoke listeners now + _this.update(time); + // Listener side effects may have modified ticker state. + if (_this.started && _this._requestId === null && _this._emitter.listeners(TICK, true)) + { + _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 milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {DOMHighResTimeStamp|number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + 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. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; +} + +Object.defineProperties(Ticker.prototype, { + /** + * The frames per second at which this ticker is running. + * The default is approximately 60 in most modern browsers. + * **Note:** This does not factor in the value of + * {@link PIXI.ticker.Ticker#speed}, which is specific + * to scaling {@link PIXI.ticker.Ticker#deltaTime}. + * + * @member + * @memberof PIXI.ticker.Ticker# + * @readonly + */ + FPS: { + get: function() + { + return 1000 / this.elapsedMS; + } + }, + + /** + * Manages the maximum amount of milliseconds allowed to + * elapse between invoking {@link PIXI.ticker.Ticker#update}. + * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime}, + * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}. + * When setting this property it is clamped to a value between + * `0` and `PIXI.TARGET_FPMS * 1000`. + * + * @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, CONST.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 && this._emitter.listeners(TICK, true)) + { + // 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. + * + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @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. + * + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @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. + * + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} this + */ +Ticker.prototype.remove = function remove(fn, context) +{ + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + 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. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. + * + * @param [currentTime=performance.now()] {DOMHighResTimeStamp|number} the current time of execution + */ +Ticker.prototype.update = function update(currentTime) +{ + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + + this.lastTime = currentTime; +}; + +module.exports = Ticker; diff --git a/src/core/ticker/index.js b/src/core/ticker/index.js new file mode 100644 index 0000000..c16ed7c --- /dev/null +++ b/src/core/ticker/index.js @@ -0,0 +1,60 @@ +/** + * @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. Please follow the examples for usage, including + * how to opt-out of auto-starting the shared ticker. + * + * @example + * var ticker = PIXI.ticker.shared; + * // Set this to prevent starting this ticker when listeners are added. + * // By default this is true only for the PIXI.ticker.shared instance. + * ticker.autoStart = false; + * // FYI, call this to ensure the ticker is stopped. It should be stopped + * // if you have not attempted to render anything yet. + * ticker.stop(); + * // Call this when you are ready for a running shared ticker. + * ticker.start(); + * + * @example + * // You may use the shared ticker to render... + * var renderer = PIXI.autoDetectRenderer(800, 600); + * var stage = new PIXI.Container(); + * var interactionManager = PIXI.interaction.InteractionManager(renderer); + * document.body.appendChild(renderer.view); + * ticker.add(function (time) { + * renderer.render(stage); + * }); + * + * @example + * // Or you can just update it manually. + * ticker.autoStart = false; + * ticker.stop(); + * function animate(time) { + * ticker.update(time); + * renderer.render(stage); + * requestAnimationFrame(animate); + * } + * animate(performance.now()); + * + * @type {PIXI.ticker.Ticker} + * @memberof PIXI.ticker + */ +var shared = new Ticker(); +shared.autoStart = true; + +/** + * @namespace PIXI.ticker + */ +module.exports = { + shared: shared, + Ticker: Ticker +}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index ce6d53f..af39b03 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -1,5 +1,4 @@ -var core = require('../core'), - ticker = require('../ticker'); +var core = require('../core'); /** * A MovieClip is a simple way to display an animation depicted by a list of textures. @@ -119,7 +118,7 @@ * * @member {number} * @memberof PIXI.MovieClip# - * @readonly + * @readonly */ currentFrame: { get: function () @@ -142,7 +141,7 @@ } this.playing = false; - ticker.shared.remove(this.update, this); + core.ticker.shared.remove(this.update, this); }; /** @@ -157,7 +156,7 @@ } this.playing = true; - ticker.shared.add(this.update, this); + core.ticker.shared.add(this.update, this); }; /** diff --git a/src/index.js b/src/index.js index 8288c0c..be359e3 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,6 @@ core.interaction = require('./interaction'); core.loaders = require('./loaders'); core.mesh = require('./mesh'); -core.ticker = require('./ticker'); // export a premade loader instance core.loader = new core.loaders.Loader(); diff --git a/src/interaction/interactiveTarget.js b/src/interaction/interactiveTarget.js index 558d745..a331ca2 100644 --- a/src/interaction/interactiveTarget.js +++ b/src/interaction/interactiveTarget.js @@ -9,7 +9,7 @@ * * Object.assign( * MyObject.prototype, - * PIXI.interaction.interactiveTarget) + * PIXI.interaction.interactiveTarget * ); */ var interactiveTarget = { diff --git a/src/ticker/Ticker.js b/src/ticker/Ticker.js deleted file mode 100644 index 0bc1f3d..0000000 --- a/src/ticker/Ticker.js +++ /dev/null @@ -1,349 +0,0 @@ -var core = require('../core'), - EventEmitter = require('eventemitter3'), - // Internal event used by composed emitter - TICK = '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) - { - // Invoke listeners now - _this.update(time); - // Listener side effects may have modified ticker state. - if (_this.started && _this._requestId === null && _this._emitter.listeners(TICK, true)) - { - _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 milliseconds between updates. - * @private - */ - this._maxElapsedMS = 100; - - /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. - * - * @member {boolean} - * @default false - */ - this.autoStart = false; - - /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. - * - * @member {number} - * @default 1 - */ - this.deltaTime = 1; - - /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {DOMHighResTimeStamp|number} - * @default 1 / TARGET_FPMS - */ - this.elapsedMS = 1 / core.TARGET_FPMS; // default to target frame time - - /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {DOMHighResTimeStamp|number} - * @default 0 - */ - this.lastTime = 0; - - /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 - */ - 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. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. - * - * @member {boolean} - * @default false - */ - this.started = false; -} - -Object.defineProperties(Ticker.prototype, { - /** - * The frames per second at which this ticker is running. - * The default is approximately 60 in most modern browsers. - * **Note:** This does not factor in the value of - * {@link PIXI.ticker.Ticker#speed}, which is specific - * to scaling {@link PIXI.ticker.Ticker#deltaTime}. - * - * @member - * @memberof PIXI.ticker.Ticker# - * @readonly - */ - FPS: { - get: function() - { - return 1000 / this.elapsedMS; - } - }, - - /** - * Manages the maximum amount of milliseconds allowed to - * elapse between invoking {@link PIXI.ticker.Ticker#update}. - * This value is used to cap {@link PIXI.ticker.Ticker#deltaTime}, - * but does not effect the measured value of {@link PIXI.ticker.Ticker#FPS}. - * When setting this property it is clamped to a value between - * `0` and `PIXI.TARGET_FPMS * 1000`. - * - * @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 && this._emitter.listeners(TICK, true)) - { - // 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. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @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. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @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. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} this - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - 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. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {DOMHighResTimeStamp|number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * core.TARGET_FPMS * 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 deleted file mode 100644 index 6547969..0000000 --- a/src/ticker/index.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @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. Please follow the examples for usage, including - * how to opt-out of auto-starting the shared ticker. - * - * @example - * var ticker = PIXI.ticker.shared; - * // Set this to prevent starting this ticker when listeners are added. - * // By default this is true only for the PIXI.ticker.shared instance. - * ticker.autoStart = false; - * // FYI, call this to ensure the ticker is stopped. It should be stopped - * // if you have not attempted to render anything yet. - * ticker.stop(); - * // Call this when you are ready for a running shared ticker. - * ticker.start(); - * - * @example - * // You may use the shared ticker to render... - * var renderer = PIXI.autoDetectRenderer(800, 600); - * var stage = new PIXI.Container(); - * var interactionManager = PIXI.interaction.InteractionManager(renderer); - * document.body.appendChild(renderer.view); - * ticker.add(function (time) { - * renderer.render(stage); - * }); - * - * @example - * // Or you can just update it manually. - * ticker.autoStart = false; - * ticker.stop(); - * function animate(time) { - * ticker.update(time); - * renderer.render(stage); - * requestAnimationFrame(animate); - * } - * animate(performance.now()); - * - * @type {PIXI.ticker.Ticker} - * @memberof PIXI.ticker - */ -var shared = new Ticker(); -shared.autoStart = true; - -module.exports = { - shared: shared, - Ticker: Ticker -};