#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 // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_GAME_SOURCE 1 #include "Nuclex/Game/Timing/SteppedTimer.h" #include "Nuclex/Game/Timing/Clock.h" #include namespace { // ------------------------------------------------------------------------------------------- // /// The number of microseconds in one second const std::uint64_t MicrosecondsPerSecond = 1000000; /// Step frequency the timer will used when initialized /// /// /// A round number like 100 would be easiest to understand, but since the dawn of TFTs, /// nearly all updating at 60 Hertz, picking any other frequency than 60 doesn't make /// much sense anymore because the game would settle into a pattern of double-update /// frames and single-update frames, creating jerky motion: /// /// /// 60 frames drawn = 100 updates = 40 frames w/2 updates + 20 frames w/1 update /// /// const std::size_t DefaultStepFrequency = 60; // ------------------------------------------------------------------------------------------- // } // anonymous namespace namespace Nuclex { namespace Game { namespace Timing { // ------------------------------------------------------------------------------------------- // SteppedTimer::SteppedTimer() : Timer(Clock::Create()), stepFrequency(DefaultStepFrequency), error(DefaultStepFrequency / 2), frameRealWorldUs(0), frameSimulationUs(0), totalRealWorldUs(0), totalSimulationUs(0) {} // ------------------------------------------------------------------------------------------- // SteppedTimer::SteppedTimer(const std::shared_ptr &clock) : Timer(clock), stepFrequency(DefaultStepFrequency), error(DefaultStepFrequency / 2), frameRealWorldUs(0), frameSimulationUs(0), totalRealWorldUs(0), totalSimulationUs(0) {} // ------------------------------------------------------------------------------------------- // void SteppedTimer::Reset() { Timer::Reset(); this->frameRealWorldUs = std::chrono::microseconds(0); this->frameSimulationUs = std::chrono::microseconds(0); this->totalRealWorldUs = std::chrono::microseconds(0); this->totalSimulationUs = std::chrono::microseconds(0); this->error = this->stepFrequency / 2; } // ------------------------------------------------------------------------------------------- // std::size_t SteppedTimer::GetStepFrequency() const { return this->stepFrequency; } // ------------------------------------------------------------------------------------------- // void SteppedTimer::SetStepFrequency(std::size_t stepFrequency) { if(stepFrequency != this->stepFrequency) { this->stepFrequency = stepFrequency; Reset(); } } // ------------------------------------------------------------------------------------------- // bool SteppedTimer::TryAdvance(GameTime &gameTime) { // Calculate the number of microseconds required to complete the next time step std::chrono::microseconds required = std::chrono::microseconds( (MicrosecondsPerSecond - this->error) / this->stepFrequency ); // In most cases (whenever the result isn't evenly dividable), we'll end up one // microsecond short of the required amount since integer divisions always round down. // If that's the case, compensate by requiring one microsecond more. uint64_t test = this->error + (required.count() * this->stepFrequency); if(test < MicrosecondsPerSecond) { ++required; } // Now we can check whether enough real time has elapsed to step game time forward std::chrono::microseconds elapsedUs = Timer::GetElapsedMicroseconds(); if(elapsedUs >= required) { Timer::AddAccountedMicroseconds(required); // Accumulate the division error which takes care of left-over microseconds in // case the step frequency is not evenly dividable by microseconds. this->error += required.count() * this->stepFrequency; this->error -= MicrosecondsPerSecond; this->totalRealWorldUs += required; this->frameRealWorldUs += required; if(IsSimulationPaused()) { gameTime.SimulationDeltaUs = std::chrono::microseconds(0); } else { gameTime.SimulationDeltaUs = required; this->totalSimulationUs += required; this->frameSimulationUs += required; } gameTime.RealWorldDeltaUs = required; } else { gameTime.SimulationDeltaUs = std::chrono::microseconds(0); gameTime.RealWorldDeltaUs = std::chrono::microseconds(0); } gameTime.RealWorldTotalUs = this->totalRealWorldUs; gameTime.SimulationTotalUs = this->totalSimulationUs; return (elapsedUs >= required); } // ------------------------------------------------------------------------------------------- // void SteppedTimer::ResetFrameTime() { this->frameRealWorldUs = std::chrono::microseconds(0); this->frameSimulationUs = std::chrono::microseconds(0); } // ------------------------------------------------------------------------------------------- // GameTime SteppedTimer::GetFrameTimeAndReset() { std::chrono::microseconds frameRealWorldUs = this->frameRealWorldUs; std::chrono::microseconds frameSimulationUs = this->frameSimulationUs; this->frameRealWorldUs = std::chrono::microseconds(0); this->frameSimulationUs = std::chrono::microseconds(0); return GameTime( this->totalSimulationUs, frameSimulationUs, this->totalRealWorldUs, frameRealWorldUs ); } // ------------------------------------------------------------------------------------------- // GameTime SteppedTimer::GetFrameTime() const { return GameTime( this->totalSimulationUs, this->frameSimulationUs, this->totalRealWorldUs, this->frameRealWorldUs ); } // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Game::Timing