Program Listing for File timer.hpp

Return to documentation for file (libtcod/timer.hpp)

/* BSD 3-Clause License
 *
 * Copyright © 2008-2025, Jice and the libtcod contributors.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
#pragma once
#ifndef LIBTCOD_TIMER_HPP_
#define LIBTCOD_TIMER_HPP_
#ifndef NO_SDL
#include <SDL3/SDL_timer.h>

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <numeric>
#include <stdexcept>
#include <vector>

#include "config.h"

namespace tcod {

class [[nodiscard]] Timer {
 public:

  Timer() : last_time_{SDL_GetPerformanceCounter()} {}

  float sync(int desired_fps = 0) {
    const uint64_t frequency = SDL_GetPerformanceFrequency();
    uint64_t current_time = SDL_GetPerformanceCounter();  // The precise current time.
    int64_t delta_time = static_cast<int64_t>(current_time - last_time_);  // The precise delta time.
    if (desired_fps > 0) {
      const int64_t desired_delta_time = frequency / desired_fps;  // Desired precise delta time.
      const int64_t time_until_next_frame_ms =
          (desired_delta_time - delta_time) * 1000 / static_cast<int64_t>(frequency);
      if (time_until_next_frame_ms > 2) {
        // Sleep until 1 millisecond before the target time.
        SDL_Delay(static_cast<uint32_t>(time_until_next_frame_ms) - 1);
      }
      while (delta_time < desired_delta_time) {  // Spin for the remaining time.
        current_time = SDL_GetPerformanceCounter();
        delta_time = static_cast<int64_t>(current_time - last_time_);
      }
    }
    last_time_ = current_time;
    const float delta_time_s = std::max(0.0f, static_cast<float>(delta_time) / frequency);  // Delta time in seconds.
    // Drop samples as they hit the total time and count limits.
    double total_time = std::accumulate(samples_.begin(), samples_.end(), 0.0);  // Total time of all samples.
    while (!samples_.empty() && (total_time > MAX_SAMPLES_TIME || samples_.size() >= MAX_SAMPLES_COUNT)) {
      total_time -= samples_.front();
      samples_.pop_front();
    }
    samples_.push_back(delta_time_s);
    return delta_time_s;
  }

  [[nodiscard]] float get_mean_fps() const noexcept {
    if (samples_.empty()) return 0;
    const double total_time = std::accumulate(samples_.begin(), samples_.end(), 0.0);
    if (total_time == 0) return 0;
    return 1.0f / static_cast<float>(total_time / static_cast<double>(samples_.size()));
  }

  [[nodiscard]] float get_last_fps() const noexcept {
    if (samples_.empty()) return 0;
    if (samples_.back() == 0) return 0;
    return 1.0f / samples_.back();
  }

  [[nodiscard]] float get_min_fps() const noexcept {
    if (samples_.empty()) return 0;
    const float sample = *std::max_element(samples_.begin(), samples_.end());
    if (sample == 0) return 0;
    return 1.0f / sample;
  }

  [[nodiscard]] float get_max_fps() const noexcept {
    if (samples_.empty()) return 0;
    const float sample = *std::min_element(samples_.begin(), samples_.end());
    if (sample == 0) return 0;
    return 1.0f / sample;
  }

  [[nodiscard]] float get_median_fps() const noexcept {
    if (samples_.empty()) return 0;
    std::vector<float> samples(samples_.begin(), samples_.end());
    std::sort(samples.begin(), samples.end());
    float median_sample = samples[samples.size() / 2];
    if (samples.size() % 2 == 0 && samples.size() > 2) {
      median_sample = (median_sample + samples[samples.size() / 2 + 1]) / 2.0f;
    }
    if (median_sample == 0) return 0;
    return 1.0f / median_sample;
  }

 private:
  static constexpr size_t MAX_SAMPLES_COUNT = 1024;  // Hard limit on the number of samples held.
  static constexpr double MAX_SAMPLES_TIME = 1.0;  // Hard limit on the total time of samples held.

  uint64_t last_time_;  // The last precise time sampled.
  std::deque<float> samples_;  // The most recent delta time samples in seconds.
};
}  // namespace tcod
#endif  // NO_SDL
#endif  // LIBTCOD_TIMER_HPP_