Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_tasks/src/slice.rs
6604 views
1
use super::TaskPool;
2
use alloc::vec::Vec;
3
4
/// Provides functions for mapping read-only slices across a provided [`TaskPool`].
5
pub 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
102
impl<S, T: Sync> ParallelSlice<T> for S where S: AsRef<[T]> {}
103
104
/// Provides functions for mapping mutable slices across a provided [`TaskPool`].
105
pub 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
213
impl<S, T: Send> ParallelSliceMut<T> for S where S: AsMut<[T]> {}
214
215
#[cfg(test)]
216
mod 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
}
268
269