1use std::any::Any;
19use std::collections::VecDeque;
20use std::sync::{Arc, Condvar, Mutex};
21use std::thread::JoinHandle;
22
23use bevy_ecs::prelude::Resource;
24
25type Job = Box<dyn FnOnce() + Send + 'static>;
26
27struct Queue {
28 jobs: VecDeque<Job>,
29 closed: bool,
30}
31
32struct Inner {
33 queue: Mutex<Queue>,
34 signal: Condvar,
35}
36
37enum SlotState<T> {
38 Empty,
39 Done(T),
40 Failed(Box<dyn Any + Send + 'static>),
41}
42
43struct Slot<T> {
44 value: Mutex<SlotState<T>>,
45 ready: Condvar,
46}
47
48pub struct Task<T> {
53 slot: Arc<Slot<T>>,
54}
55
56impl<T> Task<T> {
57 pub fn join(self) -> T {
59 let mut guard = self.slot.value.lock().unwrap_or_else(|e| e.into_inner());
60 loop {
61 if !matches!(&*guard, SlotState::Empty) {
62 break;
63 }
64 guard = self
65 .slot
66 .ready
67 .wait(guard)
68 .unwrap_or_else(|e| e.into_inner());
69 }
70 match std::mem::replace(&mut *guard, SlotState::Empty) {
71 SlotState::Done(value) => value,
72 SlotState::Failed(payload) => std::panic::resume_unwind(payload),
73 SlotState::Empty => unreachable!("wait returned on an empty slot"),
74 }
75 }
76
77 pub fn is_finished(&self) -> bool {
79 !matches!(
80 &*self.slot.value.lock().unwrap_or_else(|e| e.into_inner()),
81 SlotState::Empty
82 )
83 }
84}
85
86#[derive(Resource)]
92pub struct TaskPool {
93 inner: Arc<Inner>,
94 workers: Vec<JoinHandle<()>>,
95}
96
97impl TaskPool {
98 pub fn new(threads: usize) -> Self {
100 let threads = threads.max(1);
101 let inner = Arc::new(Inner {
102 queue: Mutex::new(Queue {
103 jobs: VecDeque::new(),
104 closed: false,
105 }),
106 signal: Condvar::new(),
107 });
108 let mut workers = Vec::with_capacity(threads);
109 for index in 0..threads {
110 let inner = Arc::clone(&inner);
111 let handle = std::thread::Builder::new()
112 .name(format!("repame-task-{index}"))
113 .spawn(move || worker(inner))
114 .expect("failed to start a task-pool worker");
115 workers.push(handle);
116 }
117 Self { inner, workers }
118 }
119
120 pub fn auto() -> Self {
122 let threads = std::thread::available_parallelism()
123 .map(|n| n.get())
124 .unwrap_or(1);
125 Self::new(threads)
126 }
127
128 pub fn threads(&self) -> usize {
130 self.workers.len()
131 }
132
133 pub fn spawn<F, T>(&self, job: F) -> Task<T>
135 where
136 F: FnOnce() -> T + Send + 'static,
137 T: Send + 'static,
138 {
139 let slot = Arc::new(Slot {
140 value: Mutex::new(SlotState::Empty),
141 ready: Condvar::new(),
142 });
143 let done = Arc::clone(&slot);
144 self.push(Box::new(move || {
145 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(job));
146 let mut guard = done.value.lock().unwrap_or_else(|e| e.into_inner());
147 *guard = match outcome {
148 Ok(value) => SlotState::Done(value),
149 Err(payload) => SlotState::Failed(payload),
150 };
151 done.ready.notify_all();
152 }));
153 Task { slot }
154 }
155
156 pub fn map<I, F, T>(&self, items: I, job: F) -> Vec<T>
158 where
159 I: IntoIterator,
160 F: Fn(I::Item) -> T + Send + Sync + 'static,
161 I::Item: Send + 'static,
162 T: Send + 'static,
163 {
164 let job = Arc::new(job);
165 let mut tasks = Vec::new();
166 for item in items {
167 let job = Arc::clone(&job);
168 tasks.push(self.spawn(move || job(item)));
169 }
170 tasks.into_iter().map(Task::join).collect()
171 }
172
173 fn push(&self, job: Job) {
174 let mut queue = self.inner.queue.lock().unwrap_or_else(|e| e.into_inner());
175 if queue.closed {
176 return;
177 }
178 queue.jobs.push_back(job);
179 self.inner.signal.notify_one();
180 }
181}
182
183impl Drop for TaskPool {
184 fn drop(&mut self) {
185 {
186 let mut queue = self.inner.queue.lock().unwrap_or_else(|e| e.into_inner());
187 queue.closed = true;
188 }
189 self.inner.signal.notify_all();
190 for worker in self.workers.drain(..) {
191 let _ = worker.join();
192 }
193 }
194}
195
196fn worker(inner: Arc<Inner>) {
197 loop {
198 let job = {
199 let mut queue = inner.queue.lock().unwrap_or_else(|e| e.into_inner());
200 loop {
201 if let Some(job) = queue.jobs.pop_front() {
202 break Some(job);
203 }
204 if queue.closed {
205 break None;
206 }
207 queue = inner.signal.wait(queue).unwrap_or_else(|e| e.into_inner());
208 }
209 };
210 match job {
211 Some(job) => job(),
212 None => return,
213 }
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use std::sync::atomic::{AtomicUsize, Ordering};
221
222 #[test]
223 fn spawn_joins_to_the_value() {
224 let pool = TaskPool::new(2);
225 assert_eq!(pool.spawn(|| 6 * 7).join(), 42);
226 assert_eq!(pool.threads(), 2);
227 }
228
229 #[test]
230 fn one_worker_is_the_floor() {
231 let pool = TaskPool::new(0);
232 assert_eq!(pool.threads(), 1);
233 assert_eq!(pool.spawn(|| 1 + 1).join(), 2);
234 }
235
236 #[test]
237 fn map_preserves_input_order() {
238 let pool = TaskPool::new(4);
239 let out = pool.map(0..64, |n| n * n);
240 assert_eq!(out, (0..64).map(|n| n * n).collect::<Vec<_>>());
241 }
242
243 #[test]
244 fn jobs_actually_run_off_thread() {
245 let pool = TaskPool::new(3);
246 let counter = Arc::new(AtomicUsize::new(0));
247 let mut tasks = Vec::new();
248 for _ in 0..32 {
249 let counter = Arc::clone(&counter);
250 tasks.push(pool.spawn(move || {
251 counter.fetch_add(1, Ordering::SeqCst);
252 }));
253 }
254 for task in tasks {
255 task.join();
256 }
257 assert_eq!(counter.load(Ordering::SeqCst), 32);
258 }
259
260 #[test]
261 #[should_panic(expected = "boom")]
262 fn a_panicking_job_re_raises_at_the_joiner() {
263 let pool = TaskPool::new(1);
264 pool.spawn(|| panic!("boom")).join();
265 }
266
267 #[test]
268 fn a_panicked_worker_does_not_stop_the_pool() {
269 let pool = TaskPool::new(2);
270 let failed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
271 pool.spawn(|| panic!("boom")).join();
272 }));
273 assert!(failed.is_err());
274 assert_eq!(pool.spawn(|| 3).join(), 3);
275 }
276
277 #[test]
278 fn dropping_the_pool_joins_its_workers() {
279 let pool = TaskPool::new(2);
280 let counter = Arc::new(AtomicUsize::new(0));
281 let c = Arc::clone(&counter);
282 let task = pool.spawn(move || {
283 c.fetch_add(1, Ordering::SeqCst);
284 });
285 task.join();
286 drop(pool);
287 assert_eq!(counter.load(Ordering::SeqCst), 1);
288 }
289}