#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_TIMER_H #define NUCLEX_GAME_TIMING_TIMER_H #include "../GameTime.h" #include #include #include namespace Nuclex { namespace Game { namespace Timing { // ------------------------------------------------------------------------------------------- // class Clock; // forward declaration so the header is not required // ------------------------------------------------------------------------------------------- // /// Base class for high-accuracy timers based on integer math /// /// /// This base class takes care of all the complicated details such as clock wraparounds, /// clock frequencies that are not evenly dividable by microseconds and clocks that run /// slower than one tick per microsecond. /// /// /// All of the internal time keeping code is based on integers and takes great care not /// to lose even a single clock tick. As a user of this class, all you need to know is /// that as long as the clock runs at the speed it indicated, after one second exactly /// 1,000,000 microseconds (1 second) will have been counted by the timer with no lost /// ticks or any inaccuracies. /// /// /// Notes if you want to derive your own timer from this class: /// Time is processed like a banking account: the base unit are microseconds. Each passed /// microsecond is credited to the timer (so GetElapsedMicroseconds() would /// check your current balance). Any microseconds you report to the game as elapsed time /// should be withdrawn from the timer by calling AddAccountedMicroseconds() (this will /// reduce the number returned from GetElapsedMicroseconds() by the withdrawn amount). /// /// /// Thread-safety: per instance. Each instance of this class may only /// be accessed by a single thread. If different threads need to access the same instance, /// these accesses need to be synchronized using a mutex. /// /// class Timer { /// Initializes a new timer /// Clock the timer will be updated from public: NUCLEX_GAME_API Timer(const std::shared_ptr &clock); /// Destroys the timer public: NUCLEX_GAME_API virtual ~Timer() {} /// Whether the simulation clock is currently paused /// True if the simulation clock is currently paused, false otherwise public: NUCLEX_GAME_API virtual bool IsSimulationPaused() const { return this->isSimulationPaused; } /// Pauses the simulation clock /// /// Real world time will still continue running, but the timer's simulation time /// will stop counting until the ResumeSimulation() or Reset() methods are called. /// public: NUCLEX_GAME_API virtual void PauseSimulation() { this->isSimulationPaused = true; } /// Resumes the simulation clock after it has been paused public: NUCLEX_GAME_API virtual void ResumeSimulation() { this->isSimulationPaused = false; } /// 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 virtual void Reset(); /// Calculates the number of microseconds that have elapsed /// The number of elapsed microseconds protected: NUCLEX_GAME_API std::chrono::microseconds GetElapsedMicroseconds() const; /// Adds microseconds that the timer has accounted for /// Number of microseconds the timer has accounted for protected: NUCLEX_GAME_API void AddAccountedMicroseconds( std::chrono::microseconds microseconds ); /// /// Takes all ticks from the timer that have accumulated since the last call /// /// The number of clock ticks accumulated since the last call /// /// This method is intended for internal usage only and handles timer wraparounds /// as it advances the last known clock value. The caller has to take care of adding /// the withdrawn ticks to some counter in order to present a consistent view to /// any user of this class. /// private: std::uint64_t withdrawAllClockTicks() const; /// Updates the scaling values used to adjust to the clock frequency private: void updateClockFrequency(); private: Timer &operator =(const Timer &); private: Timer(const Timer &); /// Whether simulation time is currently paused private: bool isSimulationPaused; /// Clock the timer is using to measure passing time private: const std::shared_ptr clock; /// Frequency at which the clock is running private: std::uint64_t clockFrequency; /// Maximum value the clock can assume before wrapping around private: std::uint64_t wraparoundTicks; /// Clock ticks the timer has already accounted for private: mutable std::uint64_t lastClockTicks; /// Error accumulator in units of MHz x clock frequency private: mutable std::uint64_t error; /// Microseconds the AddAccountedMicroseconds() method could not process private: mutable std::chrono::microseconds unaccountedMicroseconds; }; // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Game::Timing #endif // NUCLEX_GAME_TIMING_TIMER_H