fixedstep 0.3.0

Simple macro to create a fixed timestep loop for a game
Documentation

use std::time::{Duration, Instant};

const NANOS_PER_SECOND: f64 = 1_000_000_000.0;

pub struct FixedStep {
    last_time: Instant,
    update_interval: Duration,
    accumulator: Duration,
    update_counter: u32,
    update_limit: u32,
}

impl FixedStep {
    /// Create and start a new fixedstep timer with the given frequency in Hz
    pub fn start(hz: f64) -> Self {
        let seconds = 1.0 / hz;
        let full_seconds = seconds as u64;
        let remaining_nanos = (seconds.fract() * NANOS_PER_SECOND) as u32;
        FixedStep {
            update_interval: Duration::new(full_seconds, remaining_nanos),
            last_time: Instant::now(),
            accumulator: Duration::new(0, 0),
            update_counter: 0,
            update_limit: 3,
        }
    }

    /// Set the limit for how many updates can be performed between rendering.
    /// ie: the maximum number of times update() will return true between calls to render_delta
    ///
    /// Use this if rendering on time is more important than keeping the simulation on time
    /// (which is usually the case for video games).
    pub fn limit(mut self, limit: u32) -> Self {
        self.update_limit = limit;
        self
    }

    /// Remove the update limit
    pub fn unlimit(mut self) -> Self {
        self.update_limit = ::std::u32::MAX;
        self
    }

    /// Restarts the timer at the current time and clears any waiting updates.
    pub fn reset(&mut self) {
        self.last_time = Instant::now();
        self.update_counter = 0;
        self.accumulator = Duration::new(0, 0);
    }

    /// Returns true if enough time has elapsed to perform another update.
    pub fn update(&mut self) -> bool {
        let now = Instant::now();
        self.accumulator += now - self.last_time;
        self.last_time = now;
        if self.accumulator >= self.update_interval {
            // Time for another update
            self.update_counter += 1;
            if self.update_counter > self.update_limit {
                // If too many updates have occured since the last render,
                // skip any waiting updates and return false
                self.accumulator = Duration::new(0, 0);
                self.update_counter = 0;
                false
            } else {
                self.accumulator -= self.update_interval;
                true
            }
        } else {
            // Not ready for another update yet
            false
        }
    }

    /// Return the amount of time (relative to the update period) since the last update tick.
    ///
    /// Also refreshes the update counter (see the `limit` method)
    pub fn render_delta(&mut self) -> f64 {
        self.update_counter = 0;
        duration_to_float(self.accumulator) / duration_to_float(self.update_interval)
    }
}

fn duration_to_float(dur: Duration) -> f64 {
    (dur.as_secs() as f64 + dur.subsec_nanos() as f64 / NANOS_PER_SECOND)
}

// Legacy macro
#[deprecated]
#[macro_export]
macro_rules! fixedstep_loop {
    {
        Step($ticks:expr, $skip:expr),
        Update => $Update:block,
        Render($delta:pat) => $Render:block,
    } => {
        {
            use std::time::{Duration, Instant};
            let ticks = 1.0 / $ticks as f64;
            let ticks_s = ticks as u64;
            let ticks_ns = (ticks.fract() * 1000_000_000.0) as u32;
            let update_interval = Duration::new(ticks_s, ticks_ns);
            let skip_threshold: i32 = 3;

            let mut last = Instant::now();
            let mut accumulator = Duration::new(0, 0);

            let mut should_close = false;

            while !should_close
            {
                let now = Instant::now();
                accumulator += now - last;
                last = now;

                let mut update_count = 0;
                while accumulator > update_interval && update_count < skip_threshold
                {
                    should_close = $Update;
                    update_count += 1;
                    accumulator -= update_interval;
                }

                // Frame skip
                // Do not use for simulations
                if $skip && accumulator > update_interval
                {
                    accumulator = Duration::new(0, 0);
                }

                let elapsed = last.elapsed();
                let $delta = ((elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1000_000_000.0) / ticks).min(1.0);
                $Render
            }
        }
    };
    {
        Step($ticks:expr),
        Update => $Update:block,
        Render($delta:pat) => $Render:block,
    } => {
        fixedstep_loop!(
            Step($ticks, true),
            Update => $Update,
            Render($delta) => $Render,
        )
    };
    {
        Update => $Update:block,
        Render($delta:pat) => $Render:block,
    } => {
        fixedstep_loop!(
            Step(60),
            Update => $Update,
            Render($delta) => $Render,
        )
    };
}