/** * @author Richard Davey <rich@photonstorm.com> * @copyright 2016 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ // My thanks to Isaac Sukin for creating MainLoop.js, on which lots of this is based. Phaser.State.MainLoop = function (state, framerate) { /** * @property {Phaser.State} state */ this.state = state; /** * @property {Phaser.Game} game - A reference to the currently running Game. */ this.game = state.game; // The amount of time (in milliseconds) to simulate each time update() runs. this.timestep = 1000 / framerate; this.physicsStep = 1 / framerate; // The cumulative amount of in-app time that hasn't been simulated yet. // See the comments inside animate() for details. this.frameDelta = 0; // The timestamp in milliseconds of the last time the main loop was run. // Used to compute the time elapsed between frames. this.lastFrameTimeMs = 0; // An exponential moving average of the frames per second. this.fps = 60; // The timestamp (in milliseconds) of the last time the `fps` moving // average was updated. this.lastFpsUpdate = 0; // The number of frames delivered in the current second. this.framesThisSecond = 0; // The number of times update() is called in a given frame. This is only // relevant inside of animate(), but a reference is held externally so that // this variable is not marked for garbage collection every time the main // loop runs. this.numUpdateSteps = 0; // The minimum amount of time in milliseconds that must pass since the last // frame was executed before another frame can be executed. The // multiplicative inverse caps the FPS (the default of zero means there is // no cap). this.minFrameDelay = 0; // Whether the main loop is running. this.running = false; // `true` if `MainLoop.start()` has been called and the most recent time it // was called has not been followed by a call to `MainLoop.stop()`. This is // different than `running` because there is a delay of a few milliseconds // after `MainLoop.start()` is called before the application is considered // "running." This delay is due to waiting for the next frame. this.started = false; // Whether the simulation has fallen too far behind real time. // Specifically, `panic` will be set to `true` if too many updates occur in // one frame. This is only relevant inside of animate(), but a reference is // held externally so that this variable is not marked for garbage // collection every time the main loop runs. this.panic = false; }; Phaser.State.MainLoop.prototype.constructor = Phaser.State.MainLoop; Phaser.State.MainLoop.prototype = { setMaxFPS: function (fps) { if (fps === 0) { this.stop(); } else { this.minFrameDelay = 1000 / fps; } }, getMaxFPS: function () { return 1000 / this.minFrameDelay; }, resetFrameDelta: function () { var oldFrameDelta = this.frameDelta; this.frameDelta = 0; return oldFrameDelta; }, start: function () { if (this.started) { return this; } this.started = true; this.running = true; this.lastFrameTimeMs = window.performance.now(); this.lastFpsUpdate = window.performance.now(); this.framesThisSecond = 0; }, // timestamp = DOMHighResTimeStamp step: function (timestamp) { // Throttle the frame rate (if minFrameDelay is set to a non-zero value by // `MainLoop.setMaxAllowedFPS()`). if (timestamp < this.lastFrameTimeMs + this.minFrameDelay) { return; } // frameDelta is the cumulative amount of in-app time that hasn't been // simulated yet. Add the time since the last frame. We need to track total // not-yet-simulated time (as opposed to just the time elapsed since the // last frame) because not all actually elapsed time is guaranteed to be // simulated each frame. See the comments below for details. this.frameDelta += timestamp - this.lastFrameTimeMs; this.lastFrameTimeMs = timestamp; // Run any updates that are not dependent on time in the simulation. // Here we'll need to run things like tween.update, input.update, etc. this.state.sys.begin(timestamp, this.frameDelta); // Update the estimate of the frame rate, `fps`. Every second, the number // of frames that occurred in that second are included in an exponential // moving average of all frames per second, with an alpha of 0.25. This // means that more recent seconds affect the estimated frame rate more than // older seconds. if (timestamp > this.lastFpsUpdate + 1000) { // Compute the new exponential moving average with an alpha of 0.25. // Using constants inline is okay here. this.fps = 0.25 * this.framesThisSecond + 0.75 * this.fps; this.lastFpsUpdate = timestamp; this.framesThisSecond = 0; } this.framesThisSecond++; this.numUpdateSteps = 0; var step = this.timestep; while (this.frameDelta >= step) { // this.update(this.timestep); this.state.sys.update(step, this.physicsStep); for (var c = 0; c < this.state.sys.children.list.length; c++) { var child = this.state.sys.children.list[c]; if (child.exists) { child.update(step); } } // Dev level callback this.state.update(step); this.frameDelta -= this.timestep; if (++this.numUpdateSteps >= 240) { this.panic = true; break; } } this.state.sys.preRender(); this.state.sys.updates.start(); if (this.state.settings.visible && this.state.sys.color.alpha !== 0 && this.state.sys.children.list.length > 0) { this.game.renderer.render(this.state, this.frameDelta / this.timestep); } this.state.sys.updates.stop(); // Run any updates that are not dependent on time in the simulation. this.state.sys.end(this.fps, this.panic); this.panic = false; }, /* update: function (timestep) { this.state.sys.update(timestep); var c; var child; for (var c = 0; c < this.state.sys.children.list.length; c++) { var child = this.state.sys.children.list[c]; if (child.exists) { child.update(timestep); } } // Dev level callback this.state.update(timestep); for (c = 0; c < this.state.sys.children.list.length; c++) { var child = this.state.sys.children.list[c]; if (child.exists) { child.update(timestep); } } }, */ stop: function () { this.running = false; this.started = false; return this; } };