bevy_tasks/slice.rs
1use super::TaskPool;
2
3/// Provides functions for mapping read-only slices across a provided [`TaskPool`].
4pub trait ParallelSlice<T: Sync>: AsRef<[T]> {
5 /// Splits the slice in chunks of size `chunks_size` or less and maps the chunks
6 /// in parallel across the provided `task_pool`. One task is spawned in the task pool
7 /// for every chunk.
8 ///
9 /// The iteration function takes the index of the chunk in the original slice as the
10 /// first argument, and the chunk as the second argument.
11 ///
12 /// Returns a `Vec` of the mapped results in the same order as the input.
13 ///
14 /// # Example
15 ///
16 /// ```
17 /// # use bevy_tasks::prelude::*;
18 /// # use bevy_tasks::TaskPool;
19 /// let task_pool = TaskPool::new();
20 /// let counts = (0..10000).collect::<Vec<u32>>();
21 /// let incremented = counts.par_chunk_map(&task_pool, 100, |_index, chunk| {
22 /// let mut results = Vec::new();
23 /// for count in chunk {
24 /// results.push(*count + 2);
25 /// }
26 /// results
27 /// });
28 /// # let flattened: Vec<_> = incremented.into_iter().flatten().collect();
29 /// # assert_eq!(flattened, (2..10002).collect::<Vec<u32>>());
30 /// ```
31 ///
32 /// # See Also
33 ///
34 /// - [`ParallelSliceMut::par_chunk_map_mut`] for mapping mutable slices.
35 /// - [`ParallelSlice::par_splat_map`] for mapping when a specific chunk size is unknown.
36 fn par_chunk_map<F, R>(&self, task_pool: &TaskPool, chunk_size: usize, f: F) -> Vec<R>
37 where
38 F: Fn(usize, &[T]) -> R + Send + Sync,
39 R: Send + 'static,
40 {
41 let slice = self.as_ref();
42 let f = &f;
43 task_pool.scope(|scope| {
44 for (index, chunk) in slice.chunks(chunk_size).enumerate() {
45 scope.spawn(async move { f(index, chunk) });
46 }
47 })
48 }
49
50 /// Splits the slice into a maximum of `max_tasks` chunks, and maps the chunks in parallel
51 /// across the provided `task_pool`. One task is spawned in the task pool for every chunk.
52 ///
53 /// If `max_tasks` is `None`, this function will attempt to use one chunk per thread in
54 /// `task_pool`.
55 ///
56 /// The iteration function takes the index of the chunk in the original slice as the
57 /// first argument, and the chunk as the second argument.
58 ///
59 /// Returns a `Vec` of the mapped results in the same order as the input.
60 ///
61 /// # Example
62 ///
63 /// ```
64 /// # use bevy_tasks::prelude::*;
65 /// # use bevy_tasks::TaskPool;
66 /// let task_pool = TaskPool::new();
67 /// let counts = (0..10000).collect::<Vec<u32>>();
68 /// let incremented = counts.par_splat_map(&task_pool, None, |_index, chunk| {
69 /// let mut results = Vec::new();
70 /// for count in chunk {
71 /// results.push(*count + 2);
72 /// }
73 /// results
74 /// });
75 /// # let flattened: Vec<_> = incremented.into_iter().flatten().collect();
76 /// # assert_eq!(flattened, (2..10002).collect::<Vec<u32>>());
77 /// ```
78 ///
79 /// # See Also
80 ///
81 /// [`ParallelSliceMut::par_splat_map_mut`] for mapping mutable slices.
82 /// [`ParallelSlice::par_chunk_map`] for mapping when a specific chunk size is desirable.
83 fn par_splat_map<F, R>(&self, task_pool: &TaskPool, max_tasks: Option<usize>, f: F) -> Vec<R>
84 where
85 F: Fn(usize, &[T]) -> R + Send + Sync,
86 R: Send + 'static,
87 {
88 let slice = self.as_ref();
89 let chunk_size = core::cmp::max(
90 1,
91 core::cmp::max(
92 slice.len() / task_pool.thread_num(),
93 slice.len() / max_tasks.unwrap_or(usize::MAX),
94 ),
95 );
96
97 slice.par_chunk_map(task_pool, chunk_size, f)
98 }
99}
100
101impl<S, T: Sync> ParallelSlice<T> for S where S: AsRef<[T]> {}
102
103/// Provides functions for mapping mutable slices across a provided [`TaskPool`].
104pub trait ParallelSliceMut<T: Send>: AsMut<[T]> {
105 /// Splits the slice in chunks of size `chunks_size` or less and maps the chunks
106 /// in parallel across the provided `task_pool`. One task is spawned in the task pool
107 /// for every chunk.
108 ///
109 /// The iteration function takes the index of the chunk in the original slice as the
110 /// first argument, and the chunk as the second argument.
111 ///
112 /// Returns a `Vec` of the mapped results in the same order as the input.
113 ///
114 /// # Example
115 ///
116 /// ```
117 /// # use bevy_tasks::prelude::*;
118 /// # use bevy_tasks::TaskPool;
119 /// let task_pool = TaskPool::new();
120 /// let mut counts = (0..10000).collect::<Vec<u32>>();
121 /// let incremented = counts.par_chunk_map_mut(&task_pool, 100, |_index, chunk| {
122 /// let mut results = Vec::new();
123 /// for count in chunk {
124 /// *count += 5;
125 /// results.push(*count - 2);
126 /// }
127 /// results
128 /// });
129 ///
130 /// assert_eq!(counts, (5..10005).collect::<Vec<u32>>());
131 /// # let flattened: Vec<_> = incremented.into_iter().flatten().collect();
132 /// # assert_eq!(flattened, (3..10003).collect::<Vec<u32>>());
133 /// ```
134 ///
135 /// # See Also
136 ///
137 /// [`ParallelSlice::par_chunk_map`] for mapping immutable slices.
138 /// [`ParallelSliceMut::par_splat_map_mut`] for mapping when a specific chunk size is unknown.
139 fn par_chunk_map_mut<F, R>(&mut self, task_pool: &TaskPool, chunk_size: usize, f: F) -> Vec<R>
140 where
141 F: Fn(usize, &mut [T]) -> R + Send + Sync,
142 R: Send + 'static,
143 {
144 let slice = self.as_mut();
145 let f = &f;
146 task_pool.scope(|scope| {
147 for (index, chunk) in slice.chunks_mut(chunk_size).enumerate() {
148 scope.spawn(async move { f(index, chunk) });
149 }
150 })
151 }
152
153 /// Splits the slice into a maximum of `max_tasks` chunks, and maps the chunks in parallel
154 /// across the provided `task_pool`. One task is spawned in the task pool for every chunk.
155 ///
156 /// If `max_tasks` is `None`, this function will attempt to use one chunk per thread in
157 /// `task_pool`.
158 ///
159 /// The iteration function takes the index of the chunk in the original slice as the
160 /// first argument, and the chunk as the second argument.
161 ///
162 /// Returns a `Vec` of the mapped results in the same order as the input.
163 ///
164 /// # Example
165 ///
166 /// ```
167 /// # use bevy_tasks::prelude::*;
168 /// # use bevy_tasks::TaskPool;
169 /// let task_pool = TaskPool::new();
170 /// let mut counts = (0..10000).collect::<Vec<u32>>();
171 /// let incremented = counts.par_splat_map_mut(&task_pool, None, |_index, chunk| {
172 /// let mut results = Vec::new();
173 /// for count in chunk {
174 /// *count += 5;
175 /// results.push(*count - 2);
176 /// }
177 /// results
178 /// });
179 ///
180 /// assert_eq!(counts, (5..10005).collect::<Vec<u32>>());
181 /// # let flattened: Vec<_> = incremented.into_iter().flatten().collect::<Vec<u32>>();
182 /// # assert_eq!(flattened, (3..10003).collect::<Vec<u32>>());
183 /// ```
184 ///
185 /// # See Also
186 ///
187 /// [`ParallelSlice::par_splat_map`] for mapping immutable slices.
188 /// [`ParallelSliceMut::par_chunk_map_mut`] for mapping when a specific chunk size is desirable.
189 fn par_splat_map_mut<F, R>(
190 &mut self,
191 task_pool: &TaskPool,
192 max_tasks: Option<usize>,
193 f: F,
194 ) -> Vec<R>
195 where
196 F: Fn(usize, &mut [T]) -> R + Send + Sync,
197 R: Send + 'static,
198 {
199 let mut slice = self.as_mut();
200 let chunk_size = core::cmp::max(
201 1,
202 core::cmp::max(
203 slice.len() / task_pool.thread_num(),
204 slice.len() / max_tasks.unwrap_or(usize::MAX),
205 ),
206 );
207
208 slice.par_chunk_map_mut(task_pool, chunk_size, f)
209 }
210}
211
212impl<S, T: Send> ParallelSliceMut<T> for S where S: AsMut<[T]> {}
213
214#[cfg(test)]
215mod tests {
216 use crate::*;
217
218 #[test]
219 fn test_par_chunks_map() {
220 let v = vec![42; 1000];
221 let task_pool = TaskPool::new();
222 let outputs = v.par_splat_map(&task_pool, None, |_, numbers| -> i32 {
223 numbers.iter().sum()
224 });
225
226 let mut sum = 0;
227 for output in outputs {
228 sum += output;
229 }
230
231 assert_eq!(sum, 1000 * 42);
232 }
233
234 #[test]
235 fn test_par_chunks_map_mut() {
236 let mut v = vec![42; 1000];
237 let task_pool = TaskPool::new();
238
239 let outputs = v.par_splat_map_mut(&task_pool, None, |_, numbers| -> i32 {
240 for number in numbers.iter_mut() {
241 *number *= 2;
242 }
243 numbers.iter().sum()
244 });
245
246 let mut sum = 0;
247 for output in outputs {
248 sum += output;
249 }
250
251 assert_eq!(sum, 1000 * 42 * 2);
252 assert_eq!(v[0], 84);
253 }
254
255 #[test]
256 fn test_par_chunks_map_index() {
257 let v = vec![1; 1000];
258 let task_pool = TaskPool::new();
259 let outputs = v.par_chunk_map(&task_pool, 100, |index, numbers| -> i32 {
260 numbers.iter().sum::<i32>() * index as i32
261 });
262
263 assert_eq!(outputs.iter().sum::<i32>(), 100 * (9 * 10) / 2);
264 }
265}