text: ﻿//********************************************************* 
  // 
  // Copyright (c) Microsoft. All rights reserved. 
  // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF 
  // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY 
  // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR 
  // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. 
  // 
  //********************************************************* 
  
  #pragma once
  
  namespace DX
  {
      // Helper class for animation and simulation timing.
      class StepTimer
      {
      public:
          StepTimer() :
              m_elapsedTicks(0),
              m_totalTicks(0),
              m_leftOverTicks(0),
              m_frameCount(0),
              m_framesPerSecond(0),
              m_framesThisSecond(0),
              m_qpcSecondCounter(0),
              m_isFixedTimeStep(false),
              m_targetElapsedTicks(TicksPerSecond / 60)
          {
              m_qpcFrequency = GetPerformanceFrequency();
  
              // Initialize max delta to 1/10 of a second.
              m_qpcMaxDelta = m_qpcFrequency / 10;
          }
  
          // Get elapsed time since the previous Update call.
          uint64 GetElapsedTicks() const                        { return m_elapsedTicks;                  }
          double GetElapsedSeconds() const                      { return TicksToSeconds(m_elapsedTicks);  }
  
          // Get total time since the start of the program.
          uint64 GetTotalTicks() const                          { return m_totalTicks;                    }
          double GetTotalSeconds() const                        { return TicksToSeconds(m_totalTicks);    }
  
          // Get total number of updates since start of the program.
          uint32 GetFrameCount() const                          { return m_frameCount;                    }
  
          // Get the current framerate.
          uint32 GetFramesPerSecond() const                     { return m_framesPerSecond;               }
  
          // Set whether to use fixed or variable timestep mode.
          void SetFixedTimeStep(bool isFixedTimestep)           { m_isFixedTimeStep = isFixedTimestep;    }
  
          // Set how often to call Update when in fixed timestep mode.
          void SetTargetElapsedTicks(uint64 targetElapsed)      { m_targetElapsedTicks = targetElapsed;   }
          void SetTargetElapsedSeconds(double targetElapsed)    { m_targetElapsedTicks = SecondsToTicks(targetElapsed);   }
  
          // Integer format represents time using 10,000,000 ticks per second.
          static const uint64 TicksPerSecond = 10'000'000;
  
          static double TicksToSeconds(uint64 ticks)            { return static_cast<double>(ticks) / TicksPerSecond;     }
          static uint64 SecondsToTicks(double seconds)          { return static_cast<uint64>(seconds * TicksPerSecond);   }
  
          // Convenient wrapper for QueryPerformanceFrequency. Throws an exception if
          // the call to QueryPerformanceFrequency fails.
          static inline uint64 GetPerformanceFrequency()
          {
              LARGE_INTEGER freq;
              if (!QueryPerformanceFrequency(&freq))
              {
                  throw ref new Platform::FailureException();
              }
              return freq.QuadPart;
          }
  
          // Gets the current number of ticks from QueryPerformanceCounter. Throws an
          // exception if the call to QueryPerformanceCounter fails.
          static inline int64 GetTicks()
          {
              LARGE_INTEGER ticks;
              if (!QueryPerformanceCounter(&ticks))
              {
                  throw ref new Platform::FailureException();
              }
              return ticks.QuadPart;
          }
  
          // After an intentional timing discontinuity (for instance a blocking IO operation)
          // call this to avoid having the fixed timestep logic attempt a set of catch-up
          // Update calls.
  
          void ResetElapsedTime()
          {
              m_qpcLastTime = GetTicks();
  
              m_leftOverTicks    = 0;
              m_framesPerSecond  = 0;
              m_framesThisSecond = 0;
              m_qpcSecondCounter = 0;
          }
  
          // Update timer state, calling the specified Update function the appropriate number of times.
          template<typename TUpdate>
          void Tick(const TUpdate& update)
          {
              // Query the current time.
              uint64 currentTime = GetTicks();
              uint64 timeDelta   = currentTime - m_qpcLastTime;
  
              m_qpcLastTime      = currentTime;
              m_qpcSecondCounter += timeDelta;
  
              // Clamp excessively large time deltas (e.g. after paused in the debugger).
              if (timeDelta > m_qpcMaxDelta)
              {
                  timeDelta = m_qpcMaxDelta;
              }
  
              // Convert QPC units into a canonical tick format. This cannot overflow due to the previous clamp.
              timeDelta *= TicksPerSecond;
              timeDelta /= m_qpcFrequency;
  
              uint32 lastFrameCount = m_frameCount;
  
              if (m_isFixedTimeStep)
              {
                  // Fixed timestep update logic
  
                  // If the app is running very close to the target elapsed time (within 1/4 of a millisecond) just clamp
                  // the clock to exactly match the target value. This prevents tiny and irrelevant errors
                  // from accumulating over time. Without this clamping, a game that requested a 60 fps
                  // fixed update, running with vsync enabled on a 59.94 NTSC display, would eventually
                  // accumulate enough tiny errors that it would drop a frame. It is better to just round
                  // small deviations down to zero to leave things running smoothly.
  
                  if (abs(static_cast<int64>(timeDelta - m_targetElapsedTicks)) < TicksPerSecond / 4000)
                  {
                      timeDelta = m_targetElapsedTicks;
                  }
  
                  m_leftOverTicks += timeDelta;
  
                  while (m_leftOverTicks >= m_targetElapsedTicks)
                  {
                      m_elapsedTicks   = m_targetElapsedTicks;
                      m_totalTicks    += m_targetElapsedTicks;
                      m_leftOverTicks -= m_targetElapsedTicks;
                      m_frameCount++;
  
                      update();
                  }
              }
              else
              {
                  // Variable timestep update logic.
                  m_elapsedTicks  = timeDelta;
                  m_totalTicks   += timeDelta;
                  m_leftOverTicks = 0;
                  m_frameCount++;
  
                  update();
              }
  
              // Track the current framerate.
              if (m_frameCount != lastFrameCount)
              {
                  m_framesThisSecond++;
              }
  
              if (m_qpcSecondCounter >= static_cast<uint64>(m_qpcFrequency))
              {
                  m_framesPerSecond   = m_framesThisSecond;
                  m_framesThisSecond  = 0;
                  m_qpcSecondCounter %= m_qpcFrequency;
              }
          }
  
      private:
  
          // Source timing data uses QPC units.
          uint64 m_qpcFrequency;
          uint64 m_qpcLastTime;
          uint64 m_qpcMaxDelta;
  
          // Derived timing data uses a canonical tick format.
          uint64 m_elapsedTicks;
          uint64 m_totalTicks;
          uint64 m_leftOverTicks;
  
          // Members for tracking the framerate.
          uint32 m_frameCount;
          uint32 m_framesPerSecond;
          uint32 m_framesThisSecond;
          uint64 m_qpcSecondCounter;
  
          // Members for configuring fixed timestep mode.
          bool   m_isFixedTimeStep;
          uint64 m_targetElapsedTicks;
      };
  }
  