chron 0.1.6

A game loop with a fixed timestep.
Documentation
//! Provides the game loop iterator and related types.

use std::num;
use std::thread;
use std::time;

/// The game loop iterator, whichs emits [`Tick`] events.
///
/// # Examples
///
/// See the [crate-level documentation](crate#examples).
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(Debug)]
pub struct Clock {
    time_per_update: time::Duration,
    time_per_render: Option<time::Duration>,
    max_updates_per_frame: Option<usize>,
    updates: usize,

    previous_update_time: time::Instant,
    previous_render_time: time::Instant,
}

impl Clock {
    /// Creates a new `Clock`.
    ///
    /// # Examples
    ///
    /// ```
    /// use chron::Clock;
    /// use std::num::NonZeroU32;
    ///
    /// # fn main() { test().unwrap(); }
    /// # fn test() -> Option<()> {
    /// let updates_per_second = NonZeroU32::new(50)?;
    /// let clock = Clock::new(updates_per_second);
    /// # Some(())
    /// # }
    /// ```
    #[inline]
    pub fn new(updates_per_second: num::NonZeroU32) -> Self {
        let time_per_update = time::Duration::from_secs(1) / updates_per_second.get();

        let now = time::Instant::now();

        Clock {
            time_per_update,
            time_per_render: None,
            max_updates_per_frame: None,

            previous_update_time: now - time_per_update,
            previous_render_time: now,
            updates: 0,
        }
    }

    /// Sets the maximum frame rate.
    ///
    /// # Examples
    ///
    /// ```
    /// use chron::Clock;
    /// use std::num::NonZeroU32;
    ///
    /// # fn main() { test().unwrap(); }
    /// # fn test() -> Option<()> {
    /// let updates_per_second = NonZeroU32::new(50)?;
    /// let frames_per_second = NonZeroU32::new(60)?;
    ///
    /// let clock = Clock::new(updates_per_second)
    ///     .max_frame_rate(frames_per_second);
    /// # Some(())
    /// # }
    /// ```
    #[inline]
    pub fn max_frame_rate(mut self, frames_per_second: num::NonZeroU32) -> Self {
        let time_per_render = time::Duration::from_secs(1) / frames_per_second.get();
        self.time_per_render = Some(time_per_render);
        self.previous_render_time -= time_per_render;

        self
    }

    /// See [`Clock::max_frame_rate`].
    #[deprecated(note = "use `Clock::max_frame_rate` instead")]
    #[inline]
    pub fn with_frame_limit(self, frames_per_second: num::NonZeroU32) -> Self {
        self.max_frame_rate(frames_per_second)
    }

    /// Sets the maximum number of updates to be emitted in between frames.
    ///
    /// When the loop cannot maintain the specified *updates per second* (e.g.
    /// because updates are taking too long) it has to play catch-up by only
    /// emitting updates and **no** renders. This setting limits how many
    /// updates are emitted before a render is enforced. It prevents the game
    /// from freezing up, at the cost of slowing down even more.
    ///
    /// # Examples
    ///
    /// ```
    /// use chron::Clock;
    /// use std::num::NonZeroU32;
    ///
    /// # fn main() { test().unwrap(); }
    /// # fn test() -> Option<()> {
    /// let updates_per_second = NonZeroU32::new(50)?;
    ///
    /// let clock = Clock::new(updates_per_second)
    ///     .max_updates_per_frame(3);
    /// # Some(())
    /// # }
    /// ```
    #[inline]
    pub fn max_updates_per_frame(mut self, max_updates_per_frame: usize) -> Self {
        self.max_updates_per_frame = Some(max_updates_per_frame);

        self
    }

    /// See [`Clock::max_updates_per_frame`].
    #[deprecated(note = "use `Clock::max_updates_per_frame` instead")]
    #[inline]
    pub fn with_frame_skip(self, frame_skip: usize) -> Self {
        self.max_updates_per_frame(frame_skip)
    }
}

impl Iterator for Clock {
    type Item = Tick;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        let mut now = time::Instant::now();

        if let Some(time_per_render) = self.time_per_render {
            if now - self.previous_render_time < time_per_render
                && now - self.previous_update_time < self.time_per_update
            {
                let sleep_time = std::cmp::min(
                    self.previous_render_time + time_per_render - now,
                    self.previous_update_time + self.time_per_update - now,
                );

                thread::sleep(sleep_time);

                now += sleep_time;
            }
        }

        if now - self.previous_update_time >= self.time_per_update
            && self.updates < self.max_updates_per_frame.unwrap_or(usize::MAX)
        {
            self.previous_update_time += self.time_per_update;
            self.updates += 1;

            Some(Tick::Update)
        } else {
            self.previous_render_time = now;
            self.updates = 0;

            let interpolation = (now - self.previous_update_time).as_secs_f32()
                / self.time_per_update.as_secs_f32();

            Some(Tick::Render { interpolation })
        }
    }
}

/// A game loop event.
#[derive(Clone, Copy, Debug)]
pub enum Tick {
    /// Indicates that it is time for an update to the game logic.
    Update,

    /// Indicates that it is time to render the game.
    Render {
        /// Indicates how much time elapsed between the previous update and the
        /// next one.
        interpolation: f32,
    },
}