saunter/
tickloop.rs

1//! Contains the [`TickLoop`] struct.
2//! The tick loop is the heart of Saunter; It runs all of your code at a set tick rate (TPS).
3//! If your code takes longer than the tick interval to run,
4//! the tick loop will run as fast as possible until it catches back up.
5
6use log;
7use std::sync::mpsc::{self, Receiver, Sender};
8use std::sync::{Arc, Mutex, RwLock};
9use std::time::{Duration, Instant};
10
11use crate::error::SaunterError;
12use crate::snapshot::{Snapshot, Snapshots};
13type Listener<T, E> =
14    dyn FnMut(f32, Vec<E>, TickLoopControl, Instant) -> Result<T, SaunterError> + Send;
15
16pub enum TickLoopState {
17    Running,
18    Stopped,
19    Paused,
20}
21
22pub struct TickLoopControl {
23    state: Arc<Mutex<TickLoopState>>,
24}
25
26impl TickLoopControl {
27    pub fn stop(&mut self) {
28        let mut state = self.state.lock().unwrap();
29        *state = TickLoopState::Stopped;
30    }
31
32    pub fn pause(&mut self) {
33        let mut state = self.state.lock().unwrap();
34        *state = TickLoopState::Paused;
35    }
36
37    pub fn resume(&mut self) {
38        let mut state = self.state.lock().unwrap();
39        *state = TickLoopState::Running;
40    }
41}
42
43/// The tick loop is the heart of Saunter.
44/// The tick loop runs your code at a set tick rate and generates [`Snapshots`]
45pub struct TickLoop<S: Snapshot, E: Send> {
46    pub listener: Box<Listener<S, E>>,
47    pub tick_length: Duration,
48    reciever: Receiver<E>,
49    state: Arc<Mutex<TickLoopState>>,
50    snapshots: Arc<RwLock<Snapshots<S>>>,
51}
52
53impl<'a, S: Snapshot, E: Send> TickLoop<S, E> {
54    /// Creates a new Loop struct.
55    /// It is recommended to use [`init`](TickLoop::init) instead.
56    pub fn new<F>(
57        listener: F,
58        tps: f32,
59        reciever: Receiver<E>,
60        state: Arc<Mutex<TickLoopState>>,
61        snapshots: Arc<RwLock<Snapshots<S>>>,
62    ) -> Self
63    where
64        F: FnMut(f32, Vec<E>, TickLoopControl, Instant) -> Result<S, SaunterError> + Send + 'static,
65    {
66        let tick_length = Duration::from_secs_f32(1.0 / tps);
67        TickLoop {
68            listener: Box::new(listener),
69            tick_length,
70            reciever,
71            snapshots,
72            state,
73        }
74    }
75
76    /// Creates a new Loop struct and returns a [`Sender`] to send events to the loop.
77    pub fn init<F>(
78        listener: F,
79        tps: f32,
80    ) -> (
81        Self,
82        Sender<E>,
83        TickLoopControl,
84        Arc<RwLock<Snapshots<S>>>,
85    )
86    where
87        F: FnMut(f32, Vec<E>, TickLoopControl, Instant) -> Result<S, SaunterError> + Send + 'static,
88    {
89        let (event_sender, event_reciever) = mpsc::channel::<E>();
90        let snapshots = Arc::new(RwLock::new(Snapshots::new()));
91        let state = Arc::new(Mutex::new(TickLoopState::Running));
92
93        (
94            Self::new(listener, tps, event_reciever, state.clone(), snapshots.clone()),
95            event_sender,
96            TickLoopControl {
97                state: state.clone(),
98            },
99            snapshots,
100        )
101    }
102
103    /// Starts the loop. This function will block the current thread. So the loop should be sent to a new thread, and start called on it there.
104    pub fn start(&mut self) {
105        let mut deficit = Duration::from_secs_f32(0.0);
106
107        'a: loop {
108            let tick_time = std::time::Instant::now();
109
110            match *self.state.lock().unwrap() {
111                TickLoopState::Stopped => break 'a,
112                TickLoopState::Paused => {
113                    spin_sleep::sleep(self.tick_length);
114                    continue 'a;
115                }
116                TickLoopState::Running => {}
117            }
118
119            let events = self.reciever.try_iter().collect();
120
121            if let Ok(snapshot) = (self.listener)(
122                self.tick_length.as_secs_f32(),
123                events,
124                TickLoopControl {
125                    state: self.state.clone(),
126                },
127                tick_time,
128            ) {
129                let mut tick_wlock = self.snapshots.write().unwrap();
130                log::debug!("lock aquired {:?}", std::time::Instant::now());
131                (*tick_wlock).update(snapshot);
132                // Drop the write lock so the read lock can be acquired.
133            }
134            log::debug!("lock dropped {:?}", std::time::Instant::now());
135
136            let elapsed = tick_time.elapsed();
137            if elapsed < self.tick_length {
138                let mut sleep_dur = self.tick_length - elapsed;
139                // Automatically catch the loop back up when there is a deficit.
140                log::debug!("deficit: {:?}", deficit);
141                if deficit < sleep_dur {
142                    sleep_dur -= deficit;
143                    deficit = Duration::from_secs_f32(0.0);
144                } else {
145                    deficit -= sleep_dur;
146                    sleep_dur *= 0;
147                }
148
149                spin_sleep::sleep(self.tick_length - elapsed);
150            } else {
151                let current_tick_deficit = elapsed - self.tick_length;
152                deficit += current_tick_deficit;
153                log::debug!("tick took too long");
154            }
155
156            log::debug!("actual tick length {:?}", tick_time.elapsed());
157        }
158    }
159}