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}