#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2013 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_GAME_TIMING_STEPPEDTIMER_H #define NUCLEX_GAME_TIMING_STEPPEDTIMER_H #include "Timer.h" #include #include namespace Nuclex { namespace Game { namespace Timing { // ------------------------------------------------------------------------------------------- // class Clock; // forward declaration so the header isn't required // ------------------------------------------------------------------------------------------- // /// Timer for games that advance time in fixed steps /// /// /// Frame rate independent movement can be implemented in two ways: either via /// time scaling or via time stepping. Time scaling will scale the movements of all /// objects in a game by the amount of time passed since the last frame. Time stepping /// advances time in fixed steps, multiple times if more time has passed than the length /// of a single step. /// /// /// This timer is intended for the stepped approach. The advantages are simpler updating, /// improved stability of physics simulations (most physics engines go haywire if time /// jumps by a large amount) and the guarantee for identical rounding errors for all /// players in a multi-player game. /// /// /// /// steppedTimer.Reset(); /// /// while(!this->quitRequested) { /// GameTime timeStep; /// while(steppedTimer.TryAdvance(timeStep)) { /// UpdateAll(timeStep); /// } /// /// GameTime frameTime = steppedTimer.GetFrameTimeAndReset() /// DrawAll(frameTime); /// /// RunMessagePump(); /// } /// /// /// /// Delta times might not always be the same size. For example, if you decided to run /// your game at 60 Hz, you would observe a repeating pattern of this: /// ///
  ///     16667 microseconds
  ///     16666 microseconds
  ///     16667 microseconds
  ///     16667 microseconds
  ///     16666 microseconds
  ///     16667 microseconds
  ///     16667 microseconds
  ///     16666 microseconds
  ///     16667 microseconds
  ///   
/// /// Note the even distribution of some 16,666 microsecond steps, guaranteeing that after /// one second, your game will have advanced by 1,000,000 microseconds and not /// 999,960 (16,666 x 60) or 1,000,020 (16,667 x 60) as would have resulted from simply /// calculating the step size in microseconds. /// /// /// If you want a fixed, unvarying floating point delta by which your game's time is /// advanced, you can do so, too: since you know that 60 steps will be generated per /// second without fail, you can simply affix your delta time to (1.0 / 60.0) and ignore /// the deltas provided to you by the stepped timer. /// /// /// If your game does not depend on reproducible roundings (i.e. it's not a multiplayer /// game), setting the step frequency to the screen refresh rate will ensure the smoothest /// movement and the least amount of CPU time burned. /// /// /// If an iterative / penetration-allowing physics engine is involved, picking /// a higher update frequency (eg. two times the screen refresh rate if under 120 Hertz) /// results in more stable simulations (since huge forces can occur with big time steps /// in physics engines, causing simulations to explode). /// ///
class SteppedTimer : public Timer { /// Initializes a new stepped timer using the default system clock public: NUCLEX_GAME_API SteppedTimer(); /// Initializes a new stepped timer using the specified clock /// Clock the stepped timer will use public: NUCLEX_GAME_API SteppedTimer(const std::shared_ptr &clock); /// Destroys the stepped timer public: NUCLEX_GAME_API virtual ~SteppedTimer() {} /// Resets the delta times to zero /// /// /// You should call this method once immediately before entering the main loop so the /// time accumulated between the time provider being created and your game becoming /// ready to run will not result in a jump of possibly several seconds of game time /// being skipped/caught up. /// /// /// Do not use this method in your normal update loop as this will result in the time /// that passes between retrieving the timer's delta time or steps and calling Reset(), /// leading to minuscule speed differences depending on the operating system's CPU load /// and performance. /// /// /// If the simulation clock was paused, calling Reset() will also restore it running. /// /// public: NUCLEX_GAME_API void Reset(); /// Retrieves the number of steps per second the timer will produce /// The number of steps time that will be generated per second public: NUCLEX_GAME_API std::size_t GetStepFrequency() const; /// Sets the number of steps per second the timer will produce /// New number of steps time is advanced per second /// /// Altering the step frequency will reset the timer. /// public: NUCLEX_GAME_API void SetStepFrequency(std::size_t stepFrequency); /// Attempts to advance the time by one step /// /// Receives the timings to which time has been advanced. If not enough time has passed, /// will receive the timings of the current step. /// /// /// True if enough time has accumulated and time was stepped forward /// /// /// /// Call this method repeatedly until it returns false during your game's update /// cycle, then advance to the drawing cycle where you can retrieve the total time /// you have stepped forward via the GetFrameTime() method (don't forget to call /// ResetFrameTime() after that). /// /// public: NUCLEX_GAME_API bool TryAdvance(GameTime &gameTime); /// Resets the accumulated frame time /// /// /// Whenever time is successfully stepped forward via TryAdvance(), the amount of time /// returned by the method is accumulated in an internal counter so that it can be /// queried for the draw phase (see usage example) via GetFrameTime() and then reset /// through this method. /// /// public: NUCLEX_GAME_API void ResetFrameTime(); /// Determines the frame time that has accumulated since the last reset /// The amount of time that has been stepped forward since the last reset /// /// /// Whenever time is successfully stepped forward via TryAdvance(), the amount of time /// returned by the method is accumulated in an internal counter so that it can be /// queried for the draw phase (see usage example) via this method and then reset /// through ResetFrameTime(). /// /// public: NUCLEX_GAME_API GameTime GetFrameTime() const; /// Returns the accumulated frame time and resets it /// The frame time accumulated from all steps since the last reset /// /// This is just a convenience method that can be used during the game's draw phase /// to shorten the code needed to obtain the time steps time was advanced by since /// the last frame and reset the frame time in one call. /// public: NUCLEX_GAME_API GameTime GetFrameTimeAndReset(); /// Steps the timer will generate per second private: std::size_t stepFrequency; /// Error accumulator in units of MHz x step frequency private: std::uint64_t error; /// Accumulated advanced real world time for the current frame private: std::chrono::microseconds frameRealWorldUs; /// Accumulated advanced simulation time for the current frame private: std::chrono::microseconds frameSimulationUs; /// Microseconds that have passed in the real world private: std::chrono::microseconds totalRealWorldUs; /// Microseconds that have passed in simulation time private: std::chrono::microseconds totalSimulationUs; }; // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Game::Timing #endif // NUCLEX_GAME_TIMING_STEPPEDTIMER_H