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