1use 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
20pub struct Engine<WORLD, E> {
23 pub target_frame_rate: u32,
25 pub world: Arc<RwLock<WORLD>>,
27
28 renderer: Option<Box<dyn Renderer<WORLD, Error=E>>>,
30 pool: ThreadPool,
32 scheduling_queue: BinaryHeap<SystemWrapper<WORLD>>,
34}
35
36impl<WORLD: Send + Sync + 'static, E: Debug + 'static> Engine<WORLD, E> {
37 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 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 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 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}