fixedstep/
lib.rs

1
2use std::time::{Duration, Instant};
3
4const NANOS_PER_SECOND: f64 = 1_000_000_000.0;
5
6pub struct FixedStep {
7    last_time: Instant,
8    update_interval: Duration,
9    accumulator: Duration,
10    update_counter: u32,
11    update_limit: u32,
12}
13
14impl FixedStep {
15    /// Create and start a new fixedstep timer with the given frequency in Hz
16    pub fn start(hz: f64) -> Self {
17        let seconds = 1.0 / hz;
18        let full_seconds = seconds as u64;
19        let remaining_nanos = (seconds.fract() * NANOS_PER_SECOND) as u32;
20        FixedStep {
21            update_interval: Duration::new(full_seconds, remaining_nanos),
22            last_time: Instant::now(),
23            accumulator: Duration::new(0, 0),
24            update_counter: 0,
25            update_limit: 3,
26        }
27    }
28
29    /// Set the limit for how many updates can be performed between rendering.
30    /// ie: the maximum number of times update() will return true between calls to render_delta
31    ///
32    /// Use this if rendering on time is more important than keeping the simulation on time
33    /// (which is usually the case for video games).
34    pub fn limit(mut self, limit: u32) -> Self {
35        self.update_limit = limit;
36        self
37    }
38
39    /// Remove the update limit
40    pub fn unlimit(mut self) -> Self {
41        self.update_limit = ::std::u32::MAX;
42        self
43    }
44
45    /// Restarts the timer at the current time and clears any waiting updates.
46    pub fn reset(&mut self) {
47        self.last_time = Instant::now();
48        self.update_counter = 0;
49        self.accumulator = Duration::new(0, 0);
50    }
51
52    /// Returns true if enough time has elapsed to perform another update.
53    pub fn update(&mut self) -> bool {
54        let now = Instant::now();
55        self.accumulator += now - self.last_time;
56        self.last_time = now;
57        if self.accumulator >= self.update_interval {
58            // Time for another update
59            self.update_counter += 1;
60            if self.update_counter > self.update_limit {
61                // If too many updates have occured since the last render,
62                // skip any waiting updates and return false
63                self.accumulator = Duration::new(0, 0);
64                self.update_counter = 0;
65                false
66            } else {
67                self.accumulator -= self.update_interval;
68                true
69            }
70        } else {
71            // Not ready for another update yet
72            false
73        }
74    }
75
76    /// Return the amount of time (relative to the update period) since the last update tick.
77    ///
78    /// Also refreshes the update counter (see the `limit` method)
79    pub fn render_delta(&mut self) -> f64 {
80        self.update_counter = 0;
81        duration_to_float(self.accumulator) / duration_to_float(self.update_interval)
82    }
83}
84
85fn duration_to_float(dur: Duration) -> f64 {
86    (dur.as_secs() as f64 + dur.subsec_nanos() as f64 / NANOS_PER_SECOND)
87}
88
89// Legacy macro
90#[deprecated]
91#[macro_export]
92macro_rules! fixedstep_loop {
93    {
94        Step($ticks:expr, $skip:expr),
95        Update => $Update:block,
96        Render($delta:pat) => $Render:block,
97    } => {
98        {
99            use std::time::{Duration, Instant};
100            let ticks = 1.0 / $ticks as f64;
101            let ticks_s = ticks as u64;
102            let ticks_ns = (ticks.fract() * 1000_000_000.0) as u32;
103            let update_interval = Duration::new(ticks_s, ticks_ns);
104            let skip_threshold: i32 = 3;
105
106            let mut last = Instant::now();
107            let mut accumulator = Duration::new(0, 0);
108
109            let mut should_close = false;
110
111            while !should_close
112            {
113                let now = Instant::now();
114                accumulator += now - last;
115                last = now;
116
117                let mut update_count = 0;
118                while accumulator > update_interval && update_count < skip_threshold
119                {
120                    should_close = $Update;
121                    update_count += 1;
122                    accumulator -= update_interval;
123                }
124
125                // Frame skip
126                // Do not use for simulations
127                if $skip && accumulator > update_interval
128                {
129                    accumulator = Duration::new(0, 0);
130                }
131
132                let elapsed = last.elapsed();
133                let $delta = ((elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 / 1000_000_000.0) / ticks).min(1.0);
134                $Render
135            }
136        }
137    };
138    {
139        Step($ticks:expr),
140        Update => $Update:block,
141        Render($delta:pat) => $Render:block,
142    } => {
143        fixedstep_loop!(
144            Step($ticks, true),
145            Update => $Update,
146            Render($delta) => $Render,
147        )
148    };
149    {
150        Update => $Update:block,
151        Render($delta:pat) => $Render:block,
152    } => {
153        fixedstep_loop!(
154            Step(60),
155            Update => $Update,
156            Render($delta) => $Render,
157        )
158    };
159}