nate_engine_core/
lib.rs

1//!
2//! NEngine Engine
3//! 
4
5use std::sync::atomic::Ordering;
6use std::thread;
7use std::sync::{Arc, RwLock, atomic::AtomicBool};
8use std::collections::BinaryHeap;
9use std::time::{Duration, SystemTime, UNIX_EPOCH};
10use std::fmt::Debug;
11
12use threadpool::ThreadPool;
13
14mod system_wrapper;
15use system_wrapper::SystemWrapper;
16
17mod renderer;
18pub use renderer::Renderer;
19
20/// The basic Engine schedules systems to run at given time intervals in a
21/// a threadpool with a singular thread reserved for UI rendering at a given frame rate
22pub struct Engine<WORLD, E> {
23    // Target frame rate in frames / second
24    pub target_frame_rate: u32,
25    // The World
26    pub world: Arc<RwLock<WORLD>>,
27
28    // Renderer
29    renderer: Option<Box<dyn Renderer<WORLD, Error=E>>>,
30    // Threadpool
31    pool: ThreadPool,
32    // Scheduling Queue (Binary Heap)
33    scheduling_queue: BinaryHeap<SystemWrapper<WORLD>>,
34}
35
36impl<WORLD: Send + Sync + 'static, E: Debug + 'static> Engine<WORLD, E> {
37    /// Create a new engine with workers, a target frame rate, a world, and
38    /// the systems to act on the world (and their update rate (in us))
39    pub fn new(
40        frame_rate: u32,
41        workers: usize,
42        world: Arc<RwLock<WORLD>>,
43        mut systems: Vec<(fn(Arc<RwLock<WORLD>>), u128)>,
44        renderer: Box<dyn Renderer<WORLD, Error=E>>,
45    ) -> Self {
46        if workers < 2 {
47            panic!("The Engine Requires at least 2 Threads to Execute");
48        }
49
50        let mut scheduling_queue = BinaryHeap::new();
51        for (system, update_rate) in systems.drain(..) {
52            scheduling_queue.push(SystemWrapper{
53                system,
54                update_rate,
55                priority: update_rate,
56            })
57        }
58
59        Self {
60            target_frame_rate: frame_rate,
61            world,
62            pool: ThreadPool::new(workers - 1),
63            scheduling_queue,
64            renderer: Some(renderer),
65        }
66    }
67
68    /// Run the Executor
69    pub fn run(&mut self) {
70        let start_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros();
71
72        let c_world = self.world.clone();
73        let frame_delay = 1_000_000 / self.target_frame_rate;
74
75        let running = Arc::new(AtomicBool::new(true));
76        let c_running = running.clone();
77        let cc_running = running.clone();
78
79        ctrlc::set_handler(move || {
80            cc_running.store(false, Ordering::SeqCst);
81        }).expect("Unable to Set Ctrl-C Handler");
82
83        let mut renderer = self.renderer.take().unwrap();
84        let render_thread_handle = thread::spawn(move || {
85            let mut last_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros();
86            while running.load(Ordering::SeqCst) {
87                let cc_world = c_world.clone();
88                let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros();
89                let delta_time = current_time - last_time;
90
91                if frame_delay as u128 > delta_time {
92                    let sleep_time = frame_delay as u128 - delta_time;
93                    thread::sleep(Duration::from_micros(sleep_time as u64));
94                }
95
96                if let Err(err) = renderer.render(cc_world) {
97                    running.store(false, Ordering::SeqCst);
98                    // TODO: Add Logger
99                    println!("Error Occurred in Rendering: {:?}", err);
100                    return renderer;
101                }
102
103                last_time = current_time;
104            }
105            return renderer
106        });
107
108        while c_running.load(Ordering::SeqCst) {
109            if let (sleep_time, Some(mut system_wrapper)) = Self::get_next_job(&mut self.scheduling_queue, start_time) {
110                thread::sleep(Duration::from_micros(sleep_time as u64));
111
112                let c_world = self.world.clone();
113                self.pool.execute(move || (system_wrapper.system)(c_world));
114                system_wrapper.priority += system_wrapper.update_rate;
115                self.scheduling_queue.push(system_wrapper);
116            }
117        }
118
119        let renderer = match render_thread_handle.join() {
120            Ok(renderer) => renderer,
121            Err(err) => panic!("Error Joining Render Thread Handle: {:?}", err),
122        };
123
124        self.renderer.replace(renderer);
125    }
126
127    /// Wait for the next job to run and get it off the scheduling queue
128    fn get_next_job(scheduling_queue: &mut BinaryHeap<SystemWrapper<WORLD>>, start_time: u128) -> (u128, Option<SystemWrapper<WORLD>>) {
129        let elapsed_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros() - start_time;
130        if let Some(system_wrapper) = scheduling_queue.pop() {
131            let next_start_time = system_wrapper.priority;
132            if elapsed_time < next_start_time {
133                let sleep_time = next_start_time.saturating_sub(elapsed_time);
134                return (sleep_time, Some(system_wrapper));
135            }
136            return (0, Some(system_wrapper));
137        }
138        (0, None)
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
147    struct FakeWorld {}
148
149    fn test<WORLD: Send + Sync + 'static>(_world: Arc<RwLock<WORLD>>) {
150        println!("Hello World");
151    }
152
153    #[test]
154    fn text_get_next_job_time() {
155        let start_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_micros();
156
157        let mut scheduling_queue: BinaryHeap<SystemWrapper<FakeWorld>> = BinaryHeap::new();
158        scheduling_queue.push(SystemWrapper{
159            system: test,
160            update_rate: 1_000_000,
161            priority: 1_000_000,
162        });
163
164        let (time, next_job) = Engine::<FakeWorld, String>::get_next_job(&mut scheduling_queue, start_time);
165        assert!(next_job.is_some());
166        assert!(900_000 < time && time < 1_000_000);
167    }
168}