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}