Documentation
/*
 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/piot/time-tick
 * Licensed under the MIT License. See LICENSE in the project root for license information.
 */

//! # Time Tick
//!
//! `time-tick` is a Rust crate designed to manage and calculate time intervals for game loops.
//! It provides a simple interface to track elapsed time and determine how many ticks should
//! be processed in a single update cycle. This is particularly useful in games or simulations
//! that require consistent timing for updates and actions.
//!
//! ## Features
//! - Track time intervals for game updates.
//! - Specify tick durations and maximum ticks per update.
//! - Reset and adjust tick timing dynamically.
//! ## Usage
//!
//! To use this crate, add the following to your `Cargo.toml`:
//!
//! ```toml
//! [dependencies]
//! time-tick = "0.1" // Replace with the latest version
//! ```

use monotonic_time_rs::{Millis, MillisDuration};

/// A struct to manage and calculate the number of ticks for a game loop based on elapsed time.
///
/// The `TimeTick` struct is designed to track time intervals for game updates, allowing you to
/// specify a duration for each tick and the maximum number of ticks that can occur in a single
/// update. This is particularly useful for games that require consistent timing for game logic
/// updates.
#[derive(Debug)]
pub struct TimeTick {
    tick_duration: MillisDuration,
    consumed_absolute_time: Millis,
    max_tick_per_update: u16,
}

impl TimeTick {
    /// Creates a new `TimeTick` instance.
    ///
    /// # Arguments
    ///
    /// * `now` - The current absolute time in milliseconds.
    /// * `tick_duration` - The duration of each tick in milliseconds.
    /// * `max_tick_per_update` - The maximum number of ticks that can be processed in a single update.
    ///
    /// # Returns
    ///
    /// A new instance of `TimeTick` initialized with the provided values.
    pub const fn new(now: Millis, tick_duration: MillisDuration, max_tick_per_update: u16) -> Self {
        TimeTick {
            tick_duration,
            consumed_absolute_time: now,
            max_tick_per_update,
        }
    }

    /// Sets a new tick duration.
    ///
    /// This method allows you to change the duration of each tick dynamically.
    ///
    /// # Arguments
    ///
    /// * `tick_duration` - The new duration for each tick in milliseconds.
    pub fn set_tick_duration(&mut self, tick_duration: MillisDuration) {
        self.tick_duration = tick_duration;
    }

    /// Resets the consumed absolute time to the current time.
    ///
    /// This method is useful when you want to restart the timing calculations, for example,
    /// when restarting a game or resetting the state.
    ///
    /// # Arguments
    ///
    /// * `now` - The current absolute time in milliseconds to reset to.
    pub fn reset(&mut self, now: Millis) {
        self.consumed_absolute_time = now;
    }

    /// Calculates the number of ticks that should be performed based on the elapsed time since the last update.
    ///
    /// This method should be called each frame in the game loop. It computes how many ticks can
    /// be processed based on the time that has passed since the last update, clamping the result
    /// to the maximum ticks allowed per update.
    ///
    /// # Arguments
    ///
    /// * `now` - The current absolute time in milliseconds.
    ///
    /// # Returns
    ///
    /// The number of ticks that should be performed this frame.
    #[inline]
    pub fn calculate_ticks(&mut self, now: Millis) -> u16 {
        let time_ahead = now - self.consumed_absolute_time;
        let tick_count = (time_ahead.as_millis() / self.tick_duration.as_millis()) as u16;
        //        trace!("time ahead is: {time_ahead} tick_count:{tick_count}");
        if tick_count >= self.max_tick_per_update {
            self.max_tick_per_update
        } else {
            tick_count
        }
    }

    /// Updates the consumed absolute time based on the number of ticks performed.
    ///
    /// This method should be called after processing the ticks to adjust the internal timer
    /// accordingly. It calculates the new consumed absolute time based on the ticks that have
    /// been processed in this update.
    ///
    /// In most cases, the value passed to this method should be the same as the number of ticks
    /// returned by `calculate_ticks`. However, it can be a lower value if fewer ticks were actually
    /// processed (e.g., due to frame rate limitations or logic constraints). It is important to note
    /// that this value must not exceed the number of ticks returned by `calculate_ticks`.
    ///
    /// # Arguments
    ///
    /// * `tick_count` - The number of ticks that were performed during this update. This value should
    ///   typically match the value returned by `calculate_ticks`, but can be lower in certain cases.
    #[inline]
    pub fn performed_ticks(&mut self, tick_count: u16) {
        self.consumed_absolute_time +=
            MillisDuration::from_millis(tick_count as u64 * self.tick_duration.as_millis())
    }
}