text: /**
  * @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.
  
  var 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;
  };
  
  MainLoop.prototype.constructor = MainLoop;
  
  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.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;
      }
  
  };
  
  module.exports = MainLoop;
  