From fb6e602d608d46ab56cc7dda890b4e6578cbf831 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 25 Nov 2014 00:15:09 -0800 Subject: [PATCH 1/4] Time - documentation - Updating documentation for clarity, esp. wrt the now / elapsed / physicsTime. - Marked elapsedMS as deprecated as it is just elapsed based on Date.now instead of the high-res timer; both provide roughly equivalent behavior, with elapsedMS just falling back to worse-case behavior. --- src/time/Time.js | 59 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/time/Time.js b/src/time/Time.js index a76f90831..92621bd82 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -16,55 +16,81 @@ Phaser.Time = function (game) { /** * @property {Phaser.Game} game - Local reference to game. + * @protected */ this.game = game; /** - * @property {number} time - This always contains the Date.now value. + * The `Date.now()` value when the time was last updated. + * @property {integer} time * @protected */ this.time = 0; /** - * @property {number} prevTime - The time the previous update occurred. + * The `now` when the previous update occurred. + * @property {integer} prevTime * @protected */ this.prevTime = 0; /** - * @property {number} now - The high resolution RAF timer value (if RAF is available) or Date.now if using setTimeout. + * An increasing value representing cumulative milliseconds since an undisclosed epoch. + * + * The source may either be from a high-res source (eg. if RAF is available) or the standard Date.now; + * the value can only be relied upon within a particular game instance. + * @property {integer} now * @protected */ this.now = 0; /** - * @property {number} elapsed - Elapsed time since the last frame. In ms if running under setTimeout or an integer if using RAF. + * Elapsed time since the last time update, in milliseconds, based on `now`. + * + * _Note:_ This is updated only once per game loop - even if multiple logic update steps are done. + * Use {@link Phaser.Timer#physicsTime physicsTime} as a basis of game/logic calculations instead. + * + * @property {integer} elapsed * @see Phaser.Time.time * @protected */ this.elapsed = 0; /** - * @property {number} elapsedMS - The time in ms since the last update. Will vary dramatically based on system performance, do not use for physics calculations! + * The time in ms since the last time update, in milliseconds, based on `time`. + * + * _Note:_ This is updated once per game loop - even if multiple logic update steps are done. + * Use {@link Phaser.Timer#physicsTime physicsTime} as a basis of game/logic calculations instead. + * + * @property {integer} elapsedMS * @protected + * @drepecated 2.2.0 - Use `elapsed` instead. + * Both are internal methods representing a change in elapsed time but `elapsed` will always + * be at least as (and probably more) precise and cannot get a negative value if the clock is adjusted. */ this.elapsedMS = 0; /** - * @property {number} pausedTime - Records how long the game has been paused for. Is reset each time the game pauses. + * The `time` when the game was last paused. + * @property {number} pausedTime * @protected */ this.pausedTime = 0; /** - * @property {number} desiredFps - The desired frame rate of your game. + * The desired frame rate of the game. + * + * This is used is used to calculate the physic/logic multiplier and how to apply catch-up logic updates. + * @property {number} desiredFps * @default */ this.desiredFps = 60; /** - * @property {number} suggestedFps = The suggested frame rate for your game, based on an averaged real frame rate. - * NOTE: Not available until after a few frames have passed, it is recommended to use this after a few seconds (eg. after the menus) + * The suggested frame rate for your game, based on an averaged real frame rate. + * + * _Note:_ This is not available until after a few frames have passed; use it after a few seconds (eg. after the menus) + * @property {number} suggestedFps * @default */ this.suggestedFps = null; @@ -109,12 +135,19 @@ Phaser.Time = function (game) { this.msMax = 0; /** - * @property {number} physicsElapsed - The physics motion value as used by Arcade Physics. Equivalent to 1.0 / Time.desiredFps. + * The physics update delta, in fractional seconds. + * + * This should be used as an applicable multiplier by all logic update steps (eg. `preUpdate/postUpdate/update`) + * to ensure consistent game timing. + * + * With fixed-step updates this normally equivalent to `1.0 / desiredFps`. + * @property {number} physicsElapsed */ this.physicsElapsed = 0; /** - * @property {number} frames - The number of frames record in the last second. Only calculated if Time.advancedTiming is true. + * The number of render frames record in the last second. Only calculated if Time.advancedTiming is true. + * @property {integer} frames */ this.frames = 0; @@ -272,13 +305,13 @@ Phaser.Time.prototype = { update: function (time) { // Set to the old Date.now value - this.elapsedMS = this.time; + var previousDateNow = this.time; // this.time always holds Date.now, this.now may hold the RAF high resolution time value if RAF is available (otherwise it also holds Date.now) this.time = Date.now(); // Adjust accorindlgy. - this.elapsedMS = this.time - this.elapsedMS; + this.elapsedMS = this.time - previousDateNow; // 'now' is currently still holding the time of the last call, move it into prevTime this.prevTime = this.now; From 5f9025f800d425dd05196bd8217c1e6c5903b9f6 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 25 Nov 2014 00:33:13 -0800 Subject: [PATCH 2/4] Sprite/Time - lifespan based on physicsTime - Lifespan is updated based on physics time which makes this consistent with fixed-step updates and tweens, etc. --- src/gameobjects/Sprite.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/gameobjects/Sprite.js b/src/gameobjects/Sprite.js index d310318ed..18ed0c70e 100644 --- a/src/gameobjects/Sprite.js +++ b/src/gameobjects/Sprite.js @@ -116,9 +116,12 @@ Phaser.Sprite = function (game, x, y, key, frame) { this.health = 1; /** - * If you would like the Sprite to have a lifespan once 'born' you can set this to a positive value. Handy for particles, bullets, etc. - * The lifespan is decremented by game.time.elapsed each update, once it reaches zero the kill() function is called. - * @property {number} lifespan - The lifespan of the Sprite (in ms) before it will be killed. + * To given a Sprite a lifespan, in milliseconds, once 'born' you can set this to a positive value. Handy for particles, bullets, etc. + * + * The lifespan is decremented by `game.time.physicsElapsed` (converted to milliseconds) each logic update, + * and {@link Phaser.Sprite.kill kill} is called once the lifespan reaches 0. + * + * @property {number} lifespan * @default */ this.lifespan = 0; @@ -248,7 +251,7 @@ Phaser.Sprite.prototype.preUpdate = function() { if (this.lifespan > 0) { - this.lifespan -= this.game.time.elapsedMS; + this.lifespan -= 1000 * this.game.time.physicsElapsed; if (this.lifespan <= 0) { From e8869e057a8807a504fd4a4c2346ef0923255701 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 25 Nov 2014 00:56:23 -0800 Subject: [PATCH 3/4] Game/Time - updateNumber and updatesThsiFrame - Changed `count` from 0d9678e512f9037b686ca480907c90f82d5b631e to `updateNumber` and expanded documentation; also moved primary usage back to local variable. - Added `updatesThisFrame` which allows (logic) code to detect if it is the last update, or if there are pending updates the same frame. While it could be adventageous in certain cases it will be problematic if such update logic relies in the supplied delta time, as such should change if fixed-timing is deviated from or extended updates are done. - Formatting and documentation. --- src/core/Game.js | 73 ++++++++++++++++++++++++++++++------------------ 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/src/core/Game.js b/src/core/Game.js index c59f16c20..98acd9495 100644 --- a/src/core/Game.js +++ b/src/core/Game.js @@ -287,6 +287,23 @@ Phaser.Game = function (width, height, renderer, parent, state, transparent, ant */ this._codePaused = false; + /** + * The number of the logic update applied this render frame, starting from 0. + * + * The first update is `updateNumber === 0` and the last update is `updateNumber === updatesThisFrame.` + * @property {number} updateNumber + * @protected + */ + this.updateNumber = 0; + + /** + * Number of logic updates expected to occur this render frame; + * will be 1 unless there are catch-ups required (and allowed). + * @property {integer} updatesThisFrame + * @protected + */ + this.updatesThisFrame = 1; + /** * @property {number} _deltaTime - accumulate elapsed time until a logic update is due * @private @@ -294,39 +311,34 @@ Phaser.Game = function (width, height, renderer, parent, state, transparent, ant this._deltaTime = 0; /** - * @property {number} count - Update iteration counter. - * @protected - */ - this.count = 0; - - /** - * @property {number} _lastCount - remember how many 'catch-up' iterations were used on the logicUpdate last frame - * @private - */ + * @property {number} _lastCount - remember how many 'catch-up' iterations were used on the logicUpdate last frame + * @private + */ this._lastCount = 0; /** - * @property {number} _spiralling - if the 'catch-up' iterations are spiralling out of control, this counter is incremented - * @private - */ + * @property {number} _spiralling - if the 'catch-up' iterations are spiralling out of control, this counter is incremented + * @private + */ this._spiralling = 0; /** - * @property {Phaser.Signal} fpsProblemNotifier - if the game is struggling to maintain the desiredFps, this signal will be dispatched - * to suggest that the program adjust it's fps closer to the Time.suggestedFps value - * @public - */ + * If the game is struggling to maintain the desired FPS, this signal will be dispatched. + * The desired/chosen FPS should probably be closer to the {@link Phaser.Time#suggestedFps} value. + * @property {Phaser.Signal} fpsProblemNotifier + * @public + */ this.fpsProblemNotifier = new Phaser.Signal(); /** - * @property {boolean} forceSingleUpdate - Should the game loop force a logic update, regardless of the delta timer? Set to true if you know you need this. You can toggle it on the fly. - */ + * @property {boolean} forceSingleUpdate - Should the game loop force a logic update, regardless of the delta timer? Set to true if you know you need this. You can toggle it on the fly. + */ this.forceSingleUpdate = false; /** - * @property {number} _nextNotification - the soonest game.time.time value that the next fpsProblemNotifier can be dispatched - * @private - */ + * @property {number} _nextNotification - the soonest game.time.time value that the next fpsProblemNotifier can be dispatched + * @private + */ this._nextFpsNotification = 0; // Parse the configuration object (if any) @@ -710,32 +722,39 @@ Phaser.Game.prototype = { // call the game update logic multiple times if necessary to "catch up" with dropped frames // unless forceSingleUpdate is true - this.count = 0; + var count = 0; + + this.updatesThisFrame = Math.floor(this._deltaTime / slowStep); + if (this.forceSingleUpdate) + { + this.updatesThisFrame = Math.min(1, this.updatesThisFrame); + } while (this._deltaTime >= slowStep) { this._deltaTime -= slowStep; + this.updateNumber = count; this.updateLogic(1.0 / this.time.desiredFps); - this.count++; + count++; - if (this.forceSingleUpdate && this.count === 1) + if (this.forceSingleUpdate && count === 1) { break; } } // detect spiralling (if the catch-up loop isn't fast enough, the number of iterations will increase constantly) - if (this.count > this._lastCount) + if (count > this._lastCount) { this._spiralling++; } - else if (this.count < this._lastCount) + else if (count < this._lastCount) { // looks like it caught up successfully, reset the spiral alert counter this._spiralling = 0; } - this._lastCount = this.count; + this._lastCount = count; // call the game render update exactly once every frame unless we're playing catch-up from a spiral condition this.updateRender(this._deltaTime / slowStep); From e28197e6fa3591a39e0d19e7fc2f1c913fd64d38 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 25 Nov 2014 01:46:30 -0800 Subject: [PATCH 4/4] Time - documentation - Removed previous elapsed/elapsedMS deprecation as they act differently when the game has been resumed, assuming that it has been properly paused. - Refined documentation - Moved a few properties for better logical grouping - Annotated timeToCall and timeExpected as protected --- src/time/Time.js | 57 +++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/time/Time.js b/src/time/Time.js index 92621bd82..9acff1d3c 100644 --- a/src/time/Time.js +++ b/src/time/Time.js @@ -29,7 +29,7 @@ Phaser.Time = function (game) { /** * The `now` when the previous update occurred. - * @property {integer} prevTime + * @property {number} prevTime * @protected */ this.prevTime = 0; @@ -37,9 +37,12 @@ Phaser.Time = function (game) { /** * An increasing value representing cumulative milliseconds since an undisclosed epoch. * + * This value must _not_ be used with `Date.now()`. + * * The source may either be from a high-res source (eg. if RAF is available) or the standard Date.now; * the value can only be relied upon within a particular game instance. - * @property {integer} now + * + * @property {number} now * @protected */ this.now = 0; @@ -47,10 +50,12 @@ Phaser.Time = function (game) { /** * Elapsed time since the last time update, in milliseconds, based on `now`. * + * This value _may_ include time that the game is paused/inactive. + * * _Note:_ This is updated only once per game loop - even if multiple logic update steps are done. * Use {@link Phaser.Timer#physicsTime physicsTime} as a basis of game/logic calculations instead. * - * @property {integer} elapsed + * @property {number} elapsed * @see Phaser.Time.time * @protected */ @@ -59,28 +64,33 @@ Phaser.Time = function (game) { /** * The time in ms since the last time update, in milliseconds, based on `time`. * + * This value is corrected for game pauses and will be "about zero" after a game is resumed. + * * _Note:_ This is updated once per game loop - even if multiple logic update steps are done. * Use {@link Phaser.Timer#physicsTime physicsTime} as a basis of game/logic calculations instead. * * @property {integer} elapsedMS * @protected - * @drepecated 2.2.0 - Use `elapsed` instead. - * Both are internal methods representing a change in elapsed time but `elapsed` will always - * be at least as (and probably more) precise and cannot get a negative value if the clock is adjusted. */ this.elapsedMS = 0; /** - * The `time` when the game was last paused. - * @property {number} pausedTime - * @protected + * The physics update delta, in fractional seconds. + * + * This should be used as an applicable multiplier by all logic update steps (eg. `preUpdate/postUpdate/update`) + * to ensure consistent game timing. + * + * With fixed-step updates this normally equivalent to `1.0 / desiredFps`. + * + * @property {number} physicsElapsed */ - this.pausedTime = 0; + this.physicsElapsed = 0; /** * The desired frame rate of the game. * * This is used is used to calculate the physic/logic multiplier and how to apply catch-up logic updates. + * * @property {number} desiredFps * @default */ @@ -90,6 +100,7 @@ Phaser.Time = function (game) { * The suggested frame rate for your game, based on an averaged real frame rate. * * _Note:_ This is not available until after a few frames have passed; use it after a few seconds (eg. after the menus) + * * @property {number} suggestedFps * @default */ @@ -134,17 +145,6 @@ Phaser.Time = function (game) { */ this.msMax = 0; - /** - * The physics update delta, in fractional seconds. - * - * This should be used as an applicable multiplier by all logic update steps (eg. `preUpdate/postUpdate/update`) - * to ensure consistent game timing. - * - * With fixed-step updates this normally equivalent to `1.0 / desiredFps`. - * @property {number} physicsElapsed - */ - this.physicsElapsed = 0; - /** * The number of render frames record in the last second. Only calculated if Time.advancedTiming is true. * @property {integer} frames @@ -152,17 +152,28 @@ Phaser.Time = function (game) { this.frames = 0; /** - * @property {number} pauseDuration - Records how long the game was paused for in miliseconds. + * The `time` when the game was last paused. + * @property {number} pausedTime + * @protected + */ + this.pausedTime = 0; + + /** + * Records how long the game was last paused, in miliseconds. + * (This is not updated until the game is resumed.) + * @property {number} pauseDuration */ this.pauseDuration = 0; /** * @property {number} timeToCall - The value that setTimeout needs to work out when to next update + * @protected */ this.timeToCall = 0; /** * @property {number} timeExpected - The time when the next call is expected when using setTimer to control the update loop + * @protected */ this.timeExpected = 0; @@ -479,4 +490,4 @@ Phaser.Time.prototype = { }; -Phaser.Time.prototype.constructor = Phaser.Time; \ No newline at end of file +Phaser.Time.prototype.constructor = Phaser.Time;